@@ -52,6 +52,28 @@ vi.mock("openclaw/plugin-sdk/ssrf-runtime", async () => {
|
52 | 52 | let ensureOggOpus: typeof import("./voice-message.js").ensureOggOpus; |
53 | 53 | let sendDiscordVoiceMessage: typeof import("./voice-message.js").sendDiscordVoiceMessage; |
54 | 54 | |
| 55 | +function cancelTrackedResponse( |
| 56 | +text: string, |
| 57 | +init: ResponseInit, |
| 58 | +): { |
| 59 | +response: Response; |
| 60 | +wasCanceled: () => boolean; |
| 61 | +} { |
| 62 | +let canceled = false; |
| 63 | +const stream = new ReadableStream<Uint8Array>({ |
| 64 | +start(controller) { |
| 65 | +controller.enqueue(new TextEncoder().encode(text)); |
| 66 | +}, |
| 67 | +cancel() { |
| 68 | +canceled = true; |
| 69 | +}, |
| 70 | +}); |
| 71 | +return { |
| 72 | +response: new Response(stream, init), |
| 73 | +wasCanceled: () => canceled, |
| 74 | +}; |
| 75 | +} |
| 76 | + |
55 | 77 | describe("ensureOggOpus", () => { |
56 | 78 | beforeAll(async () => { |
57 | 79 | ({ ensureOggOpus, sendDiscordVoiceMessage } = await import("./voice-message.js")); |
@@ -374,4 +396,57 @@ describe("sendDiscordVoiceMessage", () => {
|
374 | 396 | message: "cdn unavailable", |
375 | 397 | }); |
376 | 398 | }); |
| 399 | + |
| 400 | +it("bounds voice upload error bodies without using response.text()", async () => { |
| 401 | +const rest = createRest(); |
| 402 | +const tracked = cancelTrackedResponse(`${"cdn unavailable ".repeat(1024)}tail`, { |
| 403 | +status: 503, |
| 404 | +headers: { "content-type": "text/plain" }, |
| 405 | +}); |
| 406 | +const textSpy = vi.spyOn(tracked.response, "text").mockRejectedValue(new Error("unbounded")); |
| 407 | +vi.spyOn(globalThis, "fetch").mockImplementation(async (input, init) => { |
| 408 | +const url = input instanceof Request ? input.url : String(input); |
| 409 | +const method = input instanceof Request ? input.method : (init?.method ?? "GET"); |
| 410 | +if (method === "POST" && url.endsWith("/channels/channel-1/attachments")) { |
| 411 | +return new Response( |
| 412 | +JSON.stringify({ |
| 413 | +attachments: [ |
| 414 | +{ |
| 415 | +id: 0, |
| 416 | +upload_url: "https://cdn.test/upload", |
| 417 | +upload_filename: "uploaded.ogg", |
| 418 | +}, |
| 419 | +], |
| 420 | +}), |
| 421 | +{ status: 200 }, |
| 422 | +); |
| 423 | +} |
| 424 | +if (method === "PUT" && url === "https://cdn.test/upload") { |
| 425 | +return tracked.response; |
| 426 | +} |
| 427 | +throw new Error(`unexpected fetch ${method} ${url}`); |
| 428 | +}); |
| 429 | + |
| 430 | +let error: unknown; |
| 431 | +try { |
| 432 | +await sendDiscordVoiceMessage( |
| 433 | +rest, |
| 434 | +"channel-1", |
| 435 | +Buffer.from("ogg"), |
| 436 | +metadata, |
| 437 | +undefined, |
| 438 | +async (fn) => await fn(), |
| 439 | +false, |
| 440 | +"bot-token", |
| 441 | +); |
| 442 | +} catch (caught) { |
| 443 | +error = caught; |
| 444 | +} |
| 445 | +expect(error).toBeInstanceOf(Error); |
| 446 | +expect((error as Error).name).toBe("DiscordError"); |
| 447 | +expect((error as Error).message).toContain("cdn unavailable"); |
| 448 | +expect(JSON.stringify((error as { rawBody?: unknown }).rawBody)).not.toContain("tail"); |
| 449 | +expect(tracked.wasCanceled()).toBe(true); |
| 450 | +expect(textSpy).not.toHaveBeenCalled(); |
| 451 | +}); |
377 | 452 | }); |