diff --git a/extensions/discord/src/monitor/threading.auto-thread.test.ts b/extensions/discord/src/monitor/threading.auto-thread.test.ts index 5c489a03cd1..1c94d4a2bbd 100644 --- a/extensions/discord/src/monitor/threading.auto-thread.test.ts +++ b/extensions/discord/src/monitor/threading.auto-thread.test.ts @@ -48,6 +48,34 @@ async function flushAsyncWork() { await Promise.resolve(); } +function requireRecord(value: unknown, label: string): Record { + expect(value, label).toBeTypeOf("object"); + expect(value, label).not.toBeNull(); + return value as Record; +} + +function callArg(mock: unknown, callIndex: number, argIndex: number, label: string) { + const calls = (mock as { mock?: { calls?: Array> } }).mock?.calls ?? []; + const call = calls.at(callIndex); + expect(call, label).toBeDefined(); + return call?.[argIndex]; +} + +function expectRestBodyField(mock: unknown, field: string, expected: unknown) { + expect(callArg(mock, 0, 0, "rest path")).toBeTypeOf("string"); + const options = requireRecord(callArg(mock, 0, 1, "rest options"), "rest options"); + const body = requireRecord(options.body, "rest body"); + expect(body[field]).toBe(expected); +} + +function expectGeneratedTitleField(field: string, expected: unknown) { + const params = requireRecord( + callArg(generateThreadTitleMock, 0, 0, "thread title params"), + "thread title params", + ); + expect(params[field]).toBe(expected); +} + beforeAll(async () => { ({ maybeCreateDiscordAutoThread } = await import("./threading.js")); }); @@ -108,10 +136,7 @@ describe("maybeCreateDiscordAutoThread autoArchiveDuration", () => { channelConfig: { allowed: true, autoThread: true, autoArchiveDuration: "10080" }, }), ); - expect(postMock).toHaveBeenCalledWith( - expect.any(String), - expect.objectContaining({ body: expect.objectContaining({ auto_archive_duration: 10080 }) }), - ); + expectRestBodyField(postMock, "auto_archive_duration", 10080); }); it("accepts numeric autoArchiveDuration", async () => { @@ -121,19 +146,13 @@ describe("maybeCreateDiscordAutoThread autoArchiveDuration", () => { channelConfig: { allowed: true, autoThread: true, autoArchiveDuration: 4320 }, }), ); - expect(postMock).toHaveBeenCalledWith( - expect.any(String), - expect.objectContaining({ body: expect.objectContaining({ auto_archive_duration: 4320 }) }), - ); + expectRestBodyField(postMock, "auto_archive_duration", 4320); }); it("defaults to 60 when autoArchiveDuration not set", async () => { postMock.mockResolvedValueOnce({ id: "thread1" }); await maybeCreateDiscordAutoThread(createBaseParams()); - expect(postMock).toHaveBeenCalledWith( - expect.any(String), - expect.objectContaining({ body: expect.objectContaining({ auto_archive_duration: 60 }) }), - ); + expectRestBodyField(postMock, "auto_archive_duration", 60); }); }); @@ -156,27 +175,16 @@ describe("maybeCreateDiscordAutoThread autoThreadName", () => { }), ); expect(result).toBe("thread1"); - expect(postMock).toHaveBeenCalledWith( - expect.any(String), - expect.objectContaining({ - body: expect.objectContaining({ name: "Need help with deploy rollout" }), - }), - ); + expectRestBodyField(postMock, "name", "Need help with deploy rollout"); await flushAsyncWork(); - expect(generateThreadTitleMock).toHaveBeenCalledWith( - expect.objectContaining({ - agentId: "main", - messageText: "Need help with deploy rollout", - channelName: "openclaw", - channelDescription: "OpenClaw development coordination and release planning", - }), - ); - expect(patchMock).toHaveBeenCalledWith( - expect.any(String), - expect.objectContaining({ - body: expect.objectContaining({ name: "Deploy rollout summary" }), - }), + expectGeneratedTitleField("agentId", "main"); + expectGeneratedTitleField("messageText", "Need help with deploy rollout"); + expectGeneratedTitleField("channelName", "openclaw"); + expectGeneratedTitleField( + "channelDescription", + "OpenClaw development coordination and release planning", ); + expectRestBodyField(patchMock, "name", "Deploy rollout summary"); }); it("does not block thread creation while title summary is pending", async () => { @@ -231,11 +239,7 @@ describe("maybeCreateDiscordAutoThread autoThreadName", () => { ); await flushAsyncWork(); - expect(generateThreadTitleMock).toHaveBeenCalledWith( - expect.objectContaining({ - modelRef: "openai/gpt-4.1-mini", - }), - ); + expectGeneratedTitleField("modelRef", "openai/gpt-4.1-mini"); }); it("falls back to parent channel override for generated title model", async () => { @@ -264,11 +268,7 @@ describe("maybeCreateDiscordAutoThread autoThreadName", () => { ); await flushAsyncWork(); - expect(generateThreadTitleMock).toHaveBeenCalledWith( - expect.objectContaining({ - modelRef: "openai/gpt-4.1-mini", - }), - ); + expectGeneratedTitleField("modelRef", "openai/gpt-4.1-mini"); }); it("skips summarization when cfg or agentId is missing", async () => { diff --git a/extensions/feishu/src/card-ux-launcher.test.ts b/extensions/feishu/src/card-ux-launcher.test.ts index 42390ddec08..2782766aded 100644 --- a/extensions/feishu/src/card-ux-launcher.test.ts +++ b/extensions/feishu/src/card-ux-launcher.test.ts @@ -80,7 +80,7 @@ describe("feishu quick-action launcher", () => { const sendArgs = sendCardFeishuMock.mock.calls[0]?.[0] as | { accountId?: string; card?: unknown; cfg?: ClawdbotConfig; to?: string } | undefined; - expect(Object.keys(sendArgs ?? {}).sort()).toEqual(["accountId", "card", "cfg", "to"]); + expect(Object.keys(sendArgs ?? {}).toSorted()).toEqual(["accountId", "card", "cfg", "to"]); expect(sendArgs?.cfg).toBe(cfg); expect(sendArgs?.to).toBe("user:u123"); expect(sendArgs?.accountId).toBe("main");