Skip to content

Commit afe26f5

Browse files
committed
test: clear whatsapp inbound dispatch broad matchers
1 parent 9ac4aef commit afe26f5

1 file changed

Lines changed: 119 additions & 83 deletions

File tree

extensions/whatsapp/src/auto-reply/monitor/inbound-dispatch.test.ts

Lines changed: 119 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,57 @@ function getCapturedReplyOptions() {
198198
)?.replyOptions;
199199
}
200200

201+
function requireRecord(value: unknown, label: string): Record<string, unknown> {
202+
expect(typeof value).toBe("object");
203+
expect(value).not.toBeNull();
204+
if (typeof value !== "object" || value === null) {
205+
throw new Error(`${label} was not an object`);
206+
}
207+
return value as Record<string, unknown>;
208+
}
209+
210+
function expectRecordFields(record: Record<string, unknown>, fields: Record<string, unknown>) {
211+
for (const [key, value] of Object.entries(fields)) {
212+
expect(record[key]).toEqual(value);
213+
}
214+
}
215+
216+
function requireMockArg(
217+
mock: { mock: { calls: unknown[][] } },
218+
callIndex: number,
219+
argIndex: number,
220+
label: string,
221+
) {
222+
return requireRecord(mock.mock.calls[callIndex]?.[argIndex], label);
223+
}
224+
225+
function requireLastMockArg(
226+
mock: { mock: { calls: unknown[][] } },
227+
argIndex: number,
228+
label: string,
229+
) {
230+
const callIndex = mock.mock.calls.length - 1;
231+
return requireMockArg(mock, callIndex, argIndex, label);
232+
}
233+
234+
function expectReplyResultFields(
235+
deliverReply: { mock: { calls: unknown[][] } },
236+
fields: Record<string, unknown>,
237+
) {
238+
const params = requireLastMockArg(deliverReply, 0, "deliver reply params");
239+
expectRecordFields(requireRecord(params.replyResult, "reply result"), fields);
240+
}
241+
242+
function expectRememberSentContextFields(
243+
rememberSentText: { mock: { calls: unknown[][] } },
244+
text: unknown,
245+
fields: Record<string, unknown>,
246+
) {
247+
const call = rememberSentText.mock.calls.at(-1);
248+
expect(call?.[0]).toBe(text);
249+
expectRecordFields(requireRecord(call?.[1], "remember sent context"), fields);
250+
}
251+
201252
type BufferedReplyParams = Parameters<typeof dispatchWhatsAppBufferedReply>[0];
202253

203254
function makeReplyLogger(): BufferedReplyParams["replyLogger"] {
@@ -288,7 +339,7 @@ describe("whatsapp inbound dispatch", () => {
288339
},
289340
});
290341

