

























11// Qqbot tests cover stt plugin behavior.
22import * as fs from "node:fs";
33import * as path from "node:path";
4-import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest";
54import { withTempDir } from "openclaw/plugin-sdk/test-env";
5+import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6677const ssrfRuntimeMocks = vi.hoisted(() => ({
88fetchWithSsrFGuard: vi.fn(),
@@ -41,6 +41,36 @@ function cancelTrackedResponse(
4141};
4242}
434344+function largeTranscriptionJsonResponse(params: { chunkCount: number; chunkSize: number }): {
45+response: Response;
46+getReadCount: () => number;
47+} {
48+let chunkIndex = 0;
49+const encoder = new TextEncoder();
50+const chunks = [
51+'{"text":"',
52+ ...Array.from({ length: params.chunkCount }, () => "a".repeat(params.chunkSize)),
53+'"}',
54+];
55+const stream = new ReadableStream<Uint8Array>({
56+pull(controller) {
57+if (chunkIndex >= chunks.length) {
58+controller.close();
59+return;
60+}
61+controller.enqueue(encoder.encode(chunks[chunkIndex]));
62+chunkIndex += 1;
63+},
64+});
65+return {
66+response: new Response(stream, {
67+status: 200,
68+headers: { "content-type": "application/json" },
69+}),
70+getReadCount: () => chunkIndex,
71+};
72+}
73+4474function requireFirstSsrfRequest(): {
4575url?: unknown;
4676auditContext?: unknown;
@@ -177,6 +207,44 @@ describe("engine/utils/stt", () => {
177207});
178208});
179209210+it("bounds successful STT JSON responses before parsing", async () => {
211+await withTempDir("openclaw-qqbot-stt-success-limit-", async (tmpDir) => {
212+const audioPath = path.join(tmpDir, "voice.wav");
213+fs.writeFileSync(audioPath, Buffer.from([1, 2, 3, 4]));
214+215+const release = vi.fn(async () => {});
216+const streamed = largeTranscriptionJsonResponse({
217+chunkCount: 18,
218+chunkSize: 1024 * 1024,
219+});
220+ssrfRuntimeMocks.fetchWithSsrFGuard.mockResolvedValueOnce({
221+response: streamed.response,
222+ release,
223+});
224+225+let error: unknown;
226+try {
227+await transcribeAudio(audioPath, {
228+channels: {
229+qqbot: {
230+stt: {
231+baseUrl: "https://api.example.test/v1/",
232+apiKey: "secret",
233+model: "whisper-1",
234+},
235+},
236+},
237+});
238+} catch (caught) {
239+error = caught;
240+}
241+242+expect(String(error)).toContain("qqbot.stt: JSON response exceeds 16777216 bytes");
243+expect(streamed.getReadCount()).toBeLessThan(20);
244+expect(release).toHaveBeenCalledTimes(1);
245+});
246+});
247+180248it("bounds STT error bodies without using response.text()", async () => {
181249await withTempDir("openclaw-qqbot-stt-error-", async (tmpDir) => {
182250const audioPath = path.join(tmpDir, "voice.wav");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。