test(whatsapp): add context isolation coverage
Includes outbound gating, threading fallback, and web auto-reply context assertions.
This commit is contained in:
@@ -23,6 +23,21 @@ describe("buildThreadingToolContext", () => {
|
|||||||
expect(result.currentChannelId).toBe("123@g.us");
|
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", () => {
|
it("uses the recipient id for other channels", () => {
|
||||||
const sessionCtx = {
|
const sessionCtx = {
|
||||||
Provider: "telegram",
|
Provider: "telegram",
|
||||||
|
|||||||
@@ -12,6 +12,14 @@ const slackConfig = {
|
|||||||
},
|
},
|
||||||
} as ClawdbotConfig;
|
} as ClawdbotConfig;
|
||||||
|
|
||||||
|
const whatsappConfig = {
|
||||||
|
channels: {
|
||||||
|
whatsapp: {
|
||||||
|
allowFrom: ["*"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ClawdbotConfig;
|
||||||
|
|
||||||
describe("runMessageAction context isolation", () => {
|
describe("runMessageAction context isolation", () => {
|
||||||
it("allows send when target matches current channel", async () => {
|
it("allows send when target matches current channel", async () => {
|
||||||
const result = await runMessageAction({
|
const result = await runMessageAction({
|
||||||
@@ -60,4 +68,36 @@ describe("runMessageAction context isolation", () => {
|
|||||||
}),
|
}),
|
||||||
).rejects.toThrow(/Cross-context messaging denied/);
|
).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/);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -168,6 +168,48 @@ describe("web auto-reply", () => {
|
|||||||
expect(payload.Body).toContain("@bot ping");
|
expect(payload.Body).toContain("@bot ping");
|
||||||
expect(payload.Body).toContain("[from: Bob (+222)]");
|
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<void>)
|
||||||
|
| undefined;
|
||||||
|
const listenerFactory = async (opts: {
|
||||||
|
onMessage: (msg: import("./inbound.js").WebInboundMessage) => Promise<void>;
|
||||||
|
}) => {
|
||||||
|
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 () => {
|
it("detects LID mentions using authDir mapping", async () => {
|
||||||
const sendMedia = vi.fn();
|
const sendMedia = vi.fn();
|
||||||
const reply = vi.fn().mockResolvedValue(undefined);
|
const reply = vi.fn().mockResolvedValue(undefined);
|
||||||
|
|||||||
Reference in New Issue
Block a user