diff --git a/extensions/feishu/src/bot.helpers.test.ts b/extensions/feishu/src/bot.helpers.test.ts new file mode 100644 index 00000000000..eabbcf81ada --- /dev/null +++ b/extensions/feishu/src/bot.helpers.test.ts @@ -0,0 +1,94 @@ +import { describe, expect, it } from "vitest"; +import type { ClawdbotConfig } from "../runtime-api.js"; +import { + buildBroadcastSessionKey, + buildFeishuAgentBody, + resolveBroadcastAgents, + toMessageResourceType, +} from "./bot.js"; + +describe("buildFeishuAgentBody", () => { + it("builds message id, speaker, quoted content, mentions, and permission notice in order", () => { + const body = buildFeishuAgentBody({ + ctx: { + content: "hello world", + senderName: "Sender Name", + senderOpenId: "ou-sender", + messageId: "msg-42", + mentionTargets: [{ openId: "ou-target", name: "Target User", key: "@_user_1" }], + }, + quotedContent: "previous message", + permissionErrorForAgent: { + code: 99991672, + message: "permission denied", + grantUrl: "https://open.feishu.cn/app/cli_test", + }, + }); + + expect(body).toBe( + '[message_id: msg-42]\nSender Name: [Replying to: "previous message"]\n\nhello world\n\n[System: Your reply will automatically @mention: Target User. Do not write @xxx yourself.]\n\n[System: The bot encountered a Feishu API permission error. Please inform the user about this issue and provide the permission grant URL for the admin to authorize. Permission grant URL: https://open.feishu.cn/app/cli_test]', + ); + }); +}); + +describe("toMessageResourceType", () => { + it("maps image to image", () => { + expect(toMessageResourceType("image")).toBe("image"); + }); + + it("maps audio to file", () => { + expect(toMessageResourceType("audio")).toBe("file"); + }); + + it("maps video/file/sticker to file", () => { + expect(toMessageResourceType("video")).toBe("file"); + expect(toMessageResourceType("file")).toBe("file"); + expect(toMessageResourceType("sticker")).toBe("file"); + }); +}); + +describe("resolveBroadcastAgents", () => { + it("returns agent list when broadcast config has the peerId", () => { + const cfg = { broadcast: { oc_group123: ["susan", "main"] } } as unknown as ClawdbotConfig; + expect(resolveBroadcastAgents(cfg, "oc_group123")).toEqual(["susan", "main"]); + }); + + it("returns null when no broadcast config", () => { + const cfg = {} as ClawdbotConfig; + expect(resolveBroadcastAgents(cfg, "oc_group123")).toBeNull(); + }); + + it("returns null when peerId not in broadcast", () => { + const cfg = { broadcast: { oc_other: ["susan"] } } as unknown as ClawdbotConfig; + expect(resolveBroadcastAgents(cfg, "oc_group123")).toBeNull(); + }); + + it("returns null when agent list is empty", () => { + const cfg = { broadcast: { oc_group123: [] } } as unknown as ClawdbotConfig; + expect(resolveBroadcastAgents(cfg, "oc_group123")).toBeNull(); + }); +}); + +describe("buildBroadcastSessionKey", () => { + it("replaces agent ID prefix in session key", () => { + expect(buildBroadcastSessionKey("agent:main:feishu:group:oc_group123", "main", "susan")).toBe( + "agent:susan:feishu:group:oc_group123", + ); + }); + + it("handles compound peer IDs", () => { + expect( + buildBroadcastSessionKey( + "agent:main:feishu:group:oc_group123:sender:ou_user1", + "main", + "susan", + ), + ).toBe("agent:susan:feishu:group:oc_group123:sender:ou_user1"); + }); + + it("returns base key unchanged when prefix does not match", () => { + expect(buildBroadcastSessionKey("custom:key:format", "main", "susan")).toBe( + "custom:key:format", + ); + }); +}); diff --git a/extensions/feishu/src/bot.test.ts b/extensions/feishu/src/bot.test.ts index 457e1be3e8d..c12c6993eea 100644 --- a/extensions/feishu/src/bot.test.ts +++ b/extensions/feishu/src/bot.test.ts @@ -110,30 +110,6 @@ async function dispatchMessage(params: { cfg: ClawdbotConfig; event: FeishuMessa return runtime; } -describe("buildFeishuAgentBody", () => { - it("builds message id, speaker, quoted content, mentions, and permission notice in order", () => { - const body = buildFeishuAgentBody({ - ctx: { - content: "hello world", - senderName: "Sender Name", - senderOpenId: "ou-sender", - messageId: "msg-42", - mentionTargets: [{ openId: "ou-target", name: "Target User", key: "@_user_1" }], - }, - quotedContent: "previous message", - permissionErrorForAgent: { - code: 99991672, - message: "permission denied", - grantUrl: "https://open.feishu.cn/app/cli_test", - }, - }); - - expect(body).toBe( - '[message_id: msg-42]\nSender Name: [Replying to: "previous message"]\n\nhello world\n\n[System: Your reply will automatically @mention: Target User. Do not write @xxx yourself.]\n\n[System: The bot encountered a Feishu API permission error. Please inform the user about this issue and provide the permission grant URL for the admin to authorize. Permission grant URL: https://open.feishu.cn/app/cli_test]', - ); - }); -}); - describe("handleFeishuMessage ACP routing", () => { beforeEach(() => { vi.clearAllMocks(); @@ -2344,68 +2320,6 @@ describe("handleFeishuMessage command authorization", () => { }); }); -describe("toMessageResourceType", () => { - it("maps image to image", () => { - expect(toMessageResourceType("image")).toBe("image"); - }); - - it("maps audio to file", () => { - expect(toMessageResourceType("audio")).toBe("file"); - }); - - it("maps video/file/sticker to file", () => { - expect(toMessageResourceType("video")).toBe("file"); - expect(toMessageResourceType("file")).toBe("file"); - expect(toMessageResourceType("sticker")).toBe("file"); - }); -}); - -describe("resolveBroadcastAgents", () => { - it("returns agent list when broadcast config has the peerId", () => { - const cfg = { broadcast: { oc_group123: ["susan", "main"] } } as unknown as ClawdbotConfig; - expect(resolveBroadcastAgents(cfg, "oc_group123")).toEqual(["susan", "main"]); - }); - - it("returns null when no broadcast config", () => { - const cfg = {} as ClawdbotConfig; - expect(resolveBroadcastAgents(cfg, "oc_group123")).toBeNull(); - }); - - it("returns null when peerId not in broadcast", () => { - const cfg = { broadcast: { oc_other: ["susan"] } } as unknown as ClawdbotConfig; - expect(resolveBroadcastAgents(cfg, "oc_group123")).toBeNull(); - }); - - it("returns null when agent list is empty", () => { - const cfg = { broadcast: { oc_group123: [] } } as unknown as ClawdbotConfig; - expect(resolveBroadcastAgents(cfg, "oc_group123")).toBeNull(); - }); -}); - -describe("buildBroadcastSessionKey", () => { - it("replaces agent ID prefix in session key", () => { - expect(buildBroadcastSessionKey("agent:main:feishu:group:oc_group123", "main", "susan")).toBe( - "agent:susan:feishu:group:oc_group123", - ); - }); - - it("handles compound peer IDs", () => { - expect( - buildBroadcastSessionKey( - "agent:main:feishu:group:oc_group123:sender:ou_user1", - "main", - "susan", - ), - ).toBe("agent:susan:feishu:group:oc_group123:sender:ou_user1"); - }); - - it("returns base key unchanged when prefix does not match", () => { - expect(buildBroadcastSessionKey("custom:key:format", "main", "susan")).toBe( - "custom:key:format", - ); - }); -}); - describe("broadcast dispatch", () => { const mockFinalizeInboundContext = vi.fn((ctx: unknown) => ctx); const mockDispatchReplyFromConfig = vi