- Move G06 task to archive - Update dashboard screen presentation - Refactor socket connection controller - Add socket connection controller unit test
255 lines
7.8 KiB
Dart
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'));
|
|
});
|
|
});
|
|
}
|