import { test, expect } from "vitest"; import { buildApp } from "../src/app.js"; import { loadConfig } from "../src/config.js"; import { SseHub } from "../src/sse/sse-hub.js"; async function readUntil( reader: ReadableStreamDefaultReader, predicate: (text: string) => boolean, ): Promise { const decoder = new TextDecoder(); let text = ""; const deadline = Date.now() + 1_000; while (Date.now() < deadline) { const result = await Promise.race([ reader.read(), new Promise<{ timeout: true }>((resolve) => setTimeout(() => resolve({ timeout: true }), Math.max(0, deadline - Date.now())), ), ]); if ("timeout" in result || result.done) break; text += decoder.decode(result.value); if (predicate(text)) break; } return text; } async function captureReplay(options: { query?: string; lastEventId?: string }): Promise { const hub = new SseHub(); hub.publish("s1", "info", { type: "info", text: "one" }); hub.publish("s1", "info", { type: "info", text: "two" }); hub.publish("s1", "info", { type: "info", text: "three" }); const app = buildApp(loadConfig({ THT_HARNESS_DIR: "/tmp/h" }), { hub, thtRunner: {} as any, }); await app.listen({ port: 0, host: "127.0.0.1" }); const port = (app.server.address() as { port: number }).port; const controller = new AbortController(); try { const response = await fetch( `http://127.0.0.1:${port}/sessions/s1/events${options.query ?? ""}`, { headers: options.lastEventId ? { "Last-Event-ID": options.lastEventId } : undefined, signal: controller.signal, }, ); const reader = response.body!.getReader(); const body = await readUntil(reader, (text) => text.includes('"three"')); await reader.cancel(); return body; } finally { controller.abort(); await app.close(); } } test("SSE emits ids and honors the native Last-Event-ID replay cursor", async () => { const body = await captureReplay({ lastEventId: "1" }); expect(body).not.toContain('"one"'); expect(body).toContain("id: 2\nevent: info\n"); expect(body).toContain('data: {"type":"info","text":"two"}'); expect(body).toContain("id: 3\nevent: info\n"); expect(body).toContain('data: {"type":"info","text":"three"}'); }); test("SSE honors the manual lastEventId query cursor", async () => { const body = await captureReplay({ query: "?lastEventId=2" }); expect(body).not.toContain('"one"'); expect(body).not.toContain('"two"'); expect(body).toContain("id: 3\nevent: info\n"); expect(body).toContain('data: {"type":"info","text":"three"}'); }); test("SSE uses the newer valid cursor when header and query are both present", async () => { const body = await captureReplay({ query: "?lastEventId=2", lastEventId: "1" }); expect(body).not.toContain('"two"'); expect(body).toContain("id: 3\nevent: info\n"); });