From 3356aae7048722cf87cb4a0255f4f4c7a08a5791 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 18 Feb 2026 12:04:09 +0000 Subject: [PATCH] test(cron): dedupe delivery target tests and add coverage --- .../isolated-agent/delivery-target.test.ts | 112 +++++++++++++----- 1 file changed, 80 insertions(+), 32 deletions(-) diff --git a/src/cron/isolated-agent/delivery-target.test.ts b/src/cron/isolated-agent/delivery-target.test.ts index f5627fc3b5f..1b61407f4e4 100644 --- a/src/cron/isolated-agent/delivery-target.test.ts +++ b/src/cron/isolated-agent/delivery-target.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it, vi } from "vitest"; +import { DEFAULT_CHAT_CHANNEL } from "../../channels/registry.js"; import type { OpenClawConfig } from "../../config/config.js"; vi.mock("../../config/sessions.js", () => ({ @@ -12,6 +13,7 @@ vi.mock("../../infra/outbound/channel-selection.js", () => ({ })); import { loadSessionStore } from "../../config/sessions.js"; +import { resolveMessageChannelSelection } from "../../infra/outbound/channel-selection.js"; import { resolveDeliveryTarget } from "./delivery-target.js"; function makeCfg(overrides?: Partial): OpenClawConfig { @@ -22,9 +24,34 @@ function makeCfg(overrides?: Partial): OpenClawConfig { } as OpenClawConfig; } +const AGENT_ID = "agent-b"; +const DEFAULT_TARGET = { + channel: "telegram" as const, + to: "123456", +}; + +type SessionStore = ReturnType; + +function setMainSessionEntry(entry?: SessionStore[string]) { + const store = entry ? ({ "agent:test:main": entry } as SessionStore) : ({} as SessionStore); + vi.mocked(loadSessionStore).mockReturnValue(store); +} + +async function resolveForAgent(params: { + cfg: OpenClawConfig; + target?: { channel?: "last" | "telegram"; to?: string }; +}) { + const channel = params.target ? params.target.channel : DEFAULT_TARGET.channel; + const to = params.target && "to" in params.target ? params.target.to : DEFAULT_TARGET.to; + return resolveDeliveryTarget(params.cfg, AGENT_ID, { + channel, + to, + }); +} + describe("resolveDeliveryTarget", () => { it("falls back to bound accountId when session has no lastAccountId", async () => { - vi.mocked(loadSessionStore).mockReturnValue({}); + setMainSessionEntry(undefined); const cfg = makeCfg({ bindings: [ @@ -35,23 +62,18 @@ describe("resolveDeliveryTarget", () => { ], }); - const result = await resolveDeliveryTarget(cfg, "agent-b", { - channel: "telegram", - to: "123456", - }); + const result = await resolveForAgent({ cfg }); expect(result.accountId).toBe("account-b"); }); it("preserves session lastAccountId when present", async () => { - vi.mocked(loadSessionStore).mockReturnValue({ - "agent:test:main": { - sessionId: "sess-1", - updatedAt: 1000, - lastChannel: "telegram", - lastTo: "123456", - lastAccountId: "session-account", - }, + setMainSessionEntry({ + sessionId: "sess-1", + updatedAt: 1000, + lastChannel: "telegram", + lastTo: "123456", + lastAccountId: "session-account", }); const cfg = makeCfg({ @@ -63,30 +85,24 @@ describe("resolveDeliveryTarget", () => { ], }); - const result = await resolveDeliveryTarget(cfg, "agent-b", { - channel: "telegram", - to: "123456", - }); + const result = await resolveForAgent({ cfg }); // Session-derived accountId should take precedence over binding expect(result.accountId).toBe("session-account"); }); it("returns undefined accountId when no binding and no session", async () => { - vi.mocked(loadSessionStore).mockReturnValue({}); + setMainSessionEntry(undefined); const cfg = makeCfg({ bindings: [] }); - const result = await resolveDeliveryTarget(cfg, "agent-b", { - channel: "telegram", - to: "123456", - }); + const result = await resolveForAgent({ cfg }); expect(result.accountId).toBeUndefined(); }); it("selects correct binding when multiple agents have bindings", async () => { - vi.mocked(loadSessionStore).mockReturnValue({}); + setMainSessionEntry(undefined); const cfg = makeCfg({ bindings: [ @@ -101,16 +117,13 @@ describe("resolveDeliveryTarget", () => { ], }); - const result = await resolveDeliveryTarget(cfg, "agent-b", { - channel: "telegram", - to: "123456", - }); + const result = await resolveForAgent({ cfg }); expect(result.accountId).toBe("account-b"); }); it("ignores bindings for different channels", async () => { - vi.mocked(loadSessionStore).mockReturnValue({}); + setMainSessionEntry(undefined); const cfg = makeCfg({ bindings: [ @@ -121,11 +134,46 @@ describe("resolveDeliveryTarget", () => { ], }); - const result = await resolveDeliveryTarget(cfg, "agent-b", { - channel: "telegram", - to: "123456", - }); + const result = await resolveForAgent({ cfg }); expect(result.accountId).toBeUndefined(); }); + + it("drops session threadId when destination does not match the previous recipient", async () => { + setMainSessionEntry({ + sessionId: "sess-2", + updatedAt: 1000, + lastChannel: "telegram", + lastTo: "999999", + lastThreadId: "thread-1", + }); + + const result = await resolveForAgent({ cfg: makeCfg({ bindings: [] }) }); + expect(result.threadId).toBeUndefined(); + }); + + it("keeps session threadId when destination matches the previous recipient", async () => { + setMainSessionEntry({ + sessionId: "sess-3", + updatedAt: 1000, + lastChannel: "telegram", + lastTo: "123456", + lastThreadId: "thread-2", + }); + + const result = await resolveForAgent({ cfg: makeCfg({ bindings: [] }) }); + expect(result.threadId).toBe("thread-2"); + }); + + it("falls back to default channel when selection probe fails", async () => { + setMainSessionEntry(undefined); + vi.mocked(resolveMessageChannelSelection).mockRejectedValueOnce(new Error("no selection")); + + const result = await resolveForAgent({ + cfg: makeCfg({ bindings: [] }), + target: { channel: "last", to: undefined }, + }); + expect(result.channel).toBe(DEFAULT_CHAT_CHANNEL); + expect(result.to).toBeUndefined(); + }); });