diff --git a/extensions/feishu/src/media.test.ts b/extensions/feishu/src/media.test.ts new file mode 100644 index 0000000000..433d193a1f --- /dev/null +++ b/extensions/feishu/src/media.test.ts @@ -0,0 +1,151 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const createFeishuClientMock = vi.hoisted(() => vi.fn()); +const resolveFeishuAccountMock = vi.hoisted(() => vi.fn()); +const normalizeFeishuTargetMock = vi.hoisted(() => vi.fn()); +const resolveReceiveIdTypeMock = vi.hoisted(() => vi.fn()); + +const fileCreateMock = vi.hoisted(() => vi.fn()); +const messageCreateMock = vi.hoisted(() => vi.fn()); +const messageReplyMock = vi.hoisted(() => vi.fn()); + +vi.mock("./client.js", () => ({ + createFeishuClient: createFeishuClientMock, +})); + +vi.mock("./accounts.js", () => ({ + resolveFeishuAccount: resolveFeishuAccountMock, +})); + +vi.mock("./targets.js", () => ({ + normalizeFeishuTarget: normalizeFeishuTargetMock, + resolveReceiveIdType: resolveReceiveIdTypeMock, +})); + +import { sendMediaFeishu } from "./media.js"; + +describe("sendMediaFeishu msg_type routing", () => { + beforeEach(() => { + vi.clearAllMocks(); + + resolveFeishuAccountMock.mockReturnValue({ + configured: true, + accountId: "main", + appId: "app_id", + appSecret: "app_secret", + domain: "feishu", + }); + + normalizeFeishuTargetMock.mockReturnValue("ou_target"); + resolveReceiveIdTypeMock.mockReturnValue("open_id"); + + createFeishuClientMock.mockReturnValue({ + im: { + file: { + create: fileCreateMock, + }, + message: { + create: messageCreateMock, + reply: messageReplyMock, + }, + }, + }); + + fileCreateMock.mockResolvedValue({ + code: 0, + data: { file_key: "file_key_1" }, + }); + + messageCreateMock.mockResolvedValue({ + code: 0, + data: { message_id: "msg_1" }, + }); + + messageReplyMock.mockResolvedValue({ + code: 0, + data: { message_id: "reply_1" }, + }); + }); + + it("uses msg_type=media for mp4", async () => { + await sendMediaFeishu({ + cfg: {} as any, + to: "user:ou_target", + mediaBuffer: Buffer.from("video"), + fileName: "clip.mp4", + }); + + expect(fileCreateMock).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ file_type: "mp4" }), + }), + ); + + expect(messageCreateMock).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ msg_type: "media" }), + }), + ); + }); + + it("uses msg_type=media for opus", async () => { + await sendMediaFeishu({ + cfg: {} as any, + to: "user:ou_target", + mediaBuffer: Buffer.from("audio"), + fileName: "voice.opus", + }); + + expect(fileCreateMock).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ file_type: "opus" }), + }), + ); + + expect(messageCreateMock).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ msg_type: "media" }), + }), + ); + }); + + it("uses msg_type=file for documents", async () => { + await sendMediaFeishu({ + cfg: {} as any, + to: "user:ou_target", + mediaBuffer: Buffer.from("doc"), + fileName: "paper.pdf", + }); + + expect(fileCreateMock).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ file_type: "pdf" }), + }), + ); + + expect(messageCreateMock).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ msg_type: "file" }), + }), + ); + }); + + it("uses msg_type=media when replying with mp4", async () => { + await sendMediaFeishu({ + cfg: {} as any, + to: "user:ou_target", + mediaBuffer: Buffer.from("video"), + fileName: "reply.mp4", + replyToMessageId: "om_parent", + }); + + expect(messageReplyMock).toHaveBeenCalledWith( + expect.objectContaining({ + path: { message_id: "om_parent" }, + data: expect.objectContaining({ msg_type: "media" }), + }), + ); + + expect(messageCreateMock).not.toHaveBeenCalled(); + }); +});