291-
expect(ctx).toMatchObject({
342+
expectRecordFields(requireRecord(ctx, "inbound context"), {
292343
Body: "Alice: hi",
293344
BodyForAgent: "hi",
294345
BodyForCommands: "hi",
@@ -321,7 +372,7 @@ describe("whatsapp inbound dispatch", () => {
321372
transcript: "spoken transcript",
322373
});
323374

324-
expect(ctx).toMatchObject({
375+
expectRecordFields(requireRecord(ctx, "voice inbound context"), {
325376
Body: "spoken transcript",
326377
BodyForAgent: "spoken transcript",
327378
BodyForCommands: "<media:audio>",
@@ -520,14 +571,10 @@ describe("whatsapp inbound dispatch", () => {
520571
);
521572
expect(deliverReply).toHaveBeenCalledTimes(1);
522573
expect(rememberSentText).toHaveBeenCalledTimes(1);
523-
expect(deliverReply).toHaveBeenLastCalledWith(
524-
expect.objectContaining({
525-
replyResult: expect.objectContaining({
526-
mediaUrls: ["/tmp/generated.jpg"],
527-
text: "generated image",
528-
}),
529-
}),
530-
);
574+
expectReplyResultFields(deliverReply, {
575+
mediaUrls: ["/tmp/generated.jpg"],
576+
text: "generated image",
577+
});
531578

532579
await deliver?.({ text: "block payload" }, { kind: "block" });
533580
await deliver?.({ text: "final payload" }, { kind: "final" });
@@ -560,27 +607,30 @@ describe("whatsapp inbound dispatch", () => {
560607
const deliver = getCapturedDeliver();
561608
await deliver?.({ text: "final payload" }, { kind: "final" });
562609

563-
expect(deliverInboundReplyWithMessageSendContextMock).toHaveBeenCalledWith(
564-
expect.objectContaining({
565-
channel: "whatsapp",
566-
accountId: "default",
567-
agentId: "main",
568-
to: "+1000",
569-
payload: expect.objectContaining({ text: "final payload" }),
570-
info: { kind: "final" },
571-
ctxPayload: expect.objectContaining({
572-
SessionKey: "agent:main:whatsapp:+15551234567",
573-
}),
574-
}),
610+
const durableParams = requireMockArg(
611+
deliverInboundReplyWithMessageSendContextMock,
612+
0,
613+
0,
614+
"durable delivery params",
575615
);
616+
expectRecordFields(durableParams, {
617+
channel: "whatsapp",
618+
accountId: "default",
619+
agentId: "main",
620+
to: "+1000",
621+
info: { kind: "final" },
622+
});
623+
expectRecordFields(requireRecord(durableParams.payload, "durable payload"), {
624+
text: "final payload",
625+
});
626+
expectRecordFields(requireRecord(durableParams.ctxPayload, "durable context"), {
627+
SessionKey: "agent:main:whatsapp:+15551234567",
628+
});
576629
expect(deliverReply).not.toHaveBeenCalled();
577-
expect(rememberSentText).toHaveBeenCalledWith(
578-
"final payload",
579-
expect.objectContaining({
580-
combinedBody: "incoming",
581-
combinedBodySessionKey: "agent:main:whatsapp:+15551234567",
582-
}),
583-
);
630+
expectRememberSentContextFields(rememberSentText, "final payload", {
631+
combinedBody: "incoming",
632+
combinedBodySessionKey: "agent:main:whatsapp:+15551234567",
633+
});
584634
});
585635

586636
it("does not fall back when durable WhatsApp delivery suppresses a send", async () => {
@@ -603,13 +653,19 @@ describe("whatsapp inbound dispatch", () => {
603653
const deliver = getCapturedDeliver();
604654
await deliver?.({ text: "cancelled by hook" }, { kind: "final" });
605655

606-
expect(deliverInboundReplyWithMessageSendContextMock).toHaveBeenCalledWith(
607-
expect.objectContaining({
608-
channel: "whatsapp",
609-
payload: expect.objectContaining({ text: "cancelled by hook" }),
610-
info: { kind: "final" },
611-
}),
656+
const durableParams = requireMockArg(
657+
deliverInboundReplyWithMessageSendContextMock,
658+
0,
659+
0,
660+
"suppressed durable delivery params",
612661
);
662+
expectRecordFields(durableParams, {
663+
channel: "whatsapp",
664+
info: { kind: "final" },
665+
});
666+
expectRecordFields(requireRecord(durableParams.payload, "suppressed payload"), {
667+
text: "cancelled by hook",
668+
});
613669
expect(deliverReply).not.toHaveBeenCalled();
614670
expect(rememberSentText).not.toHaveBeenCalled();
615671
});
@@ -637,21 +693,14 @@ describe("whatsapp inbound dispatch", () => {
637693
);
638694

639695
expect(deliverInboundReplyWithMessageSendContextMock).not.toHaveBeenCalled();
640-
expect(deliverReply).toHaveBeenCalledWith(
641-
expect.objectContaining({
642-
replyResult: expect.objectContaining({
643-
mediaUrls: ["/tmp/generated.jpg"],
644-
text: "generated image",
645-
}),
646-
}),
647-
);
648-
expect(rememberSentText).toHaveBeenCalledWith(
649-
"generated image",
650-
expect.objectContaining({
651-
combinedBody: "hi",
652-
combinedBodySessionKey: "agent:main:whatsapp:direct:+1000",
653-
}),
654-
);
696+
expectReplyResultFields(deliverReply, {
697+
mediaUrls: ["/tmp/generated.jpg"],
698+
text: "generated image",
699+
});
700+
expectRememberSentContextFields(rememberSentText, "generated image", {
701+
combinedBody: "hi",
702+
combinedBodySessionKey: "agent:main:whatsapp:direct:+1000",
703+
});
655704
});
656705

657706
it("normalizes WhatsApp payload text before delivery and echo bookkeeping", async () => {
@@ -673,18 +722,11 @@ describe("whatsapp inbound dispatch", () => {
673722
{ kind: "final" },
674723
);
675724

676-
expect(deliverReply).toHaveBeenCalledWith(
677-
expect.objectContaining({
678-
replyResult: expect.objectContaining({ text: "Before\n\nAfter" }),
679-
}),
680-
);
681-
expect(rememberSentText).toHaveBeenCalledWith(
682-
"Before\n\nAfter",
683-
expect.objectContaining({
684-
combinedBody: "hi",
685-
combinedBodySessionKey: "agent:main:whatsapp:direct:+1000",
686-
}),
687-
);
725+
expectReplyResultFields(deliverReply, { text: "Before\n\nAfter" });
726+
expectRememberSentContextFields(rememberSentText, "Before\n\nAfter", {
727+
combinedBody: "hi",
728+
combinedBodySessionKey: "agent:main:whatsapp:direct:+1000",
729+
});
688730
});
689731

690732
it("suppresses reasoning and compaction payloads before WhatsApp delivery", async () => {
@@ -774,9 +816,7 @@ describe("whatsapp inbound dispatch", () => {
774816
msg: makeMsg({ from: "+15550001000", chatType: "direct" }),
775817
});
776818

777-
expect(getCapturedReplyOptions()).toMatchObject({
778-
disableBlockStreaming: false,
779-
});
819+
expect(getCapturedReplyOptions()?.disableBlockStreaming).toBe(false);
780820
expect(getCapturedReplyOptions()?.sourceReplyDeliveryMode).toBeUndefined();
781821
});
782822

@@ -786,7 +826,7 @@ describe("whatsapp inbound dispatch", () => {
786826
msg: makeMsg({ from: "120363000000000000@g.us", chatType: "group" }),
787827
});
788828

789-
expect(getCapturedReplyOptions()).toMatchObject({
829+
expectRecordFields(requireRecord(getCapturedReplyOptions(), "reply options"), {
790830
sourceReplyDeliveryMode: "message_tool_only",
791831
disableBlockStreaming: true,
792832
});
@@ -802,7 +842,7 @@ describe("whatsapp inbound dispatch", () => {
802842
msg: makeMsg({ from: "120363000000000000@g.us", chatType: "group" }),
803843
});
804844

805-
expect(getCapturedReplyOptions()).toMatchObject({
845+
expectRecordFields(requireRecord(getCapturedReplyOptions(), "reply options"), {
806846
sourceReplyDeliveryMode: "automatic",
807847
disableBlockStreaming: false,
808848
});
@@ -873,13 +913,13 @@ describe("whatsapp inbound dispatch", () => {
873913

874914
expect(deliverReply).toHaveBeenCalledTimes(1);
875915
expect(rememberSentText).not.toHaveBeenCalled();
876-
expect(replyLogger.warn).toHaveBeenCalledWith(
877-
expect.objectContaining({
878-
replyKind: "final",
879-
conversationId: "+1000",
880-
}),
881-
"auto-reply was not accepted by WhatsApp provider",
882-
);
916+
const warnMock = replyLogger.warn as unknown as { mock: { calls: unknown[][] } };
917+
const warningContext = requireMockArg(warnMock, 0, 0, "warning context");
918+
expectRecordFields(warningContext, {
919+
replyKind: "final",
920+
conversationId: "+1000",
921+
});
922+
expect(warnMock.mock.calls[0]?.[1]).toBe("auto-reply was not accepted by WhatsApp provider");
883923
});
884924

885925
it("returns true for tool-only media turns after delivering media", async () => {
@@ -930,15 +970,11 @@ describe("whatsapp inbound dispatch", () => {
930970
).resolves.toBe(true);
931971

932972
expect(deliverReply).toHaveBeenCalledTimes(1);
933-
expect(deliverReply).toHaveBeenCalledWith(
934-
expect.objectContaining({
935-
replyResult: expect.objectContaining({
936-
mediaUrls: ["/tmp/generated.jpg"],
937-
text: undefined,
938-
}),
939-
}),
940-
);
941-
expect(rememberSentText).toHaveBeenCalledWith(undefined, expect.any(Object));
973+
expectReplyResultFields(deliverReply, {
974+
mediaUrls: ["/tmp/generated.jpg"],
975+
text: undefined,
976+
});
977+
expectRememberSentContextFields(rememberSentText, undefined, {});
942978
});
943979

944980
it("passes sendComposing through as the reply typing callback", async () => {

0 commit comments

Comments
 (0)