Skip to content

Commit 1429f9a

Browse files
committed
test: clear discord message process broad matchers
1 parent b0da65d commit 1429f9a

1 file changed

Lines changed: 136 additions & 83 deletions

File tree

extensions/discord/src/monitor/message-handler.process.test.ts

Lines changed: 136 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -445,25 +445,87 @@ function getReactionEmojis(): string[] {
445445
).map((call) => call[2]);
446446
}
447447

448-
function expectAckReactionRuntimeOptions(params?: {
449-
accountId?: string;
450-
ackReaction?: string;
451-
removeAckAfterReply?: boolean;
452-
}) {
448+
function requireRecord(value: unknown, label: string): Record<string, unknown> {
449+
expect(typeof value).toBe("object");
450+
expect(value).not.toBeNull();
451+
if (typeof value !== "object" || value === null) {
452+
throw new Error(`${label} was not an object`);
453+
}
454+
return value as Record<string, unknown>;
455+
}
456+
457+
function expectRecordFields(record: Record<string, unknown>, fields: Record<string, unknown>) {
458+
for (const [key, value] of Object.entries(fields)) {
459+
expect(record[key]).toEqual(value);
460+
}
461+
}
462+
463+
function expectAckReactionRuntimeOptions(
464+
options: unknown,
465+
params?: {
466+
accountId?: string;
467+
ackReaction?: string;
468+
removeAckAfterReply?: boolean;
469+
},
470+
) {
471+
const optionRecord = requireRecord(options, "reaction runtime options");
472+
requireRecord(optionRecord.rest, "reaction REST client");
473+
if (params?.accountId) {
474+
expect(optionRecord.accountId).toBe(params.accountId);
475+
}
453476
const messages: Record<string, unknown> = {};
454477
if (params?.ackReaction) {
455478
messages.ackReaction = params.ackReaction;
456479
}
457480
if (params?.removeAckAfterReply !== undefined) {
458481
messages.removeAckAfterReply = params.removeAckAfterReply;
459482
}
460-
return expect.objectContaining({
461-
rest: expect.anything(),
462-
...(Object.keys(messages).length > 0
463-
? { cfg: expect.objectContaining({ messages: expect.objectContaining(messages) }) }
464-
: {}),
465-
...(params?.accountId ? { accountId: params.accountId } : {}),
466-
});
483+
if (Object.keys(messages).length > 0) {
484+
const cfg = requireRecord(optionRecord.cfg, "reaction config");
485+
expectRecordFields(requireRecord(cfg.messages, "reaction message config"), messages);
486+
}
487+
}
488+
489+
function requireReactionCall(
490+
mock: typeof sendMocks.reactMessageDiscord | typeof sendMocks.removeReactionDiscord,
491+
index: number,
492+
) {
493+
const call = mock.mock.calls[index] as unknown[] | undefined;
494+
expect(call).toBeDefined();
495+
if (!call) {
496+
throw new Error(`missing reaction call ${index + 1}`);
497+
}
498+
return call;
499+
}
500+
501+
function expectReactionCallAt(
502+
mock: typeof sendMocks.reactMessageDiscord | typeof sendMocks.removeReactionDiscord,
503+
index: number,
504+
emoji: string,
505+
params?: {
506+
accountId?: string;
507+
ackReaction?: string;
508+
removeAckAfterReply?: boolean;
509+
channelId?: string;
510+
messageId?: string;
511+
},
512+
) {
513+
const call = requireReactionCall(mock, index);
514+
expect(call[0]).toBe(params?.channelId ?? "c1");
515+
expect(call[1]).toBe(params?.messageId ?? "m1");
516+
expect(call[2]).toBe(emoji);
517+
expectAckReactionRuntimeOptions(call[3], params);
518+
}
519+
520+
function expectReactionCallsContain(channelId: string, messageId: string, emoji: string) {
521+
const calls = sendMocks.reactMessageDiscord.mock.calls as unknown as Array<
522+
[string, string, string]
523+
>;
524+
const hasCall = calls.some(
525+
([actualChannelId, actualMessageId, actualEmoji]) =>
526+
actualChannelId === channelId && actualMessageId === messageId && actualEmoji === emoji,
527+
);
528+
expect(hasCall).toBe(true);
467529
}
468530

469531
function expectReactAckCallAt(
@@ -477,13 +539,7 @@ function expectReactAckCallAt(
477539
removeAckAfterReply?: boolean;
478540
},
479541
) {
480-
expect(sendMocks.reactMessageDiscord).toHaveBeenNthCalledWith(
481-
index + 1,
482-
params?.channelId ?? "c1",
483-
params?.messageId ?? "m1",
484-
emoji,
485-
expectAckReactionRuntimeOptions(params),
486-
);
542+
expectReactionCallAt(sendMocks.reactMessageDiscord, index, emoji, params);
487543
}
488544

489545
function expectRemoveAckCallAt(
@@ -497,13 +553,7 @@ function expectRemoveAckCallAt(
497553
removeAckAfterReply?: boolean;
498554
},
499555
) {
500-
expect(sendMocks.removeReactionDiscord).toHaveBeenNthCalledWith(
501-
index + 1,
502-
params?.channelId ?? "c1",
503-
params?.messageId ?? "m1",
504-
emoji,
505-
expectAckReactionRuntimeOptions(params),
506-
);
556+
expectReactionCallAt(sendMocks.removeReactionDiscord, index, emoji, params);
507557
}
508558

509559
function createMockDraftStreamForTest() {
@@ -512,13 +562,20 @@ function createMockDraftStreamForTest() {
512562
return draftStream;
513563
}
514564

565+
function expectPreviewEditContent(content: string) {
566+
const call = editMessageDiscord.mock.calls[0] as unknown[] | undefined;
567+
expect(call).toBeDefined();
568+
if (!call) {
569+
throw new Error("missing preview edit call");
570+
}
571+
expect(call[0]).toBe("c1");
572+
expect(call[1]).toBe("preview-1");
573+
expect(call[2]).toEqual({ content });
574+
requireRecord(requireRecord(call[3], "preview edit options").rest, "preview edit REST client");
575+
}
576+
515577
function expectSinglePreviewEdit() {
516-
expect(editMessageDiscord).toHaveBeenCalledWith(
517-
"c1",
518-
"preview-1",
519-
{ content: "Hello\nWorld" },
520-
expect.objectContaining({ rest: expect.anything() }),
521-
);
578+
expectPreviewEditContent("Hello\nWorld");
522579
expect(deliverDiscordReply).not.toHaveBeenCalled();
523580
}
524581

@@ -601,12 +658,13 @@ describe("processDiscordMessage ack reactions", () => {
601658
await runProcessDiscordMessage(ctx);
602659

603660
expect(sendMocks.reactMessageDiscord).toHaveBeenCalled();
604-
expect(sendMocks.reactMessageDiscord.mock.calls[0]?.[3]).toEqual(
605-
expect.objectContaining({ rest: feedbackRest }),
606-
);
607-
expect(deliverDiscordReply).toHaveBeenCalledWith(
608-
expect.objectContaining({ rest: deliveryRest }),
661+
const feedbackOptions = requireRecord(
662+
sendMocks.reactMessageDiscord.mock.calls[0]?.[3],
663+
"feedback reaction options",
609664
);
665+
expect(feedbackOptions.rest).toBe(feedbackRest);
666+
const deliveryParams = requireRecord(deliverDiscordReply.mock.calls[0]?.[0], "delivery params");
667+
expect(deliveryParams.rest).toBe(deliveryRest);
610668
expect(feedbackRest).not.toBe(deliveryRest);
611669
});
612670

@@ -669,12 +727,9 @@ describe("processDiscordMessage ack reactions", () => {
669727
await runProcessDiscordMessage(ctx);
670728
await vi.runAllTimersAsync();
671729

672-
const calls = sendMocks.reactMessageDiscord.mock.calls as unknown as Array<
673-
[string, string, string]
674-
>;
675-
expect(calls).toContainEqual(expect.arrayContaining(["c1", "m1", "📈"]));
676-
expect(calls).toContainEqual(expect.arrayContaining(["c1", "m1", "✉️"]));
677-
expect(calls).toContainEqual(expect.arrayContaining(["c1", "m1", DEFAULT_EMOJIS.done]));
730+
expectReactionCallsContain("c1", "m1", "📈");
731+
expectReactionCallsContain("c1", "m1", "✉️");
732+
expectReactionCallsContain("c1", "m1", DEFAULT_EMOJIS.done);
678733
});
679734

680735
it("resolves tracked reaction to targets like the Discord reaction action", async () => {
@@ -702,16 +757,20 @@ describe("processDiscordMessage ack reactions", () => {
702757
await runProcessDiscordMessage(ctx);
703758
await vi.runAllTimersAsync();
704759

705-
expect(discordTargetMocks.resolveDiscordTargetChannelId).toHaveBeenCalledWith(
706-
"user:u1",
707-
expect.objectContaining({ accountId: "default" }),
760+
const resolveCall = discordTargetMocks.resolveDiscordTargetChannelId.mock.calls[0] as
761+
| unknown[]
762+
| undefined;
763+
expect(resolveCall).toBeDefined();
764+
if (!resolveCall) {
765+
throw new Error("missing Discord target resolve call");
766+
}
767+
expect(resolveCall[0]).toBe("user:u1");
768+
expect(requireRecord(resolveCall[1], "Discord target resolve options").accountId).toBe(
769+
"default",
708770
);
709-
const calls = sendMocks.reactMessageDiscord.mock.calls as unknown as Array<
710-
[string, string, string]
711-
>;
712-
expect(calls).toContainEqual(expect.arrayContaining(["dm-u1", "m1", "📈"]));
713-
expect(calls).toContainEqual(expect.arrayContaining(["dm-u1", "m1", "✉️"]));
714-
expect(calls).toContainEqual(expect.arrayContaining(["dm-u1", "m1", DEFAULT_EMOJIS.done]));
771+
expectReactionCallsContain("dm-u1", "m1", "📈");
772+
expectReactionCallsContain("dm-u1", "m1", "✉️");
773+
expectReactionCallsContain("dm-u1", "m1", DEFAULT_EMOJIS.done);
715774
});
716775

717776
it("shows stall emojis for long no-progress runs", async () => {
@@ -911,7 +970,7 @@ describe("processDiscordMessage session routing", () => {
911970

912971
await runProcessDiscordMessage(ctx);
913972

914-
expect(getLastDispatchCtx()).toMatchObject({
973+
expectRecordFields(requireRecord(getLastDispatchCtx(), "dispatch context"), {
915974
BodyForAgent: "hello from discord voice",
916975
CommandBody: "hello from discord voice",
917976
Transcript: "hello from discord voice",
@@ -939,7 +998,7 @@ describe("processDiscordMessage session routing", () => {
939998
to: "user:U1",
940999
accountId: "default",
9411000
});
942-
expect(getLastDispatchCtx()).toMatchObject({
1001+
expectRecordFields(requireRecord(getLastDispatchCtx(), "dispatch context"), {
9431002
ChatType: "direct",
9441003
From: "discord:U1",
9451004
To: "user:U1",
@@ -978,16 +1037,22 @@ describe("processDiscordMessage session routing", () => {
9781037

9791038
await runProcessDiscordMessage(ctx);
9801039

981-
expect(getLastRouteUpdate()).toMatchObject({
1040+
expectRecordFields(requireRecord(getLastRouteUpdate(), "last route update"), {
9821041
sessionKey: "agent:main:main",
9831042
channel: "discord",
9841043
to: "user:222",
9851044
accountId: "default",
986-
mainDmOwnerPin: {
1045+
});
1046+
expectRecordFields(
1047+
requireRecord(
1048+
requireRecord(getLastRouteUpdate(), "last route update").mainDmOwnerPin,
1049+
"main DM owner pin",
1050+
),
1051+
{
9871052
ownerRecipient: "111",
9881053
senderRecipient: "222",
9891054
},
990-
});
1055+
);
9911056
});
9921057

9931058
it("stores group lastRoute with channel target", async () => {
@@ -1016,7 +1081,7 @@ describe("processDiscordMessage session routing", () => {
10161081

10171082
await runProcessDiscordMessage(ctx);
10181083

1019-
expect(getLastDispatchReplyOptions()).toMatchObject({
1084+
expectRecordFields(requireRecord(getLastDispatchReplyOptions(), "dispatch reply options"), {
10201085
sourceReplyDeliveryMode: "message_tool_only",
10211086
disableBlockStreaming: true,
10221087
});
@@ -1098,7 +1163,7 @@ describe("processDiscordMessage session routing", () => {
10981163

10991164
await runProcessDiscordMessage(ctx);
11001165

1101-
expect(getLastDispatchCtx()).toMatchObject({
1166+
expectRecordFields(requireRecord(getLastDispatchCtx(), "dispatch context"), {
11021167
MessageSid: "orig-123",
11031168
MessageSidFull: "proxy-456",
11041169
});
@@ -1169,7 +1234,7 @@ describe("processDiscordMessage session routing", () => {
11691234

11701235
await runProcessDiscordMessage(ctx);
11711236

1172-
expect(getLastDispatchCtx()).toMatchObject({
1237+
expectRecordFields(requireRecord(getLastDispatchCtx(), "dispatch context"), {
11731238
SessionKey: "agent:main:subagent:child",
11741239
MessageThreadId: "thread-1",
11751240
});
@@ -1202,7 +1267,7 @@ describe("processDiscordMessage session routing", () => {
12021267

12031268
await runProcessDiscordMessage(ctx);
12041269

1205-
expect(getLastDispatchCtx()).toMatchObject({
1270+
expectRecordFields(requireRecord(getLastDispatchCtx(), "dispatch context"), {
12061271
SessionKey: "agent:main:discord:channel:thread-1",
12071272
MessageThreadId: "thread-1",
12081273
ModelParentSessionKey: "agent:main:discord:channel:parent-1",
@@ -1246,7 +1311,7 @@ describe("processDiscordMessage session routing", () => {
12461311
await runProcessDiscordMessage(ctx);
12471312

12481313
expect(rest.get).toHaveBeenCalled();
1249-
expect(getLastDispatchCtx()).toMatchObject({
1314+
expectRecordFields(requireRecord(getLastDispatchCtx(), "dispatch context"), {
12501315
SessionKey: threadSessionKey,
12511316
MessageThreadId: "thread-1",
12521317
});
@@ -1320,12 +1385,7 @@ describe("processDiscordMessage draft streaming", () => {
13201385

13211386
expect(draftStream.update).toHaveBeenCalledWith(expect.stringContaining("Exec"));
13221387
expect(draftStream.update).toHaveBeenCalledWith(expect.stringContaining("exec done"));
1323-
expect(editMessageDiscord).toHaveBeenCalledWith(
1324-
"c1",
1325-
"preview-1",
1326-
{ content: "done" },
1327-
expect.objectContaining({ rest: expect.anything() }),
1328-
);
1388+
expectPreviewEditContent("done");
13291389
expect(deliverDiscordReply).not.toHaveBeenCalled();
13301390
});
13311391

@@ -1365,12 +1425,7 @@ describe("processDiscordMessage draft streaming", () => {
13651425

13661426
await runProcessDiscordMessage(ctx);
13671427

1368-
expect(editMessageDiscord).toHaveBeenCalledWith(
1369-
"c1",
1370-
"preview-1",
1371-
{ content: longReply },
1372-
expect.objectContaining({ rest: expect.anything() }),
1373-
);
1428+
expectPreviewEditContent(longReply);
13741429
expect(deliverDiscordReply).not.toHaveBeenCalled();
13751430
});
13761431

@@ -1535,9 +1590,12 @@ describe("processDiscordMessage draft streaming", () => {
15351590
expect(draftStream.update).toHaveBeenCalledTimes(1);
15361591
expect(draftStream.update).toHaveBeenCalledWith("Shelling");
15371592
expect(draftStream.flush).toHaveBeenCalledTimes(1);
1538-
expect(dispatchInboundMessage.mock.calls[0]?.[0]?.replyOptions).toMatchObject({
1539-
suppressDefaultToolProgressMessages: true,
1540-
});
1593+
expect(
1594+
requireRecord(
1595+
dispatchInboundMessage.mock.calls[0]?.[0]?.replyOptions,
1596+
"dispatch reply options",
1597+
).suppressDefaultToolProgressMessages,
1598+
).toBe(true);
15411599
});
15421600

15431601
it("does not start Discord progress drafts for text-only accepted turns", async () => {
@@ -1588,12 +1646,7 @@ describe("processDiscordMessage draft streaming", () => {
15881646

15891647
expect(draftStream.update).toHaveBeenCalledWith("Shelling\n🛠️ Exec\n• exec done");
15901648
expect(deliverDiscordReply).not.toHaveBeenCalled();
1591-
expect(editMessageDiscord).toHaveBeenCalledWith(
1592-
"c1",
1593-
"preview-1",
1594-
{ content: "done" },
1595-
expect.objectContaining({ rest: expect.anything() }),
1596-
);
1649+
expectPreviewEditContent("done");
15971650
});
15981651

15991652
it("uses raw tool-progress detail in Discord progress drafts", async () => {

0 commit comments

Comments
 (0)