





















11// Qqbot tests cover stt plugin behavior.
22import * as fs from "node:fs";
3-import * as os from "node:os";
43import * as path from "node:path";
54import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5+import { createTempDirTracker } from "../../../../../test/helpers/temp-dir.js";
6677const ssrfRuntimeMocks = vi.hoisted(() => ({
88fetchWithSsrFGuard: vi.fn(),
@@ -19,6 +19,30 @@ afterAll(() => {
19192020import { resolveSTTConfig, transcribeAudio } from "./stt.js";
212122+const tempDirs = createTempDirTracker();
23+24+function cancelTrackedResponse(
25+text: string,
26+init: ResponseInit,
27+): {
28+response: Response;
29+wasCanceled: () => boolean;
30+} {
31+let canceled = false;
32+const stream = new ReadableStream<Uint8Array>({
33+start(controller) {
34+controller.enqueue(new TextEncoder().encode(text));
35+},
36+cancel() {
37+canceled = true;
38+},
39+});
40+return {
41+response: new Response(stream, init),
42+wasCanceled: () => canceled,
43+};
44+}
45+2246function requireFirstSsrfRequest(): {
2347url?: unknown;
2448auditContext?: unknown;
@@ -47,6 +71,7 @@ describe("engine/utils/stt", () => {
4771});
48724973afterEach(() => {
74+tempDirs.cleanup();
5075ssrfRuntimeMocks.fetchWithSsrFGuard.mockReset();
5176vi.unstubAllGlobals();
5277});
@@ -110,7 +135,7 @@ describe("engine/utils/stt", () => {
110135});
111136112137it("posts audio to OpenAI-compatible transcription endpoint", async () => {
113-const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-qqbot-stt-"));
138+const tmpDir = tempDirs.make("openclaw-qqbot-stt-");
114139const audioPath = path.join(tmpDir, "voice.wav");
115140fs.writeFileSync(audioPath, Buffer.from([1, 2, 3, 4]));
116141@@ -153,4 +178,45 @@ describe("engine/utils/stt", () => {
153178);
154179expect(release).toHaveBeenCalledTimes(1);
155180});
181+182+it("bounds STT error bodies without using response.text()", async () => {
183+const tmpDir = tempDirs.make("openclaw-qqbot-stt-error-");
184+const audioPath = path.join(tmpDir, "voice.wav");
185+fs.writeFileSync(audioPath, Buffer.from([1, 2, 3, 4]));
186+187+const release = vi.fn(async () => {});
188+const tracked = cancelTrackedResponse(`${"stt provider unavailable ".repeat(1024)}tail`, {
189+status: 503,
190+statusText: "Service Unavailable",
191+headers: { "content-type": "text/plain" },
192+});
193+const textSpy = vi.spyOn(tracked.response, "text").mockRejectedValue(new Error("unbounded"));
194+ssrfRuntimeMocks.fetchWithSsrFGuard.mockResolvedValueOnce({
195+response: tracked.response,
196+ release,
197+});
198+199+let error: unknown;
200+try {
201+await transcribeAudio(audioPath, {
202+channels: {
203+qqbot: {
204+stt: {
205+baseUrl: "https://api.example.test/v1/",
206+apiKey: "secret",
207+model: "whisper-1",
208+},
209+},
210+},
211+});
212+} catch (caught) {
213+error = caught;
214+}
215+216+expect(String(error)).toContain("STT failed (HTTP 503): stt provider unavailable");
217+expect(String(error)).not.toContain("tail");
218+expect(tracked.wasCanceled()).toBe(true);
219+expect(textSpy).not.toHaveBeenCalled();
220+expect(release).toHaveBeenCalledTimes(1);
221+});
156222});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。