diff --git a/extensions/feishu/src/bot.card-action.test.ts b/extensions/feishu/src/bot.card-action.test.ts index 2df1ce361a1..2d2e7ac235d 100644 --- a/extensions/feishu/src/bot.card-action.test.ts +++ b/extensions/feishu/src/bot.card-action.test.ts @@ -35,6 +35,54 @@ describe("Feishu Card Action Handler", () => { const cfg = {} as any; // Minimal mock const runtime = { log: vi.fn(), error: vi.fn() } as any; + function createCardActionEvent(params: { + token: string; + actionValue: unknown; + chatId?: string; + openId?: string; + userId?: string; + unionId?: string; + }): FeishuCardActionEvent { + const openId = params.openId ?? "u123"; + const userId = params.userId ?? "uid1"; + return { + operator: { open_id: openId, user_id: userId, union_id: params.unionId ?? "un1" }, + token: params.token, + action: { + value: params.actionValue, + tag: "button", + }, + context: { open_id: openId, user_id: userId, chat_id: params.chatId ?? "chat1" }, + }; + } + + function createStructuredQuickActionEvent(params: { + token: string; + action: string; + command?: string; + chatId?: string; + chatType?: "group" | "p2p"; + operatorOpenId?: string; + actionOpenId?: string; + }): FeishuCardActionEvent { + return createCardActionEvent({ + token: params.token, + chatId: params.chatId, + openId: params.operatorOpenId, + actionValue: createFeishuCardInteractionEnvelope({ + k: "quick", + a: params.action, + ...(params.command ? { q: params.command } : {}), + c: { + u: params.actionOpenId ?? params.operatorOpenId ?? "u123", + h: params.chatId ?? "chat1", + t: params.chatType ?? "group", + e: Date.now() + 60_000, + }, + }), + }); + } + beforeEach(() => { vi.clearAllMocks(); resetProcessedFeishuCardActionTokensForTests(); @@ -85,20 +133,11 @@ describe("Feishu Card Action Handler", () => { }); it("routes quick command actions with operator and conversation context", async () => { - const event: FeishuCardActionEvent = { - operator: { open_id: "u123", user_id: "uid1", union_id: "un1" }, + const event = createStructuredQuickActionEvent({ token: "tok3", - action: { - value: createFeishuCardInteractionEnvelope({ - k: "quick", - a: "feishu.quick_actions.help", - q: "/help", - c: { u: "u123", h: "chat1", t: "group", e: Date.now() + 60_000 }, - }), - tag: "button", - }, - context: { open_id: "u123", user_id: "uid1", chat_id: "chat1" }, - }; + action: "feishu.quick_actions.help", + command: "/help", + }); await handleFeishuCardAction({ cfg, event, runtime }); @@ -182,20 +221,11 @@ describe("Feishu Card Action Handler", () => { }); it("runs approval confirmation through the normal message path", async () => { - const event: FeishuCardActionEvent = { - operator: { open_id: "u123", user_id: "uid1", union_id: "un1" }, + const event = createStructuredQuickActionEvent({ token: "tok5", - action: { - value: createFeishuCardInteractionEnvelope({ - k: "quick", - a: FEISHU_APPROVAL_CONFIRM_ACTION, - q: "/new", - c: { u: "u123", h: "chat1", t: "group", e: Date.now() + 60_000 }, - }), - tag: "button", - }, - context: { open_id: "u123", user_id: "uid1", chat_id: "chat1" }, - }; + action: FEISHU_APPROVAL_CONFIRM_ACTION, + command: "/new", + }); await handleFeishuCardAction({ cfg, event, runtime }); @@ -211,20 +241,15 @@ describe("Feishu Card Action Handler", () => { }); it("safely rejects stale structured actions", async () => { - const event: FeishuCardActionEvent = { - operator: { open_id: "u123", user_id: "uid1", union_id: "un1" }, + const event = createCardActionEvent({ token: "tok6", - action: { - value: createFeishuCardInteractionEnvelope({ - k: "quick", - a: "feishu.quick_actions.help", - q: "/help", - c: { u: "u123", h: "chat1", t: "group", e: Date.now() - 1 }, - }), - tag: "button", - }, - context: { open_id: "u123", user_id: "uid1", chat_id: "chat1" }, - }; + actionValue: createFeishuCardInteractionEnvelope({ + k: "quick", + a: "feishu.quick_actions.help", + q: "/help", + c: { u: "u123", h: "chat1", t: "group", e: Date.now() - 1 }, + }), + }); await handleFeishuCardAction({ cfg, event, runtime }); @@ -238,20 +263,13 @@ describe("Feishu Card Action Handler", () => { }); it("safely rejects wrong-user structured actions", async () => { - const event: FeishuCardActionEvent = { - operator: { open_id: "u999", user_id: "uid1", union_id: "un1" }, + const event = createStructuredQuickActionEvent({ token: "tok7", - action: { - value: createFeishuCardInteractionEnvelope({ - k: "quick", - a: "feishu.quick_actions.help", - q: "/help", - c: { u: "u123", h: "chat1", t: "group", e: Date.now() + 60_000 }, - }), - tag: "button", - }, - context: { open_id: "u999", user_id: "uid1", chat_id: "chat1" }, - }; + action: "feishu.quick_actions.help", + command: "/help", + operatorOpenId: "u999", + actionOpenId: "u123", + }); await handleFeishuCardAction({ cfg, event, runtime }); @@ -289,20 +307,13 @@ describe("Feishu Card Action Handler", () => { }); it("preserves p2p callbacks for DM quick actions", async () => { - const event: FeishuCardActionEvent = { - operator: { open_id: "u123", user_id: "uid1", union_id: "un1" }, + const event = createStructuredQuickActionEvent({ token: "tok9", - action: { - value: createFeishuCardInteractionEnvelope({ - k: "quick", - a: "feishu.quick_actions.help", - q: "/help", - c: { u: "u123", h: "p2p-chat-1", t: "p2p", e: Date.now() + 60_000 }, - }), - tag: "button", - }, - context: { open_id: "u123", user_id: "uid1", chat_id: "p2p-chat-1" }, - }; + action: "feishu.quick_actions.help", + command: "/help", + chatId: "p2p-chat-1", + chatType: "p2p", + }); await handleFeishuCardAction({ cfg, event, runtime }); @@ -319,20 +330,11 @@ describe("Feishu Card Action Handler", () => { }); it("drops duplicate structured callback tokens", async () => { - const event: FeishuCardActionEvent = { - operator: { open_id: "u123", user_id: "uid1", union_id: "un1" }, + const event = createStructuredQuickActionEvent({ token: "tok10", - action: { - value: createFeishuCardInteractionEnvelope({ - k: "quick", - a: "feishu.quick_actions.help", - q: "/help", - c: { u: "u123", h: "chat1", t: "group", e: Date.now() + 60_000 }, - }), - tag: "button", - }, - context: { open_id: "u123", user_id: "uid1", chat_id: "chat1" }, - }; + action: "feishu.quick_actions.help", + command: "/help", + }); await handleFeishuCardAction({ cfg, event, runtime }); await handleFeishuCardAction({ cfg, event, runtime }); @@ -341,20 +343,11 @@ describe("Feishu Card Action Handler", () => { }); it("releases a claimed token when dispatch fails so retries can succeed", async () => { - const event: FeishuCardActionEvent = { - operator: { open_id: "u123", user_id: "uid1", union_id: "un1" }, + const event = createStructuredQuickActionEvent({ token: "tok11", - action: { - value: createFeishuCardInteractionEnvelope({ - k: "quick", - a: "feishu.quick_actions.help", - q: "/help", - c: { u: "u123", h: "chat1", t: "group", e: Date.now() + 60_000 }, - }), - tag: "button", - }, - context: { open_id: "u123", user_id: "uid1", chat_id: "chat1" }, - }; + action: "feishu.quick_actions.help", + command: "/help", + }); vi.mocked(handleFeishuMessage) .mockRejectedValueOnce(new Error("transient")) .mockResolvedValueOnce(undefined as never);