alt/apps/client/test/integrations/socket/alt_socket_client_test.dart

329 lines
10 KiB
Dart

import 'dart:async';
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:proto_socket/proto_socket.dart';
import 'package:alt_client/src/generated/alt/v1/backtest.pb.dart';
import 'package:alt_client/src/generated/alt/v1/common.pb.dart';
import 'package:alt_client/src/integrations/socket/alt_socket_client.dart';
import 'package:alt_client/src/integrations/socket/socket_endpoint.dart';
class FakeWebSocket extends Stream<dynamic> implements WebSocket {
final StreamController<dynamic> _controller = StreamController<dynamic>();
final List<List<int>> sentBytes = [];
bool isClosed = false;
@override
void add(dynamic data) {
if (data is List<int>) {
sentBytes.add(data);
}
}
@override
Future<void> close([int? code, String? reason]) async {
isClosed = true;
await _controller.close();
}
void feedFromServer(List<int> data) {
_controller.add(data);
}
@override
StreamSubscription<dynamic> listen(
void Function(dynamic event)? onData, {
Function? onError,
void Function()? onDone,
bool? cancelOnError,
}) {
return _controller.stream.listen(
onData,
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError,
);
}
@override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
void main() {
group('AltSocketEndpoint tests', () {
test('default constructor values', () {
const endpoint = AltSocketEndpoint();
expect(endpoint.host, equals('127.0.0.1'));
expect(endpoint.port, equals(8080));
expect(endpoint.path, equals('/socket'));
expect(endpoint.toString(), equals('ws://127.0.0.1:8080/socket'));
});
test('custom values', () {
const endpoint = AltSocketEndpoint(
host: 'api.example.com',
port: 443,
path: '/ws',
);
expect(endpoint.host, equals('api.example.com'));
expect(endpoint.port, equals(443));
expect(endpoint.path, equals('/ws'));
expect(endpoint.toString(), equals('ws://api.example.com:443/ws'));
});
test('equality and hashCode', () {
const e1 = AltSocketEndpoint(host: '1.2.3.4', port: 9000, path: '/a');
const e2 = AltSocketEndpoint(host: '1.2.3.4', port: 9000, path: '/a');
const e3 = AltSocketEndpoint(host: '1.2.3.4', port: 9000, path: '/b');
expect(e1, equals(e2));
expect(e1.hashCode, equals(e2.hashCode));
expect(e1, isNot(equals(e3)));
});
});
group('AltSocketClient tests', () {
late FakeWebSocket fakeWs;
late AltSocketClient client;
setUp(() {
fakeWs = FakeWebSocket();
client = AltSocketClient(
fakeWs,
heartbeatIntervalTime: 1,
heartbeatWaitTime: 1,
);
});
tearDown(() async {
await client.close();
});
test('client initialization and parser registration', () {
expect(client.isAlive, isTrue);
final helloRequestType =
HelloRequest.getDefault().info_.qualifiedMessageName;
final helloResponseType =
HelloResponse.getDefault().info_.qualifiedMessageName;
expect(client.getGenerator<HelloRequest>(helloRequestType), isNotNull);
expect(client.getGenerator<HelloResponse>(helloResponseType), isNotNull);
});
test('hello handshake request-response loop', () async {
final helloFuture = client.hello(timeout: const Duration(seconds: 1));
await Future<void>.delayed(Duration.zero);
expect(fakeWs.sentBytes, isNotEmpty);
final firstSentPacketBytes = fakeWs.sentBytes.first;
final sentPacket = PacketBase.fromBuffer(firstSentPacketBytes);
final helloRequestType =
HelloRequest.getDefault().info_.qualifiedMessageName;
expect(sentPacket.typeName, equals(helloRequestType));
final sentHelloRequest = HelloRequest.fromBuffer(sentPacket.data);
expect(sentHelloRequest.clientName, equals('alt-client'));
expect(sentHelloRequest.clientVersion, equals('dev'));
expect(sentHelloRequest.altProtocolVersion, equals('alt.v1'));
final expectedResponse = HelloResponse()
..serverName = 'alt-server'
..serverVersion = '1.0.0'
..altProtocolVersion = 'alt.v1';
final responsePacket = PacketBase()
..typeName = HelloResponse.getDefault().info_.qualifiedMessageName
..nonce = 100
..responseNonce = sentPacket.nonce
..data = expectedResponse.writeToBuffer();
fakeWs.feedFromServer(responsePacket.writeToBuffer());
final response = await helloFuture;
expect(response.serverName, equals('alt-server'));
expect(response.serverVersion, equals('1.0.0'));
expect(response.altProtocolVersion, equals('alt.v1'));
});
// Drains the request sent by [trigger], asserts the sent packet type/payload
// through [onSent], then feeds a matching response packet back so the worker
// -> API -> client backtest boundary round-trips consistently.
Future<PacketBase> roundTrip({
required Future<void> Function() trigger,
required void Function(PacketBase sentPacket) onSent,
required String responseTypeName,
required List<int> responseData,
}) async {
final sentCountBefore = fakeWs.sentBytes.length;
unawaited(trigger());
await Future<void>.delayed(Duration.zero);
expect(fakeWs.sentBytes.length, greaterThan(sentCountBefore));
final sentPacket = PacketBase.fromBuffer(fakeWs.sentBytes.last);
onSent(sentPacket);
final responsePacket = PacketBase()
..typeName = responseTypeName
..nonce = 200
..responseNonce = sentPacket.nonce
..data = responseData;
fakeWs.feedFromServer(responsePacket.writeToBuffer());
return sentPacket;
}
test('listBacktestRuns request-response loop', () async {
final request = ListBacktestRunsRequest()
..status = BacktestRunStatus.BACKTEST_RUN_STATUS_SUCCEEDED;
final expectedResponse = ListBacktestRunsResponse()
..runs.add(BacktestRun()
..id = 'run-1'
..status = BacktestRunStatus.BACKTEST_RUN_STATUS_SUCCEEDED);
final future = client.listBacktestRuns(
request,
timeout: const Duration(seconds: 1),
);
await roundTrip(
trigger: () => future,
onSent: (sentPacket) {
expect(
sentPacket.typeName,
equals(
ListBacktestRunsRequest.getDefault().info_.qualifiedMessageName,
),
);
final sentRequest = ListBacktestRunsRequest.fromBuffer(sentPacket.data);
expect(
sentRequest.status,
equals(BacktestRunStatus.BACKTEST_RUN_STATUS_SUCCEEDED),
);
},
responseTypeName:
ListBacktestRunsResponse.getDefault().info_.qualifiedMessageName,
responseData: expectedResponse.writeToBuffer(),
);
final response = await future;
expect(response.runs, hasLength(1));
expect(response.runs.first.id, equals('run-1'));
expect(
response.runs.first.status,
equals(BacktestRunStatus.BACKTEST_RUN_STATUS_SUCCEEDED),
);
});
test('getBacktestRunDetail request-response loop', () async {
final request = GetBacktestRunDetailRequest()..runId = 'run-7';
final expectedResponse = GetBacktestRunDetailResponse()
..run = (BacktestRun()..id = 'run-7')
..result = (BacktestResult()..runId = 'run-7');
final future = client.getBacktestRunDetail(
request,
timeout: const Duration(seconds: 1),
);
await roundTrip(
trigger: () => future,
onSent: (sentPacket) {
expect(
sentPacket.typeName,
equals(
GetBacktestRunDetailRequest.getDefault()
.info_
.qualifiedMessageName,
),
);
final sentRequest =
GetBacktestRunDetailRequest.fromBuffer(sentPacket.data);
expect(sentRequest.runId, equals('run-7'));
},
responseTypeName: GetBacktestRunDetailResponse.getDefault()
.info_
.qualifiedMessageName,
responseData: expectedResponse.writeToBuffer(),
);
final response = await future;
expect(response.run.id, equals('run-7'));
expect(response.result.runId, equals('run-7'));
});
test('getBacktestResult request-response loop', () async {
final request = GetBacktestResultRequest()..runId = 'run-9';
final expectedResponse = GetBacktestResultResponse()
..result = (BacktestResult()..runId = 'run-9');
final future = client.getBacktestResult(
request,
timeout: const Duration(seconds: 1),
);
await roundTrip(
trigger: () => future,
onSent: (sentPacket) {
expect(
sentPacket.typeName,
equals(
GetBacktestResultRequest.getDefault().info_.qualifiedMessageName,
),
);
final sentRequest =
GetBacktestResultRequest.fromBuffer(sentPacket.data);
expect(sentRequest.runId, equals('run-9'));
},
responseTypeName:
GetBacktestResultResponse.getDefault().info_.qualifiedMessageName,
responseData: expectedResponse.writeToBuffer(),
);
final response = await future;
expect(response.result.runId, equals('run-9'));
});
test('compareBacktestRuns request-response loop', () async {
final request = CompareBacktestRunsRequest()
..runIds.addAll(['run-1', 'run-2']);
final expectedResponse = CompareBacktestRunsResponse()
..results.add(BacktestResult()..runId = 'run-1')
..results.add(BacktestResult()..runId = 'run-2');
final future = client.compareBacktestRuns(
request,
timeout: const Duration(seconds: 1),
);
await roundTrip(
trigger: () => future,
onSent: (sentPacket) {
expect(
sentPacket.typeName,
equals(
CompareBacktestRunsRequest.getDefault().info_.qualifiedMessageName,
),
);
final sentRequest =
CompareBacktestRunsRequest.fromBuffer(sentPacket.data);
expect(sentRequest.runIds, equals(['run-1', 'run-2']));
},
responseTypeName:
CompareBacktestRunsResponse.getDefault().info_.qualifiedMessageName,
responseData: expectedResponse.writeToBuffer(),
);
final response = await future;
expect(response.results, hasLength(2));
expect(
response.results.map((r) => r.runId),
equals(['run-1', 'run-2']),
);
});
});
}