proto-socket/dart/test/socket_test.dart
toki 240239682c Initial commit: Dart implementation of toki-socket protocol
- Protobuf-based binary TCP socket library
- ProtobufClient / ProtobufServer abstract classes with heartbeat
- Type-based message routing via Communicator
- 4-byte big-endian length-prefixed framing protocol
- 8 integration tests (all passing)
- PROTOCOL.md wire format specification
- VSCode launch.json for test runner

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 13:44:04 +09:00

154 lines
5 KiB
Dart

import 'dart:async';
import 'dart:io';
import 'package:test/test.dart';
import 'package:toki_socket/toki_socket.dart';
const _testPort = 19090;
const _host = 'localhost';
// ── 테스트 전용 픽스처 ──────────────────────────────────────────
class _TestClient extends ProtobufClient {
_TestClient(Socket socket)
: super(socket, 5, 3, {
TestData.getDefault().info_.qualifiedMessageName: TestData.fromBuffer,
});
}
class _TestServer extends ProtobufServer {
final receivedMessages = <TestData>[];
final connectedClients = <ProtobufClient>[];
_TestServer() : super(_host, _testPort, (socket) => _TestClient(socket));
@override
void onClientConnected(ProtobufClient client) {
connectedClients.add(client);
client.addListener<TestData>((data) => receivedMessages.add(data));
}
}
// ── 테스트 ──────────────────────────────────────────────────────
void main() {
group('ProtobufServer', () {
late _TestServer server;
setUp(() async {
server = _TestServer();
await server.start();
});
tearDown(() async {
await server.stop();
});
test('서버가 정상 시작된다', () {
expect(server.started, isTrue);
});
});
group('ProtobufClient', () {
late _TestServer server;
late Socket clientSocket;
late _TestClient client;
setUp(() async {
server = _TestServer();
await server.start();
clientSocket = await Socket.connect(_host, _testPort);
client = _TestClient(clientSocket);
await Future.delayed(const Duration(milliseconds: 100));
});
tearDown(() async {
client.dispose();
await Future.delayed(const Duration(milliseconds: 100));
await server.stop();
});
test('클라이언트가 서버에 연결된다', () {
expect(server.connectedClients, isNotEmpty);
});
test('TestData 메시지를 서버가 수신한다', () async {
final msg = TestData()
..index = 42
..message = 'hello toki-socket';
await client.send(msg);
await Future.delayed(const Duration(milliseconds: 200));
expect(server.receivedMessages, hasLength(1));
expect(server.receivedMessages.first.index, equals(42));
expect(server.receivedMessages.first.message, equals('hello toki-socket'));
});
test('여러 메시지를 순서대로 수신한다', () async {
for (var i = 0; i < 5; i++) {
await client.send(TestData()
..index = i
..message = 'msg$i');
}
await Future.delayed(const Duration(milliseconds: 300));
expect(server.receivedMessages, hasLength(5));
for (var i = 0; i < 5; i++) {
expect(server.receivedMessages[i].index, equals(i));
}
});
test('서버에서 클라이언트로 메시지를 전송한다', () async {
final completer = Completer<TestData>();
client.addListener<TestData>((data) => completer.complete(data));
await Future.delayed(const Duration(milliseconds: 100));
final serverClient = server.connectedClients.first;
await serverClient.send(TestData()
..index = 99
..message = 'from server');
final received = await completer.future.timeout(const Duration(seconds: 2));
expect(received.index, equals(99));
expect(received.message, equals('from server'));
});
test('nonce가 송신마다 증가한다', () async {
final receivedNonces = <int>[];
server.connectedClients.first.addListener<TestData>((data) {});
// 서버에서 수신한 PacketBase의 nonce를 검증하기 위해
// 클라이언트에서 3개의 메시지를 순서대로 전송
for (var i = 1; i <= 3; i++) {
await client.send(TestData()..index = i);
}
await Future.delayed(const Duration(milliseconds: 200));
// 3개 수신 확인 (nonce 단조 증가는 wire level 검증 — 여기선 수신 순서로 간접 확인)
expect(server.receivedMessages, hasLength(3));
});
test('클라이언트 disconnect 시 서버 콜백이 호출된다', () async {
final completer = Completer<void>();
server.connectedClients.first.addDisconnectListener((_) {
if (!completer.isCompleted) completer.complete();
});
client.dispose();
await completer.future.timeout(const Duration(seconds: 2));
// completer가 완료되면 disconnect 콜백이 정상 호출된 것
expect(completer.isCompleted, isTrue);
});
test('HeartBeat interval 동안 연결이 유지된다', () async {
// heartbeat interval(5초)보다 짧은 시간 대기 후 메시지 송수신 확인
await Future.delayed(const Duration(seconds: 2));
await client.send(TestData()..index = 1..message = 'alive check');
await Future.delayed(const Duration(milliseconds: 200));
expect(server.receivedMessages, hasLength(1));
});
});
}