blob: 8d87877b60bf5d0b98fcad1e8777817be388ea9f [file]
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
@TestOn('vm')
library;
import 'dart:async';
import 'package:async/async.dart';
import 'package:shelf/shelf.dart' as shelf;
import 'package:sse/server/sse_handler.dart';
import 'package:sse/src/server/sse_handler.dart' show closeSink;
import 'package:stream_channel/stream_channel.dart';
import 'package:test/test.dart';
Future<void> sendRequest(
String method,
SseHandler handler, {
Map<String, /* String | List<String> */ Object>? headers,
Object? body,
int? messageId,
}) async {
final streamController = StreamController<List<int>>();
void onHijack(void Function(StreamChannel<List<int>>) callback) {
callback(StreamChannel(streamController.stream, streamController));
}
final request = shelf.Request(
method,
Uri.http('localhost', 'test', {
'sseClientId': '14560f0c-c207-4245-9c1b-abe9faf8a1b4',
if (messageId != null) 'messageId': messageId.toString(),
}),
headers: headers,
body: body,
onHijack: onHijack,
);
try {
await handler.handler(request);
} on shelf.HijackException {
// Swallow the expected HijackException.
}
}
Future<void> createSseConnection(SseHandler handler) async {
await sendRequest('GET', handler, headers: {'accept': 'text/event-stream'});
}
Future<void> sendMessage(
SseHandler handler,
String message,
int messageId,
) async {
await sendRequest(
'POST',
handler,
headers: {'origin': 'http://localhost'},
body: '"$message"',
messageId: messageId,
);
}
void main() {
late SseHandler handler;
group('SSE with server keep-alive', () {
setUp(() async {
handler = SseHandler(
Uri(path: '/test'),
keepAlive: const Duration(seconds: 5),
ignoreDisconnect: const Duration(seconds: 5),
);
});
tearDown(() async {});
test('handles out of order disconnect and reconnect', () async {
var lastMessageId = -1;
expect(handler.numberOfClients, 0);
await createSseConnection(handler);
var connection = await handler.connections.next;
expect(handler.numberOfClients, 1);
var queue = StreamQueue(connection.stream);
await sendMessage(handler, 'baz', ++lastMessageId);
expect(await queue.next, 'baz');
await sendMessage(handler, 'bar', ++lastMessageId);
expect(await queue.next, 'bar');
// Simulate an out of order disconnect and reconnect by sending the create
// connection request before the close event
await createSseConnection(handler);
closeSink(connection);
await sendMessage(handler, 'foo', ++lastMessageId);
expect(await queue.next, 'foo');
await sendMessage(handler, 'dart', ++lastMessageId);
expect(await queue.next, 'dart');
expect(handler.numberOfClients, 1);
});
}, timeout: const Timeout(Duration(seconds: 120)));
}