alt/apps/client/test/integrations/socket/socket_connection_controller_test.dart
toki 48ee5189a9 refactor: socket state management and dashboard screen updates
- Move G06 task to archive
- Update dashboard screen presentation
- Refactor socket connection controller
- Add socket connection controller unit test
2026-06-02 10:32:29 +09:00

255 lines
7.8 KiB
Dart

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<HelloResponse> Function()? helloMock;
bool isClosed = false;
final List<void Function(AltSocketClient)> _disconnectListeners = [];
FakeAltSocketClient(super.ws, {this.helloMock});
@override
Future<HelloResponse> 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<void> 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'));
});
});
}