proto-socket/dart/test/socket_test.dart

562 lines
18 KiB
Dart

import 'dart:async';
import 'dart:io';
import 'package:test/test.dart';
import 'package:toki_socket/toki_socket.dart';
const _testPort = 19090;
const _testPortSsl = 19091;
const _testPortWs = 19092;
const _testPortWss = 19093;
const _host = 'localhost';
const _certPath = 'test/certs/server.crt';
const _keyPath = 'test/certs/server.key';
// ── 테스트 전용 픽스처 ──────────────────────────────────────────
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));
}
}
class _TestServerSsl extends ProtobufServer {
final receivedMessages = <TestData>[];
final connectedClients = <ProtobufClient>[];
_TestServerSsl(SecurityContext ctx)
: super.secure(_host, _testPortSsl, ctx, (socket) => _TestClient(socket));
@override
void onClientConnected(ProtobufClient client) {
connectedClients.add(client);
client.addListener<TestData>((data) => receivedMessages.add(data));
}
}
class _TestWsClient extends WsProtobufClient {
_TestWsClient(WebSocket ws)
: super(ws, 5, 3, {
TestData.getDefault().info_.qualifiedMessageName: TestData.fromBuffer,
});
}
class _TestWsServer extends WsProtobufServer {
final receivedMessages = <TestData>[];
final connectedClients = <WsProtobufClient>[];
_TestWsServer() : super(_host, _testPortWs, (ws) => _TestWsClient(ws));
@override
void onClientConnected(WsProtobufClient client) {
connectedClients.add(client);
client.addListener<TestData>((data) => receivedMessages.add(data));
}
}
class _TestWsServerSsl extends WsProtobufServer {
final receivedMessages = <TestData>[];
final connectedClients = <WsProtobufClient>[];
_TestWsServerSsl(SecurityContext ctx)
: super.secure(_host, _testPortWss, ctx, (ws) => _TestWsClient(ws));
@override
void onClientConnected(WsProtobufClient client) {
connectedClients.add(client);
client.addListener<TestData>((data) => receivedMessages.add(data));
}
}
class _TestReqServer extends ProtobufServer {
_TestReqServer() : super(_host, _testPort, (socket) => _TestClient(socket));
@override
void onClientConnected(ProtobufClient client) {
client.addRequestListener<TestData, TestData>((req) async {
return TestData()
..index = req.index * 2
..message = 'echo: ${req.message}';
});
}
}
class _TestWsReqServer extends WsProtobufServer {
_TestWsReqServer() : super(_host, _testPortWs, (ws) => _TestWsClient(ws));
@override
void onClientConnected(WsProtobufClient client) {
client.addRequestListener<TestData, TestData>((req) async {
return TestData()
..index = req.index * 2
..message = 'echo: ${req.message}';
});
}
}
// ── 테스트 ──────────────────────────────────────────────────────
void main() {
// ── Plain TCP ────────────────────────────────────────────────
group('ProtobufServer (plain)', () {
late _TestServer server;
setUp(() async {
server = _TestServer();
await server.start();
});
tearDown(() async {
await server.stop();
});
test('서버가 정상 시작된다', () {
expect(server.started, isTrue);
expect(server.isSecure, isFalse);
});
});
group('ProtobufClient (plain)', () {
late _TestServer server;
late _TestClient client;
setUp(() async {
server = _TestServer();
await server.start();
final socket = await ProtobufClient.connect(_host, _testPort);
client = _TestClient(socket);
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 {
await client.send(TestData()
..index = 42
..message = 'hello toki-socket');
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));
await server.connectedClients.first.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 {
for (var i = 1; i <= 3; i++) {
await client.send(TestData()..index = i);
}
await Future.delayed(const Duration(milliseconds: 200));
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));
expect(completer.isCompleted, isTrue);
});
test('HeartBeat interval 동안 연결이 유지된다', () async {
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));
});
});
// ── SSL/TLS ──────────────────────────────────────────────────
group('ProtobufServer (SSL)', () {
late _TestServerSsl server;
setUp(() async {
final ctx = SecurityContext()
..useCertificateChain(_certPath)
..usePrivateKey(_keyPath);
server = _TestServerSsl(ctx);
await server.start();
});
tearDown(() async {
await server.stop();
});
test('SSL 서버가 정상 시작된다', () {
expect(server.started, isTrue);
expect(server.isSecure, isTrue);
});
});
group('ProtobufClient (SSL)', () {
late _TestServerSsl server;
late _TestClient client;
setUp(() async {
final serverCtx = SecurityContext()
..useCertificateChain(_certPath)
..usePrivateKey(_keyPath);
server = _TestServerSsl(serverCtx);
await server.start();
final clientCtx = SecurityContext()
..setTrustedCertificates(_certPath);
final socket = await ProtobufClient.connectSecure(
_host, _testPortSsl,
context: clientCtx,
);
client = _TestClient(socket);
await Future.delayed(const Duration(milliseconds: 100));
});
tearDown(() async {
client.dispose();
await Future.delayed(const Duration(milliseconds: 100));
await server.stop();
});
test('SSL 클라이언트가 서버에 연결된다', () {
expect(server.connectedClients, isNotEmpty);
});
test('SSL TestData 메시지를 서버가 수신한다', () async {
await client.send(TestData()
..index = 7
..message = 'hello over ssl');
await Future.delayed(const Duration(milliseconds: 200));
expect(server.receivedMessages, hasLength(1));
expect(server.receivedMessages.first.index, equals(7));
expect(server.receivedMessages.first.message, equals('hello over ssl'));
});
test('SSL 서버에서 클라이언트로 메시지를 전송한다', () async {
final completer = Completer<TestData>();
client.addListener<TestData>((data) => completer.complete(data));
await Future.delayed(const Duration(milliseconds: 100));
await server.connectedClients.first.send(TestData()
..index = 55
..message = 'from ssl server');
final received = await completer.future.timeout(const Duration(seconds: 2));
expect(received.index, equals(55));
expect(received.message, equals('from ssl server'));
});
test('SSL 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));
expect(completer.isCompleted, isTrue);
});
});
// ── WebSocket ────────────────────────────────────────────────
group('WsProtobufServer (plain)', () {
late _TestWsServer server;
setUp(() async {
server = _TestWsServer();
await server.start();
});
tearDown(() async {
await server.stop();
});
test('WS 서버가 정상 시작된다', () {
expect(server.started, isTrue);
expect(server.isSecure, isFalse);
});
});
group('WsProtobufClient (plain)', () {
late _TestWsServer server;
late _TestWsClient client;
setUp(() async {
server = _TestWsServer();
await server.start();
final ws = await WsProtobufClient.connect(_host, _testPortWs);
client = _TestWsClient(ws);
await Future.delayed(const Duration(milliseconds: 100));
});
tearDown(() async {
client.dispose();
await Future.delayed(const Duration(milliseconds: 100));
await server.stop();
});
test('WS 클라이언트가 서버에 연결된다', () {
expect(server.connectedClients, isNotEmpty);
});
test('WS TestData 메시지를 서버가 수신한다', () async {
await client.send(TestData()
..index = 42
..message = 'hello ws');
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 ws'));
});
test('WS 서버에서 클라이언트로 메시지를 전송한다', () async {
final completer = Completer<TestData>();
client.addListener<TestData>((data) => completer.complete(data));
await Future.delayed(const Duration(milliseconds: 100));
await server.connectedClients.first.send(TestData()
..index = 99
..message = 'from ws server');
final received = await completer.future.timeout(const Duration(seconds: 2));
expect(received.index, equals(99));
expect(received.message, equals('from ws server'));
});
test('WS 클라이언트 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));
expect(completer.isCompleted, isTrue);
});
});
// ── WebSocket SSL ─────────────────────────────────────────────
group('WsProtobufServer (SSL)', () {
late _TestWsServerSsl server;
setUp(() async {
final ctx = SecurityContext()
..useCertificateChain(_certPath)
..usePrivateKey(_keyPath);
server = _TestWsServerSsl(ctx);
await server.start();
});
tearDown(() async {
await server.stop();
});
test('WSS 서버가 정상 시작된다', () {
expect(server.started, isTrue);
expect(server.isSecure, isTrue);
});
});
group('WsProtobufClient (SSL)', () {
late _TestWsServerSsl server;
late _TestWsClient client;
setUp(() async {
final serverCtx = SecurityContext()
..useCertificateChain(_certPath)
..usePrivateKey(_keyPath);
server = _TestWsServerSsl(serverCtx);
await server.start();
final clientCtx = SecurityContext()..setTrustedCertificates(_certPath);
final ws = await WsProtobufClient.connectSecure(
_host, _testPortWss,
context: clientCtx,
);
client = _TestWsClient(ws);
await Future.delayed(const Duration(milliseconds: 100));
});
tearDown(() async {
client.dispose();
await Future.delayed(const Duration(milliseconds: 100));
await server.stop();
});
test('WSS 클라이언트가 서버에 연결된다', () {
expect(server.connectedClients, isNotEmpty);
});
test('WSS TestData 메시지를 서버가 수신한다', () async {
await client.send(TestData()
..index = 7
..message = 'hello over wss');
await Future.delayed(const Duration(milliseconds: 200));
expect(server.receivedMessages, hasLength(1));
expect(server.receivedMessages.first.index, equals(7));
expect(server.receivedMessages.first.message, equals('hello over wss'));
});
test('WSS 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));
expect(completer.isCompleted, isTrue);
});
});
// ── Request-Response (TCP) ────────────────────────────────────
group('Request-Response (TCP plain)', () {
late _TestReqServer server;
late _TestClient client;
setUp(() async {
server = _TestReqServer();
await server.start();
final socket = await ProtobufClient.connect(_host, _testPort);
client = _TestClient(socket);
await Future.delayed(const Duration(milliseconds: 100));
});
tearDown(() async {
client.dispose();
await Future.delayed(const Duration(milliseconds: 100));
await server.stop();
});
test('sendRequest로 서버 응답을 받는다', () async {
final response = await client
.sendRequest<TestData, TestData>(TestData()
..index = 21
..message = 'hello')
.timeout(const Duration(seconds: 2));
expect(response.index, equals(42));
expect(response.message, equals('echo: hello'));
});
test('여러 sendRequest가 각각 올바른 응답을 받는다', () async {
final futures = [
client.sendRequest<TestData, TestData>(TestData()..index = 1..message = 'a'),
client.sendRequest<TestData, TestData>(TestData()..index = 2..message = 'b'),
client.sendRequest<TestData, TestData>(TestData()..index = 3..message = 'c'),
];
final results = await Future.wait(futures).timeout(const Duration(seconds: 3));
expect(results[0].index, equals(2));
expect(results[1].index, equals(4));
expect(results[2].index, equals(6));
expect(results[0].message, equals('echo: a'));
expect(results[1].message, equals('echo: b'));
expect(results[2].message, equals('echo: c'));
});
});
// ── Request-Response (WS) ─────────────────────────────────────
group('Request-Response (WS plain)', () {
late _TestWsReqServer server;
late _TestWsClient client;
setUp(() async {
server = _TestWsReqServer();
await server.start();
final ws = await WsProtobufClient.connect(_host, _testPortWs);
client = _TestWsClient(ws);
await Future.delayed(const Duration(milliseconds: 100));
});
tearDown(() async {
client.dispose();
await Future.delayed(const Duration(milliseconds: 100));
await server.stop();
});
test('WS sendRequest로 서버 응답을 받는다', () async {
final response = await client
.sendRequest<TestData, TestData>(TestData()
..index = 21
..message = 'hello ws')
.timeout(const Duration(seconds: 2));
expect(response.index, equals(42));
expect(response.message, equals('echo: hello ws'));
});
test('WS 여러 sendRequest가 각각 올바른 응답을 받는다', () async {
final futures = [
client.sendRequest<TestData, TestData>(TestData()..index = 1..message = 'x'),
client.sendRequest<TestData, TestData>(TestData()..index = 2..message = 'y'),
client.sendRequest<TestData, TestData>(TestData()..index = 3..message = 'z'),
];
final results = await Future.wait(futures).timeout(const Duration(seconds: 3));
expect(results[0].index, equals(2));
expect(results[1].index, equals(4));
expect(results[2].index, equals(6));
});
});
}