Files
HealthLog/src/lib/sse/__tests__/create-stream.test.ts
T
Marc-André BombeckandClaude Opus 4.7 2075d392b1 refactor(api): extract createSseStream helper from chat route
The Coach chat route had three identical `new ReadableStream({
start(controller) { try { ... } finally { controller.close(); } } })`
blocks (refusal / provider-error / happy path), differing only in
which frames they enqueued. Extract the boilerplate into
`src/lib/sse/create-stream.ts` so the v1.5 iOS streaming endpoints
inherit the same try/finally close semantics without copy-paste.

The helper accepts sync or async emit callbacks; the controller is
always closed in `finally` so a thrown emit never hangs the response.

Three unit tests pin the sync, async, and throw paths.

Closes Sr-H2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:12:22 +02:00

49 lines
1.7 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { createSseStream } from "../create-stream";
/**
* v1.4.22 W5 reconcile (Sr-H2) — extracted SSE-stream constructor.
*
* Pin the contract: emit can be sync or async, the controller is
* always closed (success or thrown), and the resulting stream reads
* as a real `ReadableStream<Uint8Array>`.
*/
async function readAll(stream: ReadableStream<Uint8Array>): Promise<string> {
const reader = stream.getReader();
const decoder = new TextDecoder();
let out = "";
for (;;) {
const { value, done } = await reader.read();
if (done) break;
out += decoder.decode(value, { stream: true });
}
return out;
}
describe("createSseStream", () => {
it("enqueues frames from a synchronous emit callback and closes", async () => {
const stream = createSseStream((controller) => {
controller.enqueue(new TextEncoder().encode("data: one\n\n"));
controller.enqueue(new TextEncoder().encode("data: two\n\n"));
});
expect(await readAll(stream)).toBe("data: one\n\ndata: two\n\n");
});
it("awaits an async emit callback before closing", async () => {
const stream = createSseStream(async (controller) => {
await Promise.resolve();
controller.enqueue(new TextEncoder().encode("data: async\n\n"));
});
expect(await readAll(stream)).toBe("data: async\n\n");
});
it("closes the stream cleanly even when emit throws", async () => {
const stream = createSseStream(() => {
throw new Error("emit-failure");
});
// Should not hang — the finally block always closes the
// controller. The reader sees an empty body.
expect(await readAll(stream)).toBe("");
});
});