





















@@ -0,0 +1,128 @@
1+import { beforeEach, describe, expect, it, vi } from "vitest";
2+import {
3+processAttachments,
4+registerAudioConvertAdapter,
5+type AudioConvertAdapter,
6+} from "./inbound-attachments.js";
7+8+const downloadFileMock = vi.hoisted(() => vi.fn());
9+const resolveSTTConfigMock = vi.hoisted(() => vi.fn());
10+const transcribeAudioMock = vi.hoisted(() => vi.fn());
11+12+vi.mock("../utils/file-utils.js", () => ({
13+downloadFile: downloadFileMock,
14+}));
15+16+vi.mock("../utils/platform.js", () => ({
17+getQQBotMediaDir: () => "/tmp/openclaw-qqbot-downloads",
18+}));
19+20+vi.mock("../utils/stt.js", () => ({
21+resolveSTTConfig: resolveSTTConfigMock,
22+transcribeAudio: transcribeAudioMock,
23+}));
24+25+function registerAdapter(overrides: Partial<AudioConvertAdapter> = {}): void {
26+registerAudioConvertAdapter({
27+convertSilkToWav: vi.fn(async () => null),
28+formatDuration: (seconds) => `${seconds}s`,
29+isVoiceAttachment: (att) =>
30+att.content_type === "voice" || att.content_type.startsWith("audio/"),
31+ ...overrides,
32+});
33+}
34+35+describe("engine/gateway/inbound-attachments", () => {
36+beforeEach(() => {
37+vi.clearAllMocks();
38+resolveSTTConfigMock.mockReturnValue(null);
39+transcribeAudioMock.mockResolvedValue(null);
40+registerAdapter();
41+});
42+43+it("returns an empty result when no attachments are present", async () => {
44+await expect(
45+processAttachments(undefined, { accountId: "qq", cfg: {} }),
46+).resolves.toMatchObject({
47+attachmentInfo: "",
48+imageUrls: [],
49+voiceAttachmentPaths: [],
50+attachmentLocalPaths: [],
51+});
52+});
53+54+it("uses remote image URL when image download fails", async () => {
55+downloadFileMock.mockResolvedValue(null);
56+57+const result = await processAttachments(
58+[{ content_type: "image/png", url: "//cdn.example.test/a.png", filename: "a.png" }],
59+{ accountId: "qq", cfg: {} },
60+);
61+62+expect(downloadFileMock).toHaveBeenCalledWith(
63+"https://cdn.example.test/a.png",
64+"/tmp/openclaw-qqbot-downloads",
65+"a.png",
66+);
67+expect(result.imageUrls).toEqual(["https://cdn.example.test/a.png"]);
68+expect(result.imageMediaTypes).toEqual(["image/png"]);
69+expect(result.attachmentLocalPaths).toEqual([null]);
70+});
71+72+it("prefers voice_wav_url for voice downloads and transcribes with configured STT", async () => {
73+downloadFileMock.mockResolvedValue("/tmp/openclaw-qqbot-downloads/voice.wav");
74+resolveSTTConfigMock.mockReturnValue({
75+baseUrl: "https://stt.example.test",
76+apiKey: "key",
77+model: "whisper-1",
78+});
79+transcribeAudioMock.mockResolvedValue("transcribed voice");
80+81+const result = await processAttachments(
82+[
83+{
84+content_type: "voice",
85+url: "https://cdn.example.test/voice.silk",
86+filename: "voice.silk",
87+voice_wav_url: "//cdn.example.test/voice.wav",
88+asr_refer_text: "platform text",
89+},
90+],
91+{ accountId: "qq", cfg: { channels: { qqbot: { stt: {} } } } },
92+);
93+94+expect(downloadFileMock).toHaveBeenCalledWith(
95+"https://cdn.example.test/voice.wav",
96+"/tmp/openclaw-qqbot-downloads",
97+);
98+expect(transcribeAudioMock).toHaveBeenCalledWith("/tmp/openclaw-qqbot-downloads/voice.wav", {
99+channels: { qqbot: { stt: {} } },
100+});
101+expect(result.voiceAttachmentPaths).toEqual(["/tmp/openclaw-qqbot-downloads/voice.wav"]);
102+expect(result.voiceAttachmentUrls).toEqual(["https://cdn.example.test/voice.wav"]);
103+expect(result.voiceAsrReferTexts).toEqual(["platform text"]);
104+expect(result.voiceTranscripts).toEqual(["transcribed voice"]);
105+expect(result.voiceTranscriptSources).toEqual(["stt"]);
106+});
107+108+it("falls back to platform ASR text when voice download fails", async () => {
109+downloadFileMock.mockResolvedValue(null);
110+111+const result = await processAttachments(
112+[
113+{
114+content_type: "voice",
115+url: "https://cdn.example.test/voice.silk",
116+filename: "voice.silk",
117+asr_refer_text: "platform text",
118+},
119+],
120+{ accountId: "qq", cfg: {} },
121+);
122+123+expect(result.voiceAttachmentUrls).toEqual(["https://cdn.example.test/voice.silk"]);
124+expect(result.voiceTranscripts).toEqual(["platform text"]);
125+expect(result.voiceTranscriptSources).toEqual(["asr"]);
126+expect(result.attachmentLocalPaths).toEqual([null]);
127+});
128+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。