From fd41000bc3a2005a6de8dabdd0279f8673174d78 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 14 Jan 2026 23:23:28 +0000 Subject: [PATCH] test(whatsapp): add context isolation coverage Includes outbound gating, threading fallback, and web auto-reply context assertions. --- .../reply/agent-runner-utils.test.ts | 15 +++++++ .../outbound/message-action-runner.test.ts | 40 ++++++++++++++++++ ...oup-chats-injects-history-replying.test.ts | 42 +++++++++++++++++++ 3 files changed, 97 insertions(+) diff --git a/src/auto-reply/reply/agent-runner-utils.test.ts b/src/auto-reply/reply/agent-runner-utils.test.ts index c10a8f0e4..f51f90f31 100644 --- a/src/auto-reply/reply/agent-runner-utils.test.ts +++ b/src/auto-reply/reply/agent-runner-utils.test.ts @@ -23,6 +23,21 @@ describe("buildThreadingToolContext", () => { expect(result.currentChannelId).toBe("123@g.us"); }); + it("falls back to To for WhatsApp when From is missing", () => { + const sessionCtx = { + Provider: "whatsapp", + To: "+15550001", + } as TemplateContext; + + const result = buildThreadingToolContext({ + sessionCtx, + config: cfg, + hasRepliedRef: undefined, + }); + + expect(result.currentChannelId).toBe("+15550001"); + }); + it("uses the recipient id for other channels", () => { const sessionCtx = { Provider: "telegram", diff --git a/src/infra/outbound/message-action-runner.test.ts b/src/infra/outbound/message-action-runner.test.ts index 7dc76ef71..58954e205 100644 --- a/src/infra/outbound/message-action-runner.test.ts +++ b/src/infra/outbound/message-action-runner.test.ts @@ -12,6 +12,14 @@ const slackConfig = { }, } as ClawdbotConfig; +const whatsappConfig = { + channels: { + whatsapp: { + allowFrom: ["*"], + }, + }, +} as ClawdbotConfig; + describe("runMessageAction context isolation", () => { it("allows send when target matches current channel", async () => { const result = await runMessageAction({ @@ -60,4 +68,36 @@ describe("runMessageAction context isolation", () => { }), ).rejects.toThrow(/Cross-context messaging denied/); }); + + it("allows WhatsApp send when target matches current chat", async () => { + const result = await runMessageAction({ + cfg: whatsappConfig, + action: "send", + params: { + channel: "whatsapp", + to: "group:123@g.us", + message: "hi", + }, + toolContext: { currentChannelId: "123@g.us" }, + dryRun: true, + }); + + expect(result.kind).toBe("send"); + }); + + it("blocks WhatsApp send when target differs from current chat", async () => { + await expect( + runMessageAction({ + cfg: whatsappConfig, + action: "send", + params: { + channel: "whatsapp", + to: "456@g.us", + message: "hi", + }, + toolContext: { currentChannelId: "123@g.us" }, + dryRun: true, + }), + ).rejects.toThrow(/Cross-context messaging denied/); + }); }); diff --git a/src/web/auto-reply.web-auto-reply.requires-mention-group-chats-injects-history-replying.test.ts b/src/web/auto-reply.web-auto-reply.requires-mention-group-chats-injects-history-replying.test.ts index 2f208c6e3..8b184f5a0 100644 --- a/src/web/auto-reply.web-auto-reply.requires-mention-group-chats-injects-history-replying.test.ts +++ b/src/web/auto-reply.web-auto-reply.requires-mention-group-chats-injects-history-replying.test.ts @@ -168,6 +168,48 @@ describe("web auto-reply", () => { expect(payload.Body).toContain("@bot ping"); expect(payload.Body).toContain("[from: Bob (+222)]"); }); + + it("passes conversation id through as From for group replies", async () => { + const sendMedia = vi.fn(); + const reply = vi.fn().mockResolvedValue(undefined); + const sendComposing = vi.fn(); + const resolver = vi.fn().mockResolvedValue({ text: "ok" }); + + let capturedOnMessage: + | ((msg: import("./inbound.js").WebInboundMessage) => Promise) + | undefined; + const listenerFactory = async (opts: { + onMessage: (msg: import("./inbound.js").WebInboundMessage) => Promise; + }) => { + capturedOnMessage = opts.onMessage; + return { close: vi.fn() }; + }; + + await monitorWebChannel(false, listenerFactory, false, resolver); + expect(capturedOnMessage).toBeDefined(); + + await capturedOnMessage?.({ + body: "@bot ping", + from: "123@g.us", + conversationId: "123@g.us", + chatId: "123@g.us", + chatType: "group", + to: "+2", + id: "g1", + senderE164: "+222", + senderName: "Bob", + mentionedJids: ["999@s.whatsapp.net"], + selfE164: "+999", + selfJid: "999@s.whatsapp.net", + sendComposing, + reply, + sendMedia, + }); + + const payload = resolver.mock.calls[0]?.[0] as { From?: string; To?: string }; + expect(payload.From).toBe("123@g.us"); + expect(payload.To).toBe("+2"); + }); it("detects LID mentions using authDir mapping", async () => { const sendMedia = vi.fn(); const reply = vi.fn().mockResolvedValue(undefined);