import { describe, it, expect } from "vitest"; import { maybeRejectEarlyStreamError } from "../../open-sse/utils/streamErrorPeek.js"; const encoder = new TextEncoder(); const sseResp = (lines) => new Response( new ReadableStream({ start(c) { for (const l of lines) c.enqueue(encoder.encode(l)); c.close(); }, }), { status: 200, headers: { "content-type": "text/event-stream" } }, ); describe("maybeRejectEarlyStreamError", () => { it("returns 502 when a pattern matches early stream text", async () => { const res = await maybeRejectEarlyStreamError( sseResp([ '{"type":"start"}\n{"type":"error","error":{"type":"server_error","message":"Network connection lost."}}\n', ]), ["server_error"], ); expect(res.status).toBe(502); const body = await res.json(); expect(body.error.message).toContain("server_error"); }); it("passes the stream through unchanged when nothing matches", async () => { const res = await maybeRejectEarlyStreamError( sseResp([ 'data: {"choices":[{"delta":{"content":"hello"},"finish_reason":null}]}\n\ndata: [DONE]\n\n', ]), ["server_error"], ); expect(res.status).toBe(200); const text = await res.text(); expect(text).toContain("hello"); expect(text).toContain("[DONE]"); }); it("commits on timeout without hanging", async () => { const stalled = new Response(new ReadableStream({ start() {} }), { status: 200, }); const res = await maybeRejectEarlyStreamError(stalled, ["x"], { timeoutMs: 50, }); expect(res.status).toBe(200); await res.body.cancel(); }); it("commits on abort without hanging", async () => { const ctrl = new AbortController(); const stalled = new Response(new ReadableStream({ start() {} }), { status: 200, }); setTimeout(() => ctrl.abort(new Error("gone")), 10); const res = await maybeRejectEarlyStreamError(stalled, ["x"], { signal: ctrl.signal, timeoutMs: 2000, }); expect(res.status).toBe(200); await res.body.cancel(); }); it("commits (passthrough) when patterns are empty", async () => { const res = await maybeRejectEarlyStreamError( sseResp(["data: hi\n\n"]), [], ); expect(res.status).toBe(200); expect(await res.text()).toBe("data: hi\n\n"); }); it("multi-byte UTF-8 split across peek boundary round-trips losslessly", async () => { // "café" split mid-é: the peek consumes bytes up to and including 0xC3 // (the first half of é); the re-emitted stream must contain the RAW bytes // (never a re-encoded decoded string — TextDecoder flush would replace the // lone 0xC3 with U+FFFD and corrupt the output). const bytes = [ 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x22, 0x63, 0x61, 0x66, 0xc3, ]; const rest = new Uint8Array([0xa9, 0x22, 0x0a, 0x0a]); const body = new ReadableStream({ start(c) { c.enqueue(new Uint8Array(bytes)); c.enqueue(rest); c.close(); }, }); const res = await maybeRejectEarlyStreamError( new Response(body, { status: 200 }), ["nomatch"], { maxBytes: 11 }, ); expect(await res.text()).toBe('data: "café"\n\n'); }); });