Skip to content

Commit 8beb048

Browse files
Yaxuan42Takhoffman
andauthored
test(feishu): add regression for audio download resource type=file (#16311) thanks @Yaxuan42
Verified: - pnpm build - pnpm check - pnpm vitest run --config vitest.extensions.config.ts extensions/feishu/src/bot.test.ts extensions/feishu/src/media.test.ts Co-authored-by: Yaxuan42 <[email protected]> Co-authored-by: Tak Hoffman <[email protected]>
1 parent b28344e commit 8beb048

File tree

4 files changed

+86
-2
lines changed

4 files changed

+86
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Docs: https://docs.openclaw.ai
3131
- Feishu/Local media sends: propagate `mediaLocalRoots` through Feishu outbound media sending into `loadWebMedia` so local path attachments work with post-CVE local-root enforcement. (#27884) Thanks @joelnishanth.
3232
- Feishu/Group sender allowlist fallback: add global `channels.feishu.groupSenderAllowFrom` sender authorization for group chats, with per-group `groups.<id>.allowFrom` precedence and regression coverage for allow/block/precedence behavior. (#29174) Thanks @1MoreBuild.
3333
- Feishu/Docx append/write ordering: insert converted Docx blocks sequentially (single-block creates) so Feishu append/write preserves markdown block order instead of returning shuffled sections in asynchronous batch inserts. (#26172, #26022) Thanks @echoVic.
34+
- Feishu/Inbound media regression coverage: add explicit tests for message resource type mapping (`image` stays `image`, non-image maps to `file`) to prevent reintroducing unsupported Feishu `type=audio` fetches. (#16311, #8746) Thanks @Yaxuan42.
3435
- Security/Feishu webhook ingress: bound unauthenticated webhook rate-limit state with stale-window pruning and a hard key cap to prevent unbounded pre-auth memory growth from rotating source keys. (#26050) Thanks @bmendonca3.
3536
- Security/Compaction audit: remove the post-compaction audit injection message. (#28507) Thanks @fuller-stack-dev and @vincentkoc.
3637
- Telegram/Reply media context: include replied media files in inbound context when replying to media, defer reply-media downloads to debounce flush, gate reply-media fetch behind DM authorization, and preserve replied media when non-vision sticker fallback runs (including cached-sticker paths). (#28488) Thanks @obviyus.

extensions/feishu/src/bot.test.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { ClawdbotConfig, PluginRuntime, RuntimeEnv } from "openclaw/plugin-sdk";
22
import { beforeEach, describe, expect, it, vi } from "vitest";
33
import type { FeishuMessageEvent } from "./bot.js";
4-
import { buildFeishuAgentBody, handleFeishuMessage } from "./bot.js";
4+
import { buildFeishuAgentBody, handleFeishuMessage, toMessageResourceType } from "./bot.js";
55
import { setFeishuRuntime } from "./runtime.js";
66

77
const {
@@ -993,3 +993,19 @@ describe("handleFeishuMessage command authorization", () => {
993993
);
994994
});
995995
});
996+
997+
describe("toMessageResourceType", () => {
998+
it("maps image to image", () => {
999+
expect(toMessageResourceType("image")).toBe("image");
1000+
});
1001+
1002+
it("maps audio to file", () => {
1003+
expect(toMessageResourceType("audio")).toBe("file");
1004+
});
1005+
1006+
it("maps video/file/sticker to file", () => {
1007+
expect(toMessageResourceType("video")).toBe("file");
1008+
expect(toMessageResourceType("file")).toBe("file");
1009+
expect(toMessageResourceType("sticker")).toBe("file");
1010+
});
1011+
});

extensions/feishu/src/bot.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,14 @@ function parsePostContent(content: string): {
460460
}
461461
}
462462

463+
/**
464+
* Map Feishu message type to messageResource.get resource type.
465+
* Feishu messageResource API supports only: image | file.
466+
*/
467+
export function toMessageResourceType(messageType: string): "image" | "file" {
468+
return messageType === "image" ? "image" : "file";
469+
}
470+
463471
/**
464472
* Infer placeholder text based on message type.
465473
*/
@@ -570,7 +578,7 @@ async function resolveFeishuMediaList(params: {
570578
return [];
571579
}
572580

573-
const resourceType = messageType === "image" ? "image" : "file";
581+
const resourceType = toMessageResourceType(messageType);
574582
const result = await downloadMessageResourceFeishu({
575583
cfg,
576584
messageId,

extensions/feishu/src/media.test.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,3 +335,62 @@ describe("sendMediaFeishu msg_type routing", () => {
335335
expect(messageResourceGetMock).not.toHaveBeenCalled();
336336
});
337337
});
338+
339+
describe("downloadMessageResourceFeishu", () => {
340+
beforeEach(() => {
341+
vi.clearAllMocks();
342+
343+
resolveFeishuAccountMock.mockReturnValue({
344+
configured: true,
345+
accountId: "main",
346+
config: {},
347+
appId: "app_id",
348+
appSecret: "app_secret",
349+
domain: "feishu",
350+
});
351+
352+
createFeishuClientMock.mockReturnValue({
353+
im: {
354+
messageResource: {
355+
get: messageResourceGetMock,
356+
},
357+
},
358+
});
359+
360+
messageResourceGetMock.mockResolvedValue(Buffer.from("fake-audio-data"));
361+
});
362+
363+
// Regression: Feishu API only supports type=image|file for messageResource.get.
364+
// Audio/video resources must use type=file, not type=audio (#8746).
365+
it("forwards provided type=file for non-image resources", async () => {
366+
const result = await downloadMessageResourceFeishu({
367+
cfg: {} as any,
368+
messageId: "om_audio_msg",
369+
fileKey: "file_key_audio",
370+
type: "file",
371+
});
372+
373+
expect(messageResourceGetMock).toHaveBeenCalledWith({
374+
path: { message_id: "om_audio_msg", file_key: "file_key_audio" },
375+
params: { type: "file" },
376+
});
377+
expect(result.buffer).toBeInstanceOf(Buffer);
378+
});
379+
380+
it("image uses type=image", async () => {
381+
messageResourceGetMock.mockResolvedValue(Buffer.from("fake-image-data"));
382+
383+
const result = await downloadMessageResourceFeishu({
384+
cfg: {} as any,
385+
messageId: "om_img_msg",
386+
fileKey: "img_key_1",
387+
type: "image",
388+
});
389+
390+
expect(messageResourceGetMock).toHaveBeenCalledWith({
391+
path: { message_id: "om_img_msg", file_key: "img_key_1" },
392+
params: { type: "image" },
393+
});
394+
expect(result.buffer).toBeInstanceOf(Buffer);
395+
});
396+
});

0 commit comments

Comments
 (0)