diff --git a/packages/dart_node_ws/test/ws_test.dart b/packages/dart_node_ws/test/ws_test.dart index 61e50e5..f73f564 100644 --- a/packages/dart_node_ws/test/ws_test.dart +++ b/packages/dart_node_ws/test/ws_test.dart @@ -4,12 +4,15 @@ @TestOn('node') library; +import 'dart:async'; +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; + +import 'package:dart_node_core/dart_node_core.dart'; import 'package:dart_node_coverage/dart_node_coverage.dart'; import 'package:dart_node_ws/dart_node_ws.dart'; import 'package:test/test.dart'; -//TODO: we need actual web socket server/client interaction tests here. - void main() { setUp(initCoverage); tearDownAll(() => writeCoverageFile('coverage/coverage.json')); @@ -79,14 +82,12 @@ void main() { test('onConnection registers handler', () { createWebSocketServer(port: 9994) ..onConnection((client, url) { - // Handler registered - just verify it doesn't throw + // Handler registered }) ..close(); }); test('onConnection receives client on connection', () { - // The connection test happens in websocket_test.dart (integration tests) - // Here we just verify the API works without throwing createWebSocketServer(port: 9993) ..onConnection((client, url) { expect(client, isNotNull); @@ -95,4 +96,132 @@ void main() { ..close(); }); }); + + group('WebSocket Integration Tests', () { + late WebSocketServer server; + const testPort = 3456; + + setUp(() async { + server = createWebSocketServer(port: testPort); + }); + + tearDown(() async { + server.close(); + await Future.delayed(const Duration(milliseconds: 50)); + }); + + test('client can connect to server', () async { + final completer = Completer(); + + server.onConnection((client, url) { + completer.complete(); + }); + + final client = _createWebSocketClient('ws://localhost:$testPort'); + + await completer.future.timeout(const Duration(seconds: 2)); + await _waitForOpen(client); + client.close(); + }); + + test('server receives messages from client', () async { + final messageCompleter = Completer(); + + server.onConnection((serverClient, url) { + serverClient.onMessage((message) { + messageCompleter.complete(message.text ?? ''); + }); + }); + + final client = _createWebSocketClient('ws://localhost:$testPort'); + + await _waitForOpen(client); + _sendMessage(client, 'Hello from client'); + + final receivedMessage = await messageCompleter.future.timeout( + const Duration(seconds: 2), + ); + expect(receivedMessage, equals('Hello from client')); + client.close(); + }); + + test('client receives messages from server', () async { + final messageCompleter = Completer(); + + server.onConnection((serverClient, url) { + serverClient.send('Welcome to server'); + }); + + final client = _createWebSocketClient('ws://localhost:$testPort'); + + _onMessage(client, (data) { + final message = _extractMessage(data); + messageCompleter.complete(message); + }); + + final receivedMessage = await messageCompleter.future.timeout( + const Duration(seconds: 2), + ); + expect(receivedMessage, equals('Welcome to server')); + client.close(); + }); + }); +} + +/// Creates a WebSocket client using Node.js ws package +JSWebSocket _createWebSocketClient(String url) { + final ws = requireModule('ws'); + final wsClass = switch (ws) { + final JSFunction f => f, + _ => throw StateError('WebSocket module not found'), + }; + return JSWebSocket(wsClass.callAsConstructor(url.toJS)); +} + +/// Waits for WebSocket to reach OPEN state +Future _waitForOpen(JSWebSocket ws) async { + final completer = Completer(); + + if (ws.readyState == 1) { + completer.complete(); + } else { + ws.on('open', (() => completer.complete()).toJS); + ws.on( + 'error', + ((JSAny error) => completer.completeError( + 'Connection failed: $error', + )).toJS, + ); + } + + return completer.future.timeout(const Duration(seconds: 2)); +} + +/// Sends a message through WebSocket +void _sendMessage(JSWebSocket ws, String message) { + ws.send(message.toJS); +} + +/// Sets up message handler for WebSocket +void _onMessage(JSWebSocket ws, void Function(JSAny) handler) { + ws.on('message', handler.toJS); +} + +/// Extracts string message from JSAny data +String _extractMessage(JSAny data) { + // Convert using JavaScript String() function for safety + try { + final stringConstructor = globalContext['String'] as JSFunction; + return (stringConstructor.callAsFunction(null, data) as JSString).toDart; + } catch (_) { + return data.toString(); + } +} + +/// JS interop types for WebSocket client +extension type JSWebSocket(JSObject _) implements JSObject { + external void on(String event, JSFunction handler); + external void send(JSAny data); + external void close([int? code, String? reason]); + external int get readyState; }