/** * TASK-0126 — the frontend SSE parser must round-trip the page-generation * wire frames (tool started/done, terminal done{pageId,created}, cancelled, * error), skip keepalive lines, and survive frames split across chunk * boundaries. `parseSseFrames` is generic (it parses `data:` JSON), so the * generate frames are asserted by casting to `GenerateFrame`. * * @vitest-environment node (the config default is jsdom, not installed — every * web test opts into node explicitly). */ import { describe, it, expect } from "vitest"; import { parseSseFrames, type GenerateFrame } from "@/lib/ai"; const wire = (frames: GenerateFrame[]) => frames.map((f) => `data: ${JSON.stringify(f)}\n\n`).join(""); const parse = (buf: string): GenerateFrame[] => parseSseFrames(buf).frames as GenerateFrame[]; describe("parseSseFrames — generate (TASK-0126) wire contract", () => { it("parses the tool started/done + terminal done{pageId,created} sequence", () => { const frames: GenerateFrame[] = [ { type: "tool", name: "createPage", status: "started" }, { type: "tool", name: "createPage", status: "done", ref: "pg_1" }, { type: "tool", name: "insertBlocks", status: "started" }, { type: "tool", name: "insertBlocks", status: "done" }, { type: "tool", name: "createDatabase", status: "started" }, { type: "tool", name: "createDatabase", status: "done", ref: "db_1" }, { type: "done", pageId: "pg_1", created: { pages: ["pg_1"], blocks: 6, databases: ["db_1"], relations: 0 }, tokensIn: 120, tokensOut: 40 } ]; const out = parse(wire(frames)); expect(out).toEqual(frames); }); it("parses a cancelled terminal frame", () => { const out = parse(`data: ${JSON.stringify({ type: "tool", name: "createPage", status: "started" })}\n\ndata: ${JSON.stringify({ type: "cancelled" })}\n\n`); expect(out).toEqual([ { type: "tool", name: "createPage", status: "started" }, { type: "cancelled" } ]); }); it("parses an error frame with code + retryable", () => { const out = parse(`data: ${JSON.stringify({ type: "error", code: "generation_failed", message: "Step 2 failed.", retryable: true })}\n\n`); expect(out).toEqual([{ type: "error", code: "generation_failed", message: "Step 2 failed.", retryable: true }]); }); it("skips `: keepalive` comment lines between tool frames", () => { const out = parse( ": keepalive\n\ndata: {\"type\":\"tool\",\"name\":\"createPage\",\"status\":\"started\"}\n\n: keepalive\n\ndata: {\"type\":\"tool\",\"name\":\"createPage\",\"status\":\"done\",\"ref\":\"p\"}\n\n" ); expect(out).toEqual([ { type: "tool", name: "createPage", status: "started" }, { type: "tool", name: "createPage", status: "done", ref: "p" } ]); }); it("keeps a partial trailing frame (the done payload split mid-JSON across chunks)", () => { const full = `data: ${JSON.stringify({ type: "done", pageId: "pg_9", created: { pages: ["pg_9"], blocks: 1, databases: [], relations: 0 } })}\n\n`; const splitAt = 30; // mid-JSON const first = parseSseFrames(full.slice(0, splitAt)); expect(first.frames).toEqual([]); const second = parseSseFrames(first.rest + full.slice(splitAt)); expect(second.frames).toEqual([ { type: "done", pageId: "pg_9", created: { pages: ["pg_9"], blocks: 1, databases: [], relations: 0 } } ]); }); });