Skip to content

Commit a1fae59

Browse files
committed
test(agent): add sse parser tests
1 parent 0dbeba8 commit a1fae59

1 file changed

Lines changed: 114 additions & 0 deletions

File tree

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Exercises the `parseSSEStream` helper from tools/test/, not a production
2+
// parser. The kit ships no SSE parser today; consumers parse on their side.
3+
// These tests pin down the helper's edge-case behavior so future parser work
4+
// has a baseline to match.
5+
import { parseSSEStream } from '@test/index';
6+
7+
import type { AgentEvent } from '../src';
8+
9+
const encoder = new TextEncoder();
10+
11+
function streamFromChunks(chunks: string[]): ReadableStream<Uint8Array> {
12+
return new ReadableStream<Uint8Array>({
13+
start(controller) {
14+
for (const chunk of chunks) {
15+
controller.enqueue(encoder.encode(chunk));
16+
}
17+
controller.close();
18+
},
19+
});
20+
}
21+
22+
async function collect(stream: ReadableStream<Uint8Array>): Promise<AgentEvent[]> {
23+
const out: AgentEvent[] = [];
24+
for await (const event of parseSSEStream(stream)) {
25+
out.push(event);
26+
}
27+
return out;
28+
}
29+
30+
describe('parseSSEStream', () => {
31+
it('parses a single complete event', async () => {
32+
const events = await collect(streamFromChunks(['data: {"type":"agent_start"}\n\n']));
33+
expect(events).toEqual([{ type: 'agent_start' }]);
34+
});
35+
36+
it('reassembles a payload split across chunks', async () => {
37+
const events = await collect(
38+
streamFromChunks(['data: {"type":"agen', 't_start"}\n', '\n'])
39+
);
40+
expect(events).toEqual([{ type: 'agent_start' }]);
41+
});
42+
43+
it('joins multiple data: lines with newlines into a single payload', async () => {
44+
const events = await collect(
45+
streamFromChunks(['data: {"type":\ndata: "agent_start"}\n\n'])
46+
);
47+
expect(events).toEqual([{ type: 'agent_start' }]);
48+
});
49+
50+
it('ignores comment lines starting with `:`', async () => {
51+
const events = await collect(
52+
streamFromChunks([': keepalive\ndata: {"type":"turn_start"}\n\n'])
53+
);
54+
expect(events).toEqual([{ type: 'turn_start' }]);
55+
});
56+
57+
it('ignores event:, id:, and retry: framing fields', async () => {
58+
const events = await collect(
59+
streamFromChunks([
60+
'event: turn_start\nid: 1\nretry: 1000\ndata: {"type":"turn_start"}\n\n',
61+
])
62+
);
63+
expect(events).toEqual([{ type: 'turn_start' }]);
64+
});
65+
66+
it('skips a [DONE] marker without yielding an event', async () => {
67+
const events = await collect(
68+
streamFromChunks([
69+
'data: {"type":"agent_start"}\n\ndata: [DONE]\n\n',
70+
])
71+
);
72+
expect(events).toEqual([{ type: 'agent_start' }]);
73+
});
74+
75+
it('handles trailing newlines without emitting a spurious event', async () => {
76+
const events = await collect(
77+
streamFromChunks(['data: {"type":"agent_start"}\n\n\n\n'])
78+
);
79+
expect(events).toEqual([{ type: 'agent_start' }]);
80+
});
81+
82+
it('handles CRLF line endings', async () => {
83+
const events = await collect(
84+
streamFromChunks(['data: {"type":"agent_start"}\r\n\r\n'])
85+
);
86+
expect(events).toEqual([{ type: 'agent_start' }]);
87+
});
88+
89+
it('drops a final incomplete event when the stream ends mid-event', async () => {
90+
const events = await collect(
91+
streamFromChunks([
92+
'data: {"type":"agent_start"}\n\n',
93+
'data: {"type":"turn_start"}',
94+
])
95+
);
96+
expect(events).toEqual([{ type: 'agent_start' }]);
97+
});
98+
99+
it('yields multiple complete events in order', async () => {
100+
const events = await collect(
101+
streamFromChunks([
102+
'data: {"type":"agent_start"}\n\ndata: {"type":"turn_start"}\n\n',
103+
])
104+
);
105+
expect(events).toEqual([{ type: 'agent_start' }, { type: 'turn_start' }]);
106+
});
107+
108+
it('honors an optional space after the `data:` field name', async () => {
109+
const events = await collect(
110+
streamFromChunks(['data:{"type":"agent_start"}\n\n'])
111+
);
112+
expect(events).toEqual([{ type: 'agent_start' }]);
113+
});
114+
});

0 commit comments

Comments
 (0)