import 'dart:async'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:alt_client/src/integrations/socket/socket_connection_controller.dart'; import 'package:alt_client/src/integrations/socket/socket_endpoint.dart'; import 'package:alt_client/src/integrations/socket/alt_socket_client.dart'; import 'package:alt_client/src/generated/alt/v1/common.pb.dart'; import 'alt_socket_client_test.dart'; // To use FakeWebSocket class FakeAltSocketClient extends AltSocketClient { final Future Function()? helloMock; bool isClosed = false; final List _disconnectListeners = []; FakeAltSocketClient(super.ws, {this.helloMock}); @override Future hello({Duration timeout = const Duration(seconds: 2)}) { if (helloMock != null) { return helloMock!(); } return Future.value( HelloResponse() ..serverName = 'alt-server' ..serverVersion = '1.0.0' ..altProtocolVersion = 'alt.v1', ); } @override void addDisconnectListener(void Function(AltSocketClient) fn) { _disconnectListeners.add(fn); } @override Future close() async { isClosed = true; for (final listener in _disconnectListeners) { listener(this); } } void triggerDisconnect() { for (final listener in _disconnectListeners) { listener(this); } } } void main() { group('SocketConnectionController tests', () { late ProviderContainer container; late AltSocketEndpoint endpoint; late FakeWebSocket fakeWs; setUp(() { container = ProviderContainer(); endpoint = const AltSocketEndpoint( host: '127.0.0.1', port: 9000, path: '/test-socket', ); fakeWs = FakeWebSocket(); }); tearDown(() { container.dispose(); }); test('initial state is disconnected', () { final state = container.read(socketConnectionControllerProvider); expect(state.status, equals(SocketConnectionStatus.disconnected)); expect(state.endpoint, isNull); expect(state.reasonCode, isNull); expect(state.lastError, isNull); }); test('successful connection and handshake', () async { final controller = container.read( socketConnectionControllerProvider.notifier, ); late FakeAltSocketClient client; controller.connectFactory = (ep) async { client = FakeAltSocketClient(fakeWs); return client; }; final connectFuture = controller.connect(endpoint); // Verify connecting state expect( container.read(socketConnectionControllerProvider).status, equals(SocketConnectionStatus.connecting), ); expect( container.read(socketConnectionControllerProvider).endpoint, equals(endpoint), ); await connectFuture; // Verify connected state final state = container.read(socketConnectionControllerProvider); expect(state.status, equals(SocketConnectionStatus.connected)); expect(state.endpoint, equals(endpoint)); expect(state.reasonCode, isNull); expect(state.lastError, isNull); expect(controller.client, equals(client)); }); test('connect throws standard error', () async { final controller = container.read( socketConnectionControllerProvider.notifier, ); final error = Exception('Failed to connect'); controller.connectFactory = (ep) async { throw error; }; await expectLater(controller.connect(endpoint), throwsA(equals(error))); final state = container.read(socketConnectionControllerProvider); expect(state.status, equals(SocketConnectionStatus.error)); expect(state.endpoint, equals(endpoint)); expect(state.reasonCode, equals('connect_failed')); expect(state.lastError, equals(error)); }); test('connect throws TimeoutException', () async { final controller = container.read( socketConnectionControllerProvider.notifier, ); final error = TimeoutException('Connect timeout'); controller.connectFactory = (ep) async { throw error; }; await expectLater(controller.connect(endpoint), throwsA(equals(error))); final state = container.read(socketConnectionControllerProvider); expect(state.status, equals(SocketConnectionStatus.timeout)); expect(state.endpoint, equals(endpoint)); expect(state.reasonCode, equals('connect_timeout')); expect(state.lastError, equals(error)); }); test('handshake hello throws standard error', () async { final controller = container.read( socketConnectionControllerProvider.notifier, ); final error = Exception('Hello failed'); late FakeAltSocketClient client; controller.connectFactory = (ep) async { client = FakeAltSocketClient( fakeWs, helloMock: () async { throw error; }, ); return client; }; await expectLater(controller.connect(endpoint), throwsA(equals(error))); final state = container.read(socketConnectionControllerProvider); expect(state.status, equals(SocketConnectionStatus.error)); expect(state.endpoint, equals(endpoint)); expect(state.reasonCode, equals('hello_failed')); expect(state.lastError, equals(error)); }); test('handshake hello throws TimeoutException', () async { final controller = container.read( socketConnectionControllerProvider.notifier, ); final error = TimeoutException('Hello timeout'); late FakeAltSocketClient client; controller.connectFactory = (ep) async { client = FakeAltSocketClient( fakeWs, helloMock: () async { throw error; }, ); return client; }; await expectLater(controller.connect(endpoint), throwsA(equals(error))); final state = container.read(socketConnectionControllerProvider); expect(state.status, equals(SocketConnectionStatus.timeout)); expect(state.endpoint, equals(endpoint)); expect(state.reasonCode, equals('hello_timeout')); expect(state.lastError, equals(error)); }); test('explicit disconnect', () async { final controller = container.read( socketConnectionControllerProvider.notifier, ); late FakeAltSocketClient client; controller.connectFactory = (ep) async { client = FakeAltSocketClient(fakeWs); return client; }; await controller.connect(endpoint); expect( container.read(socketConnectionControllerProvider).status, equals(SocketConnectionStatus.connected), ); await controller.disconnect(); final state = container.read(socketConnectionControllerProvider); expect(state.status, equals(SocketConnectionStatus.disconnected)); expect(state.endpoint, equals(endpoint)); expect(state.reasonCode, equals('explicit_disconnect')); expect(controller.client, isNull); expect(client.isClosed, isTrue); }); test('disconnect listener triggers state update', () async { final controller = container.read( socketConnectionControllerProvider.notifier, ); late FakeAltSocketClient client; controller.connectFactory = (ep) async { client = FakeAltSocketClient(fakeWs); return client; }; await controller.connect(endpoint); expect( container.read(socketConnectionControllerProvider).status, equals(SocketConnectionStatus.connected), ); client.triggerDisconnect(); final state = container.read(socketConnectionControllerProvider); expect(state.status, equals(SocketConnectionStatus.disconnected)); expect(state.endpoint, equals(endpoint)); expect(state.reasonCode, equals('socket_closed')); }); }); }