返回 CodeWhale
sse.test.ts
根目录 / extensions / vscode / src / test / sse.test.ts
1 import assert from "node:assert/strict";
2 import { describe, it } from "node:test";
3 import { SseParser, parseFrame, type RuntimeEvent } from "../sse";
4
5 function eventsOf(chunks: string[]): RuntimeEvent[] {
6 const parser = new SseParser();
7 return chunks.flatMap((chunk) => parser.push(chunk));
8 }
9
10 describe("SseParser", () => {
11 it("parses a single frame with the runtime envelope", () => {
12 const events = eventsOf([
13 'data: {"seq":42,"previous_seq":38,"event":"item.delta","thread_id":"t1","turn_id":"turn_1","item_id":"i1","timestamp":"2026-02-11T20:18:49.123Z","payload":{"delta":"partial output","kind":"agent_message"}}\n\n',
14 ]);
15 assert.equal(events.length, 1);
16 assert.equal(events[0].seq, 42);
17 assert.equal(events[0].previousSeq, 38);
18 assert.equal(events[0].event, "item.delta");
19 assert.equal(events[0].threadId, "t1");
20 assert.equal(events[0].turnId, "turn_1");
21 assert.equal(events[0].itemId, "i1");
22 assert.deepEqual(events[0].payload, { delta: "partial output", kind: "agent_message" });
23 });
24
25 it("parses several frames in one chunk", () => {
26 const chunk =
27 'data: {"seq":1,"event":"turn.started"}\n\n' +
28 'data: {"seq":2,"event":"item.started"}\n\n' +
29 'data: {"seq":3,"event":"turn.completed"}\n\n';
30 const events = eventsOf([chunk]);
31 assert.deepEqual(
32 events.map((event) => event.seq),
33 [1, 2, 3],
34 );
35 });
36
37 it("reassembles a frame split across chunks", () => {
38 const events = eventsOf([
39 'data: {"seq":7,',
40 '"event":"item.delta"}',
41 "\n\n",
42 ]);
43 assert.equal(events.length, 1);
44 assert.equal(events[0].seq, 7);
45 assert.equal(events[0].event, "item.delta");
46 });
47
48 it("handles CRLF frame boundaries", () => {
49 const events = eventsOf(['data: {"seq":9,"event":"turn.started"}\r\n\r\n']);
50 assert.equal(events.length, 1);
51 assert.equal(events[0].seq, 9);
52 });
53
54 it("ignores comments and heartbeats", () => {
55 const events = eventsOf([": heartbeat\n\n", ": keep-alive\n\n"]);
56 assert.equal(events.length, 0);
57 });
58
59 it("ignores frames without valid JSON", () => {
60 const events = eventsOf(["data: not-json\n\n"]);
61 assert.equal(events.length, 0);
62 });
63
64 it("joins multi-line data fields", () => {
65 // Two `data:` lines join with \n; the joined payload is valid JSON here.
66 const events = eventsOf(['data: {"seq":1,\ndata: "event":"x"}\n\n']);
67 assert.equal(events.length, 1);
68 assert.equal(events[0].seq, 1);
69 assert.equal(events[0].event, "x");
70 const good = parseFrame('data: {"seq":1,\ndata: "x":1}\n\n');
71 assert.equal(good, undefined); // parses, but has no event name
72 });
73
74 it("drops events without seq or event name", () => {
75 assert.equal(eventsOf(['data: {"event":"no-seq"}\n\n']).length, 0);
76 assert.equal(eventsOf(['data: {"seq":1}\n\n']).length, 0);
77 });
78 });
79
79 lines TYPESCRIPT