Files
Agent-System/web/src/api/chatStream.test.ts
T
WeiCHEN 472ec6497f fix(web): plan-card catch, removeTurn on finish, pane ErrorBoundary, CRLF SSE frames
T20-F2/F3/F5/F6: reset stuck executing cards, reclaim AbortControllers via removeTurn, wrap chat/doc panes, and split SSE on CRLF blank lines as well as LF.
2026-09-22 21:24:20 +08:00

67 lines
2.6 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { parseSSEChunk } from './chatStream';
test('parseSSEChunk: parses event and data lines', () => {
const raw =
'event: phase\ndata: {"type":"phase","phase":{"phase_id":"intent_recognizing","message":"正在研判您的意图与项目上下文..."}}\n\n';
const { events, rest } = parseSSEChunk(raw);
assert.equal(rest, '');
assert.equal(events.length, 1);
assert.equal(events[0].type, 'phase');
assert.equal(events[0].phase?.phase_id, 'intent_recognizing');
});
test('parseSSEChunk: keeps incomplete frame in rest', () => {
const { events, rest } = parseSSEChunk('event: delta\ndata: {"type":"delta"');
assert.equal(events.length, 0);
assert.ok(rest.includes('event: delta'));
});
test('parseSSEChunk: flush parses final frame without trailing delimiter', () => {
const raw = 'event: done\ndata: {"type":"done","done":{"session_id":"s1"}}';
const { events, rest } = parseSSEChunk(raw, { flush: true });
assert.equal(rest, '');
assert.equal(events.length, 1);
assert.equal(events[0].type, 'done');
assert.equal(events[0].done?.session_id, 's1');
});
test('parseSSEChunk: ignores heartbeat comments and keeps last id', () => {
const raw = 'id: 1\nevent: status\ndata: {"type":"status"}\n\n: heartbeat 9\n\nid: 2\nevent: delta\ndata: {"type":"delta","delta":"z"}\n\n';
const { events, lastSeq } = parseSSEChunk(raw);
assert.equal(events.length, 2);
assert.equal(lastSeq, 2);
});
test('parseSSEChunk: skips a single malformed frame and keeps parsing the next valid frame', () => {
const raw =
'id: 1\nevent: delta\ndata: {not valid json\n\n' +
'id: 2\nevent: delta\ndata: {"type":"delta","delta":"ok"}\n\n';
const { events, lastSeq } = parseSSEChunk(raw);
assert.equal(events.length, 1);
assert.equal(events[0].delta, 'ok');
assert.equal(lastSeq, 2);
});
test('parseSSEChunk: parses CRLF-delimited frames', () => {
const raw =
'event: phase\r\ndata: {"type":"phase","phase":{"phase_id":"intent_recognizing"}}\r\n\r\n' +
'event: delta\r\ndata: {"type":"delta","delta":"hi"}\r\n\r\n';
const { events, rest } = parseSSEChunk(raw);
assert.equal(rest, '');
assert.equal(events.length, 2);
assert.equal(events[0].type, 'phase');
assert.equal(events[1].delta, 'hi');
});
test('parseSSEChunk: mixed LF and CRLF delimiters', () => {
const raw =
'data: {"type":"status"}\n\n' +
'data: {"type":"delta","delta":"x"}\r\n\r\n';
const { events, rest } = parseSSEChunk(raw);
assert.equal(rest, '');
assert.equal(events.length, 2);
assert.equal(events[1].delta, 'x');
});