/** * Unit tests for the CommandCode executor early-error peek. * * The upstream emits AI SDK v5 NDJSON over an HTTP 200 stream, so a terminal * `{"type":"error"}` event is invisible to the normal `response.ok` success * check. `peekForUpstreamError` reads the first events before committing the * response: an error event → non-ok Response (fallback can kick in); otherwise * the buffered bytes are re-emitted and streaming proceeds as before. */ import { describe, it, expect } from "vitest"; import { peekForUpstreamError } from "../../open-sse/executors/commandcode.js"; const encoder = new TextEncoder(); function ndjsonResponse(lines) { const body = new ReadableStream({ start(controller) { for (const line of lines) controller.enqueue(encoder.encode(line + "\n")); controller.close(); }, }); return new Response(body, { status: 200, headers: { "content-type": "text/event-stream" }, }); } describe("commandcode executor — early-error peek", () => { it("returns 502 when the first meaningful event is an error", async () => { const res = await peekForUpstreamError( ndjsonResponse([ '{"type":"error","error":{"type":"server_error","message":"Network connection lost."}}', ]), "model", ); expect(res.status).toBe(502); const body = await res.json(); expect(body.error.message).toBe("Network connection lost."); expect(body.error.type).toBe("server_error"); }); it("detects an error event even when metadata events arrive first", async () => { const res = await peekForUpstreamError( ndjsonResponse([ '{"type":"start"}', '{"type":"start-step"}', '{"type":"error","error":{"type":"server_error","message":"Network connection lost."}}', ]), "model", ); expect(res.status).toBe(502); const body = await res.json(); expect(body.error.message).toContain("Network connection lost"); }); it("commits and streams normally when the first meaningful event is content", async () => { const res = await peekForUpstreamError( ndjsonResponse([ '{"type":"start"}', '{"type":"text-delta","text":"hi there"}', '{"type":"finish"}', ]), "model", ); expect(res.status).toBe(200); const text = await res.text(); expect(text).toContain('"content":"hi there"'); expect(text).not.toContain("[CommandCode error:"); }); it("commits when the stream ends without any event", async () => { const res = await peekForUpstreamError(ndjsonResponse([]), "model"); expect(res.status).toBe(200); await res.body.cancel(); }); it("commits (does not hang) when no event arrives before the peek timeout", async () => { const stalled = new Response(new ReadableStream({ start() {} }), { status: 200, headers: { "content-type": "text/event-stream" }, }); const res = await peekForUpstreamError(stalled, "model", { timeoutMs: 50 }); expect(res.status).toBe(200); await res.body.cancel(); }); it("does not hang when the request signal aborts during the peek", async () => { const controller = new AbortController(); const stalled = new Response(new ReadableStream({ start() {} }), { status: 200, headers: { "content-type": "text/event-stream" }, }); setTimeout(() => controller.abort(new Error("client gone")), 10); const res = await peekForUpstreamError(stalled, "model", { signal: controller.signal, timeoutMs: 2000, }); expect(res.status).toBe(200); await res.body.cancel(); }); it("re-emits raw bytes so a multi-byte char split across the peek boundary survives", async () => { // chunk1 ends mid-é (0xC3); the peek commits on the first complete line // (text-delta "hi") while the decoder still holds 0xC3. Re-emission must // use RAW bytes — re-encoding decoded text would replace 0xC3 with U+FFFD. const first = Buffer.from( '{"type":"text-delta","text":"hi"}\n{"type":"text-delta","text":"caf', ); const chunk1 = new Uint8Array([...first, 0xc3]); const rest = new Uint8Array([0xa9, 0x22, 0x7d, 0x0a]); // é"}\n const body = new ReadableStream({ start(c) { c.enqueue(chunk1); c.enqueue(rest); c.close(); }, }); const res = await peekForUpstreamError( new Response(body, { status: 200 }), "m", ); const text = await res.text(); expect(text).toContain("café"); expect(text).not.toContain("\uFFFD"); }); });