1
11import { describe, it, expect } from "vitest";
12import { parseSseFrames, type GenerateFrame } from "@/lib/ai";
13
14const wire = (frames: GenerateFrame[]) =>
15 frames.map((f) => `data: ${JSON.stringify(f)}\n\n`).join("");
16
17const parse = (buf: string): GenerateFrame[] => parseSseFrames(buf).frames as GenerateFrame[];
18
19describe("parseSseFrames — generate (TASK-0126) wire contract", () => {
20 it("parses the tool started/done + terminal done{pageId,created} sequence", () => {
21 const frames: GenerateFrame[] = [
22 { type: "tool", name: "createPage", status: "started" },
23 { type: "tool", name: "createPage", status: "done", ref: "pg_1" },
24 { type: "tool", name: "insertBlocks", status: "started" },
25 { type: "tool", name: "insertBlocks", status: "done" },
26 { type: "tool", name: "createDatabase", status: "started" },
27 { type: "tool", name: "createDatabase", status: "done", ref: "db_1" },
28 {
29 type: "done",
30 pageId: "pg_1",
31 created: { pages: ["pg_1"], blocks: 6, databases: ["db_1"], relations: 0 },
32 tokensIn: 120,
33 tokensOut: 40
34 }
35 ];
36 const out = parse(wire(frames));
37 expect(out).toEqual(frames);
38 });
39
40 it("parses a cancelled terminal frame", () => {
41 const out = parse(`data: ${JSON.stringify({ type: "tool", name: "createPage", status: "started" })}\n\ndata: ${JSON.stringify({ type: "cancelled" })}\n\n`);
42 expect(out).toEqual([
43 { type: "tool", name: "createPage", status: "started" },
44 { type: "cancelled" }
45 ]);
46 });
47
48 it("parses an error frame with code + retryable", () => {
49 const out = parse(`data: ${JSON.stringify({ type: "error", code: "generation_failed", message: "Step 2 failed.", retryable: true })}\n\n`);
50 expect(out).toEqual([{ type: "error", code: "generation_failed", message: "Step 2 failed.", retryable: true }]);
51 });
52
53 it("skips `: keepalive` comment lines between tool frames", () => {
54 const out = parse(
55 ": keepalive\n\ndata: {\"type\":\"tool\",\"name\":\"createPage\",\"status\":\"started\"}\n\n: keepalive\n\ndata: {\"type\":\"tool\",\"name\":\"createPage\",\"status\":\"done\",\"ref\":\"p\"}\n\n"
56 );
57 expect(out).toEqual([
58 { type: "tool", name: "createPage", status: "started" },
59 { type: "tool", name: "createPage", status: "done", ref: "p" }
60 ]);
61 });
62
63 it("keeps a partial trailing frame (the done payload split mid-JSON across chunks)", () => {
64 const full = `data: ${JSON.stringify({
65 type: "done",
66 pageId: "pg_9",
67 created: { pages: ["pg_9"], blocks: 1, databases: [], relations: 0 }
68 })}\n\n`;
69 const splitAt = 30;
70 const first = parseSseFrames(full.slice(0, splitAt));
71 expect(first.frames).toEqual([]);
72 const second = parseSseFrames(first.rest + full.slice(splitAt));
73 expect(second.frames).toEqual([
74 { type: "done", pageId: "pg_9", created: { pages: ["pg_9"], blocks: 1, databases: [], relations: 0 } }
75 ]);
76 });
77});
78