Skip to content

Commit ab07005

Browse files
committed
test: clear discord native command broad matchers
1 parent db37179 commit ab07005

1 file changed

Lines changed: 123 additions & 93 deletions

File tree

extensions/discord/src/monitor/native-command.plugin-dispatch.test.ts

Lines changed: 123 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,67 @@ function createUnboundRouteState(params: {
192192
>;
193193
}
194194

195+
type MockCalls = {
196+
mock: { calls: unknown[][] };
197+
};
198+
199+
function isRecord(value: unknown): value is Record<string, unknown> {
200+
return typeof value === "object" && value !== null;
201+
}
202+
203+
function requireRecord(value: unknown, label: string): Record<string, unknown> {
204+
expect(isRecord(value), `${label} should be an object`).toBe(true);
205+
if (!isRecord(value)) {
206+
throw new Error(`${label} should be an object`);
207+
}
208+
return value;
209+
}
210+
211+
function expectFields(record: Record<string, unknown>, expected: Record<string, unknown>) {
212+
for (const [key, value] of Object.entries(expected)) {
213+
expect(record[key], key).toEqual(value);
214+
}
215+
}
216+
217+
function expectSingleCallFirstArg(
218+
mock: MockCalls,
219+
expected: Record<string, unknown>,
220+
label = "mock first argument",
221+
): Record<string, unknown> {
222+
expect(mock.mock.calls).toHaveLength(1);
223+
const [firstArg] = mock.mock.calls[0] ?? [];
224+
const record = requireRecord(firstArg, label);
225+
expectFields(record, expected);
226+
return record;
227+
}
228+
229+
function expectPluginCommandExecution(params: {
230+
mock: MockCalls;
231+
commandName: string;
232+
expected: Record<string, unknown>;
233+
}) {
234+
const payload = expectSingleCallFirstArg(params.mock, params.expected, "plugin command payload");
235+
expect(requireRecord(payload.command, "plugin command").name).toBe(params.commandName);
236+
return payload;
237+
}
238+
239+
function expectFollowUpFields(
240+
interaction: MockCommandInteraction,
241+
expected: Record<string, unknown>,
242+
) {
243+
return expectSingleCallFirstArg(
244+
interaction.followUp as unknown as MockCalls,
245+
expected,
246+
"followUp",
247+
);
248+
}
249+
250+
function expectNoFollowUpContent(interaction: MockCommandInteraction, content: string) {
251+
const calls = (interaction.followUp as unknown as MockCalls).mock.calls;
252+
const matched = calls.some(([payload]) => isRecord(payload) && payload.content === content);
253+
expect(matched).toBe(false);
254+
}
255+
195256
async function createPluginCommand(params: { cfg: OpenClawConfig; name: string }) {
196257
return createDiscordNativeCommand({
197258
command: {
@@ -269,15 +330,12 @@ async function expectPairCommandReply(params: {
269330
);
270331

271332
expect(dispatchSpy).not.toHaveBeenCalled();
272-
expect(executeSpy).toHaveBeenCalledWith(
273-
expect.objectContaining({
274-
command: expect.objectContaining({ name: params.expectedRegisteredName ?? "pair" }),
275-
args: "now",
276-
}),
277-
);
278-
expect(params.interaction.followUp).toHaveBeenCalledWith(
279-
expect.objectContaining({ content: "paired:now" }),
280-
);
333+
expectPluginCommandExecution({
334+
mock: executeSpy,
335+
commandName: params.expectedRegisteredName ?? "pair",
336+
expected: { args: "now" },
337+
});
338+
expectFollowUpFields(params.interaction, { content: "paired:now" });
281339
expect(params.interaction.reply).not.toHaveBeenCalled();
282340
}
283341

@@ -421,11 +479,9 @@ describe("Discord native plugin command dispatch", () => {
421479
await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
422480

423481
expect(handler).not.toHaveBeenCalled();
424-
expect(interaction.followUp).toHaveBeenCalledWith(
425-
expect.objectContaining({
426-
content: "⚠️ This command requires gateway scope: operator.pairing.",
427-
}),
428-
);
482+
expectFollowUpFields(interaction, {
483+
content: "⚠️ This command requires gateway scope: operator.pairing.",
484+
});
429485
expect(interaction.reply).not.toHaveBeenCalled();
430486
});
431487

@@ -448,9 +504,7 @@ describe("Discord native plugin command dispatch", () => {
448504
await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
449505

450506
expect(handler).toHaveBeenCalledTimes(1);
451-
expect(interaction.followUp).toHaveBeenCalledWith(
452-
expect.objectContaining({ content: "paired:now" }),
453-
);
507+
expectFollowUpFields(interaction, { content: "paired:now" });
454508
expect(interaction.reply).not.toHaveBeenCalled();
455509
});
456510

@@ -464,11 +518,9 @@ describe("Discord native plugin command dispatch", () => {
464518
await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
465519

466520
expect(handler).not.toHaveBeenCalled();
467-
expect(interaction.followUp).toHaveBeenCalledWith(
468-
expect.objectContaining({
469-
content: "⚠️ This command requires gateway scope: operator.pairing.",
470-
}),
471-
);
521+
expectFollowUpFields(interaction, {
522+
content: "⚠️ This command requires gateway scope: operator.pairing.",
523+
});
472524
expect(interaction.reply).not.toHaveBeenCalled();
473525
});
474526

@@ -529,12 +581,10 @@ describe("Discord native plugin command dispatch", () => {
529581

530582
expect(executeSpy).not.toHaveBeenCalled();
531583
expect(dispatchSpy).not.toHaveBeenCalled();
532-
expect(interaction.followUp).toHaveBeenCalledWith(
533-
expect.objectContaining({
534-
content: "You are not authorized to use this command.",
535-
ephemeral: true,
536-
}),
537-
);
584+
expectFollowUpFields(interaction, {
585+
content: "You are not authorized to use this command.",
586+
ephemeral: true,
587+
});
538588
expect(interaction.reply).not.toHaveBeenCalled();
539589
});
540590

@@ -589,15 +639,12 @@ describe("Discord native plugin command dispatch", () => {
589639

590640
await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
591641

592-
expect(executeSpy).toHaveBeenCalledWith(
593-
expect.objectContaining({
594-
command: expect.objectContaining({ name: "pair" }),
595-
args: "now",
596-
}),
597-
);
598-
expect(interaction.followUp).toHaveBeenCalledWith(
599-
expect.objectContaining({ content: "open:now" }),
600-
);
642+
expectPluginCommandExecution({
643+
mock: executeSpy,
644+
commandName: "pair",
645+
expected: { args: "now" },
646+
});
647+
expectFollowUpFields(interaction, { content: "open:now" });
601648
expect(interaction.reply).not.toHaveBeenCalled();
602649
});
603650

@@ -652,15 +699,12 @@ describe("Discord native plugin command dispatch", () => {
652699

653700
await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
654701

655-
expect(executeSpy).toHaveBeenCalledWith(
656-
expect.objectContaining({
657-
command: expect.objectContaining({ name: "pair" }),
658-
args: "now",
659-
}),
660-
);
661-
expect(interaction.followUp).toHaveBeenCalledWith(
662-
expect.objectContaining({ content: "open:now" }),
663-
);
702+
expectPluginCommandExecution({
703+
mock: executeSpy,
704+
commandName: "pair",
705+
expected: { args: "now" },
706+
});
707+
expectFollowUpFields(interaction, { content: "open:now" });
664708
expect(interaction.reply).not.toHaveBeenCalled();
665709
});
666710

@@ -692,11 +736,9 @@ describe("Discord native plugin command dispatch", () => {
692736
await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
693737

694738
expect(dispatchSpy).not.toHaveBeenCalled();
695-
expect(interaction.followUp).toHaveBeenCalledWith(
696-
expect.objectContaining({
697-
content: "This group DM is not allowed.",
698-
}),
699-
);
739+
expectFollowUpFields(interaction, {
740+
content: "This group DM is not allowed.",
741+
});
700742
expect(interaction.reply).not.toHaveBeenCalled();
701743
});
702744

@@ -732,9 +774,7 @@ describe("Discord native plugin command dispatch", () => {
732774

733775
expect(executeSpy).toHaveBeenCalledTimes(1);
734776
expect(dispatchSpy).not.toHaveBeenCalled();
735-
expect(interaction.followUp).toHaveBeenCalledWith(
736-
expect.objectContaining({ content: "direct plugin output" }),
737-
);
777+
expectFollowUpFields(interaction, { content: "direct plugin output" });
738778
expect(interaction.reply).not.toHaveBeenCalled();
739779
});
740780

@@ -754,12 +794,10 @@ describe("Discord native plugin command dispatch", () => {
754794

755795
await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
756796

757-
expect(interaction.followUp).toHaveBeenCalledWith(
758-
expect.objectContaining({
759-
content: "⚠️ Command produced no visible reply.",
760-
ephemeral: true,
761-
}),
762-
);
797+
expectFollowUpFields(interaction, {
798+
content: "⚠️ Command produced no visible reply.",
799+
ephemeral: true,
800+
});
763801
expect(interaction.reply).not.toHaveBeenCalled();
764802
});
765803

@@ -779,9 +817,7 @@ describe("Discord native plugin command dispatch", () => {
779817

780818
await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
781819

782-
expect(interaction.followUp).not.toHaveBeenCalledWith(
783-
expect.objectContaining({ content: "⚠️ Command produced no visible reply." }),
784-
);
820+
expectNoFollowUpContent(interaction, "⚠️ Command produced no visible reply.");
785821
expect(interaction.reply).not.toHaveBeenCalled();
786822
});
787823

@@ -814,9 +850,7 @@ describe("Discord native plugin command dispatch", () => {
814850
await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
815851

816852
expect(dispatchSpy).not.toHaveBeenCalled();
817-
expect(interaction.followUp).toHaveBeenCalledWith(
818-
expect.objectContaining({ content: "⚠️ Command produced no visible reply." }),
819-
);
853+
expectFollowUpFields(interaction, { content: "⚠️ Command produced no visible reply." });
820854
expect(interaction.reply).not.toHaveBeenCalled();
821855
});
822856

@@ -878,16 +912,14 @@ describe("Discord native plugin command dispatch", () => {
878912

879913
await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
880914

881-
expect(executeSpy).toHaveBeenCalledWith(
882-
expect.objectContaining({
883-
channel: "discord",
884-
from: "discord:channel:thread-123",
885-
to: "slash:owner",
886-
sessionKey: "agent:main:discord:channel:thread-123",
887-
messageThreadId: "thread-123",
888-
threadParentId: "parent-456",
889-
}),
890-
);
915+
expectSingleCallFirstArg(executeSpy, {
916+
channel: "discord",
917+
from: "discord:channel:thread-123",
918+
to: "slash:owner",
919+
sessionKey: "agent:main:discord:channel:thread-123",
920+
messageThreadId: "thread-123",
921+
threadParentId: "parent-456",
922+
});
891923
});
892924

893925
it("preserves fetched thread parent metadata when interaction parentId getter throws", async () => {
@@ -963,14 +995,12 @@ describe("Discord native plugin command dispatch", () => {
963995

964996
await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
965997

966-
expect(executeSpy).toHaveBeenCalledWith(
967-
expect.objectContaining({
968-
channel: "discord",
969-
from: "discord:channel:partial-thread-123",
970-
messageThreadId: "partial-thread-123",
971-
threadParentId: "partial-parent-456",
972-
}),
973-
);
998+
expectSingleCallFirstArg(executeSpy, {
999+
channel: "discord",
1000+
from: "discord:channel:partial-thread-123",
1001+
messageThreadId: "partial-thread-123",
1002+
threadParentId: "partial-parent-456",
1003+
});
9741004
});
9751005

9761006
it("routes native slash commands through configured ACP Discord channel bindings", async () => {
@@ -1120,11 +1150,9 @@ describe("Discord native plugin command dispatch", () => {
11201150

11211151
await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
11221152

1123-
expect(resolveRouteState).toHaveBeenCalledWith(
1124-
expect.objectContaining({
1125-
enforceConfiguredBindingReadiness: true,
1126-
}),
1127-
);
1153+
expectSingleCallFirstArg(resolveRouteState, {
1154+
enforceConfiguredBindingReadiness: true,
1155+
});
11281156
expect(dispatchSpy).toHaveBeenCalledTimes(1);
11291157
});
11301158

@@ -1161,10 +1189,12 @@ describe("Discord native plugin command dispatch", () => {
11611189
expect(dispatchCall.ctx?.CommandTargetSessionKey).toMatch(
11621190
/^agent:codex:acp:binding:discord:default:/,
11631191
);
1164-
expect(interaction.reply).not.toHaveBeenCalledWith(
1165-
expect.objectContaining({
1166-
content: "Configured ACP binding is unavailable right now. Please try again.",
1167-
}),
1192+
const replyCalls = (interaction.reply as unknown as MockCalls).mock.calls;
1193+
const blockedReply = replyCalls.some(
1194+
([payload]) =>
1195+
isRecord(payload) &&
1196+
payload.content === "Configured ACP binding is unavailable right now. Please try again.",
11681197
);
1198+
expect(blockedReply).toBe(false);
11691199
});
11701200
});

0 commit comments

Comments
 (0)