Server on an isolate, dedicated server binary, room -> server (proto 5)
- serve(Map config, events) is the one server entrypoint: the app hosts it via Isolate.spawn (crash surfaced on the Host card, logs forwarded as [server]); bin/server.dart is the dedicated server, compiled into the bundle as watch-together-server. --headless gone. - Host identity via a per-hosting hostKey in hello, replacing the loopback heuristic. - Rename room -> server across code, files and wire (t:"state", /health name); protocol bumped to 5. - CI compiles the server binary; isolate smoke tests in server and desktop suites. Docs updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d8a0729029
commit
81cac1214d
48 files changed
+1073
-423
No files matched your search
@@ -0,0 +1,70 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:sync_relay_core/core.dart';
|
||||
import 'package:sync_relay_server/server.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
Future<void> _run((Map<String, Object?>, SendPort) args) =>
|
||||
serve(args.$1, events: args.$2);
|
||||
|
||||
void main() {
|
||||
test('serve in a real isolate: ready, /health, stop, clean exit', () async {
|
||||
final events = ReceivePort();
|
||||
final exit = ReceivePort();
|
||||
final errors = ReceivePort();
|
||||
final config = const AppConfig(port: 0, secret: '', name: 'iso', hostKey: 'hk').toMap();
|
||||
await Isolate.spawn(_run, (config, events.sendPort),
|
||||
onExit: exit.sendPort, onError: errors.sendPort);
|
||||
final crashes = <Object?>[];
|
||||
errors.listen(crashes.add);
|
||||
final logs = <Map>[];
|
||||
final ready = Completer<Map>();
|
||||
events.listen((m) {
|
||||
if (m is! Map) return;
|
||||
if (m['t'] == 'log') logs.add(m);
|
||||
if (m['t'] == 'ready' && !ready.isCompleted) ready.complete(m);
|
||||
if (m['t'] == 'error' && !ready.isCompleted) ready.completeError('${m['message']}');
|
||||
});
|
||||
final r = await ready.future.timeout(const Duration(seconds: 10));
|
||||
final port = r['port'] as int;
|
||||
expect(port, greaterThan(0));
|
||||
|
||||
final http = HttpClient();
|
||||
final resp = await (await http.get('127.0.0.1', port, '/health')).close();
|
||||
final body = jsonDecode(await resp.transform(utf8.decoder).join()) as Map;
|
||||
http.close();
|
||||
expect(body['ok'], isTrue);
|
||||
expect(body['name'], 'iso');
|
||||
expect(body['proto'], RelayConstants.protoVersion);
|
||||
expect(logs.any((l) => '${l['message']}'.contains('listening on $port')), isTrue);
|
||||
|
||||
(r['stop'] as SendPort).send({'t': 'stop'});
|
||||
await exit.first.timeout(const Duration(seconds: 10));
|
||||
expect(crashes, isEmpty);
|
||||
// The port is free again.
|
||||
final again = await ServerSocket.bind(InternetAddress.anyIPv4, port);
|
||||
await again.close();
|
||||
events.close();
|
||||
errors.close();
|
||||
});
|
||||
|
||||
test('serve reports a bind failure as an error event', () async {
|
||||
final blocker = await ServerSocket.bind(InternetAddress.anyIPv4, 0);
|
||||
final events = ReceivePort();
|
||||
final exit = ReceivePort();
|
||||
await Isolate.spawn(
|
||||
_run,
|
||||
(AppConfig(port: blocker.port, secret: '', name: 'x').toMap(),
|
||||
events.sendPort),
|
||||
onExit: exit.sendPort);
|
||||
final first = await events
|
||||
.firstWhere((m) => m is Map && m['t'] != 'log')
|
||||
.timeout(const Duration(seconds: 10)) as Map;
|
||||
expect(first['t'], 'error');
|
||||
await exit.first.timeout(const Duration(seconds: 10));
|
||||
await blocker.close();
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user