import { describe, expect, test } from "bun:test"; import { type AiMapStreamEvent, createAiMapEventBuffer, parseAiMapEvent, serializeAiMapEvent, } from "./ndjson"; describe("parseAiMapEvent", () => { test("accepts each well-formed event and trims mapping fields", () => { expect(parseAiMapEvent({ type: "meta", prompt: "p" })).toEqual({ type: "meta", prompt: "p", }); expect(parseAiMapEvent({ type: "reasoning", text: "thinking" })).toEqual({ type: "reasoning", text: "thinking", }); expect( parseAiMapEvent({ type: "mapping", id: "r1", group: " Scribe " }), ).toEqual({ type: "mapping", id: "r1", group: "Scribe" }); expect(parseAiMapEvent({ type: "done" })).toEqual({ type: "done" }); }); test("rejects malformed events", () => { expect(parseAiMapEvent(null)).toBeNull(); expect(parseAiMapEvent({ type: "reasoning", text: "" })).toBeNull(); expect( parseAiMapEvent({ type: "mapping", id: "r1", group: " " }), ).toBeNull(); expect(parseAiMapEvent({ type: "mapping", id: "", group: "x" })).toBeNull(); expect(parseAiMapEvent({ type: "unknown" })).toBeNull(); }); test("error defaults its message", () => { expect(parseAiMapEvent({ type: "error" })).toEqual({ type: "error", message: "AI mapping failed", }); }); }); describe("createAiMapEventBuffer", () => { test("emits complete lines and holds a partial line across chunks", () => { const buf = createAiMapEventBuffer(); const events: AiMapStreamEvent[] = []; // A meta line split across two chunks, then a full reasoning line. events.push(...buf.push('{"type":"meta","prompt":"P')); expect(events).toHaveLength(0); // partial — nothing yet events.push( ...buf.push('"}\n{"type":"reasoning","text":"a"}\n{"type":"mapp'), ); expect(events).toEqual([ { type: "meta", prompt: "P" }, { type: "reasoning", text: "a" }, ]); events.length = 0; events.push(...buf.push('ing","id":"r1","group":"G"}\n')); expect(events).toEqual([{ type: "mapping", id: "r1", group: "G" }]); }); test("flush emits a final newline-less line and skips junk", () => { const buf = createAiMapEventBuffer(); expect(buf.push("not json\n")).toEqual([]); // malformed line skipped expect(buf.push('{"type":"done"}')).toEqual([]); // no newline yet expect(buf.flush()).toEqual([{ type: "done" }]); }); test("round-trips serializeAiMapEvent through the buffer", () => { const buf = createAiMapEventBuffer(); const wire = serializeAiMapEvent({ type: "meta", prompt: "hi" }) + serializeAiMapEvent({ type: "mapping", id: "a", group: "B" }) + serializeAiMapEvent({ type: "done" }); expect(buf.push(wire)).toEqual([ { type: "meta", prompt: "hi" }, { type: "mapping", id: "a", group: "B" }, { type: "done" }, ]); }); });