

























@@ -192,6 +192,67 @@ function createUnboundRouteState(params: {
192192>;
193193}
194194195+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+195256async function createPluginCommand(params: { cfg: OpenClawConfig; name: string }) {
196257return createDiscordNativeCommand({
197258command: {
@@ -269,15 +330,12 @@ async function expectPairCommandReply(params: {
269330);
270331271332expect(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" });
281339expect(params.interaction.reply).not.toHaveBeenCalled();
282340}
283341@@ -421,11 +479,9 @@ describe("Discord native plugin command dispatch", () => {
421479await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
422480423481expect(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+});
429485expect(interaction.reply).not.toHaveBeenCalled();
430486});
431487@@ -448,9 +504,7 @@ describe("Discord native plugin command dispatch", () => {
448504await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
449505450506expect(handler).toHaveBeenCalledTimes(1);
451-expect(interaction.followUp).toHaveBeenCalledWith(
452-expect.objectContaining({ content: "paired:now" }),
453-);
507+expectFollowUpFields(interaction, { content: "paired:now" });
454508expect(interaction.reply).not.toHaveBeenCalled();
455509});
456510@@ -464,11 +518,9 @@ describe("Discord native plugin command dispatch", () => {
464518await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
465519466520expect(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+});
472524expect(interaction.reply).not.toHaveBeenCalled();
473525});
474526@@ -529,12 +581,10 @@ describe("Discord native plugin command dispatch", () => {
529581530582expect(executeSpy).not.toHaveBeenCalled();
531583expect(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+});
538588expect(interaction.reply).not.toHaveBeenCalled();
539589});
540590@@ -589,15 +639,12 @@ describe("Discord native plugin command dispatch", () => {
589639590640await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
591641592-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" });
601648expect(interaction.reply).not.toHaveBeenCalled();
602649});
603650@@ -652,15 +699,12 @@ describe("Discord native plugin command dispatch", () => {
652699653700await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
654701655-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" });
664708expect(interaction.reply).not.toHaveBeenCalled();
665709});
666710@@ -692,11 +736,9 @@ describe("Discord native plugin command dispatch", () => {
692736await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
693737694738expect(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+});
700742expect(interaction.reply).not.toHaveBeenCalled();
701743});
702744@@ -732,9 +774,7 @@ describe("Discord native plugin command dispatch", () => {
732774733775expect(executeSpy).toHaveBeenCalledTimes(1);
734776expect(dispatchSpy).not.toHaveBeenCalled();
735-expect(interaction.followUp).toHaveBeenCalledWith(
736-expect.objectContaining({ content: "direct plugin output" }),
737-);
777+expectFollowUpFields(interaction, { content: "direct plugin output" });
738778expect(interaction.reply).not.toHaveBeenCalled();
739779});
740780@@ -754,12 +794,10 @@ describe("Discord native plugin command dispatch", () => {
754794755795await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
756796757-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+});
763801expect(interaction.reply).not.toHaveBeenCalled();
764802});
765803@@ -779,9 +817,7 @@ describe("Discord native plugin command dispatch", () => {
779817780818await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
781819782-expect(interaction.followUp).not.toHaveBeenCalledWith(
783-expect.objectContaining({ content: "⚠️ Command produced no visible reply." }),
784-);
820+expectNoFollowUpContent(interaction, "⚠️ Command produced no visible reply.");
785821expect(interaction.reply).not.toHaveBeenCalled();
786822});
787823@@ -814,9 +850,7 @@ describe("Discord native plugin command dispatch", () => {
814850await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
815851816852expect(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." });
820854expect(interaction.reply).not.toHaveBeenCalled();
821855});
822856@@ -878,16 +912,14 @@ describe("Discord native plugin command dispatch", () => {
878912879913await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
880914881-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});
892924893925it("preserves fetched thread parent metadata when interaction parentId getter throws", async () => {
@@ -963,14 +995,12 @@ describe("Discord native plugin command dispatch", () => {
963995964996await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
965997966-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});
97510059761006it("routes native slash commands through configured ACP Discord channel bindings", async () => {
@@ -1120,11 +1150,9 @@ describe("Discord native plugin command dispatch", () => {
1120115011211151await (command as { run: (interaction: unknown) => Promise<void> }).run(interaction as unknown);
112211521123-expect(resolveRouteState).toHaveBeenCalledWith(
1124-expect.objectContaining({
1125-enforceConfiguredBindingReadiness: true,
1126-}),
1127-);
1153+expectSingleCallFirstArg(resolveRouteState, {
1154+enforceConfiguredBindingReadiness: true,
1155+});
11281156expect(dispatchSpy).toHaveBeenCalledTimes(1);
11291157});
11301158@@ -1161,10 +1189,12 @@ describe("Discord native plugin command dispatch", () => {
11611189expect(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});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。