halkyon/packages/halkyon_llm/test/sse_test.dart

86 lines
2.6 KiB
Dart
Raw Normal View History

import 'dart:convert';
import 'package:halkyon_llm/src/sse.dart';
import 'package:test/test.dart';
Future<List<(String, String)>> _events(List<String> chunks) async => [
await for (final event in parseSse(
Stream.fromIterable(chunks.map(utf8.encode)),
))
(event.type, event.data),
];
void main() {
group('parseSse, as the HTML Standard reads an event stream', () {
test('an event, typed, its data after one space', () async {
expect(await _events(['event: ping\ndata: {"a":1}\n\n']), [
('ping', '{"a":1}'),
]);
});
test('untyped events are messages', () async {
expect(await _events(['data: x\n\n']), [('message', 'x')]);
});
test('data lines are joined by line feeds', () async {
expect(await _events(['data: a\ndata: b\ndata:c\n\n']), [
('message', 'a\nb\nc'),
]);
});
test('only one space after the colon is taken off', () async {
expect(await _events(['data: two\n\n']), [('message', ' two')]);
});
test(
'lines end in CR LF, LF or CR, also when a chunk cuts CR LF',
() async {
expect(
await _events(['data: a\r', '\n\r\ndata: b\r\rdata: c\n\n']),
[('message', 'a'), ('message', 'b'), ('message', 'c')],
);
},
);
test('comments, id and retry are skipped', () async {
expect(
await _events([': keep-alive\nid: 7\nretry: 100\ndata: x\n\n']),
[('message', 'x')],
);
});
test('a field without a colon has an empty value', () async {
expect(await _events(['data\n\n']), [('message', '')]);
});
test('an event without data is not dispatched', () async {
expect(await _events(['event: ping\n\ndata: x\n\n']), [('message', 'x')]);
});
test('an event the stream ends in the middle of is dropped', () async {
expect(await _events(['data: a\n\ndata: b\n']), [('message', 'a')]);
});
test('a byte order mark is skipped', () async {
expect(await _events(['\uFEFFdata: x\n\n']), [('message', 'x')]);
});
test('characters cut in two by chunks are put together', () async {
final bytes = utf8.encode('data: 犬 (いぬ)\n\n');
final events = await parseSse(
Stream.fromIterable([
for (final byte in bytes) [byte],
]),
).toList();
expect(events.single.data, '犬 (いぬ)');
});
test('bytes that are not UTF-8 fail as a FormatException', () {
expect(
parseSse(Stream.value([0x64, 0x61, 0x74, 0x61, 0x3A, 0xFF, 0x0A, 0x0A]))
.toList(),
throwsFormatException,
);
});
});
}