/** * NetworkClient 集成测试 * 测试网络客户端的完整功能,包括依赖注入和错误处理 */ import { NetworkClient } from '../src/core/NetworkClient'; // Mock 所有外部依赖 jest.mock('@esengine/ecs-framework', () => ({ Core: { scene: null, schedule: { scheduleRepeating: jest.fn((callback: Function, interval: number) => ({ stop: jest.fn() })) } }, Emitter: jest.fn().mockImplementation(() => ({ emit: jest.fn(), on: jest.fn(), off: jest.fn(), removeAllListeners: jest.fn() })) })); jest.mock('@esengine/ecs-framework-network-shared', () => ({ NetworkValue: {}, generateMessageId: jest.fn(() => 'test-message-id-123'), generateNetworkId: jest.fn(() => 12345), NetworkUtils: { generateMessageId: jest.fn(() => 'test-message-id-456'), calculateDistance: jest.fn(() => 100), isNodeEnvironment: jest.fn(() => false), isBrowserEnvironment: jest.fn(() => true) } })); // Mock WebSocket class MockWebSocket { public readyState: number = WebSocket.CONNECTING; public onopen: ((event: Event) => void) | null = null; public onclose: ((event: CloseEvent) => void) | null = null; public onmessage: ((event: MessageEvent) => void) | null = null; public onerror: ((event: Event) => void) | null = null; constructor(public url: string, public protocols?: string | string[]) {} send(data: string | ArrayBuffer | Blob): void {} close(code?: number, reason?: string): void { this.readyState = WebSocket.CLOSED; if (this.onclose) { this.onclose(new CloseEvent('close', { code: code || 1000, reason: reason || '' })); } } } (global as any).WebSocket = MockWebSocket; (global as any).WebSocket.CONNECTING = 0; (global as any).WebSocket.OPEN = 1; (global as any).WebSocket.CLOSING = 2; (global as any).WebSocket.CLOSED = 3; describe('NetworkClient 集成测试', () => { let client: NetworkClient; beforeEach(() => { jest.clearAllMocks(); }); afterEach(() => { if (client) { client.disconnect().catch(() => {}); client = null as any; } }); describe('依赖注入测试', () => { it('应该正确处理所有依赖模块', () => { expect(() => { client = new NetworkClient({ transport: 'websocket', transportConfig: { host: 'localhost', port: 8080 } }); }).not.toThrow(); expect(client).toBeInstanceOf(NetworkClient); }); it('应该正确使用network-shared中的工具函数', () => { const { generateMessageId, NetworkUtils } = require('@esengine/ecs-framework-network-shared'); client = new NetworkClient({ transport: 'websocket', transportConfig: { host: 'localhost', port: 8080 } }); // 验证network-shared模块被正确导入 expect(generateMessageId).toBeDefined(); expect(NetworkUtils).toBeDefined(); }); it('应该正确使用ecs-framework中的Core模块', () => { const { Core } = require('@esengine/ecs-framework'); client = new NetworkClient({ transport: 'websocket', transportConfig: { host: 'localhost', port: 8080 } }); expect(Core).toBeDefined(); expect(Core.schedule).toBeDefined(); }); }); describe('构造函数错误处理', () => { it('应该处理network-shared模块导入失败', () => { // 重置模块并模拟导入失败 jest.resetModules(); jest.doMock('@esengine/ecs-framework-network-shared', () => { throw new Error('network-shared模块导入失败'); }); expect(() => { const { NetworkClient } = require('../src/core/NetworkClient'); new NetworkClient({ transportType: 'websocket', host: 'localhost', port: 8080 }); }).toThrow(); }); it('应该处理ecs-framework模块导入失败', () => { // 重置模块并模拟导入失败 jest.resetModules(); jest.doMock('@esengine/ecs-framework', () => { throw new Error('ecs-framework模块导入失败'); }); expect(() => { const { NetworkClient } = require('../src/core/NetworkClient'); new NetworkClient({ transportType: 'websocket', host: 'localhost', port: 8080 }); }).toThrow(); }); it('应该处理传输层构造失败', () => { // Mock传输层构造函数抛出异常 const originalWebSocket = (global as any).WebSocket; (global as any).WebSocket = jest.fn(() => { throw new Error('WebSocket不可用'); }); client = new NetworkClient({ transport: 'websocket', transportConfig: { host: 'localhost', port: 8080 } }); expect(client.connect()).rejects.toThrow(); // 恢复原始WebSocket (global as any).WebSocket = originalWebSocket; }); }); describe('功能测试', () => { beforeEach(() => { client = new NetworkClient({ transport: 'websocket', transportConfig: { host: 'localhost', port: 8080 } }); }); it('应该能够成功连接', async () => { const connectPromise = client.connect(); // 模拟连接成功 setTimeout(() => { const transport = (client as any).transport; if (transport && transport.websocket && transport.websocket.onopen) { transport.websocket.readyState = WebSocket.OPEN; transport.websocket.onopen(new Event('open')); } }, 0); await expect(connectPromise).resolves.toBeUndefined(); }); it('应该能够发送消息', async () => { // 先连接 const connectPromise = client.connect(); setTimeout(() => { const transport = (client as any).transport; if (transport && transport.websocket && transport.websocket.onopen) { transport.websocket.readyState = WebSocket.OPEN; transport.websocket.onopen(new Event('open')); } }, 0); await connectPromise; // 发送消息 const message = { type: 'custom' as const, data: { test: 'message' }, reliable: true }; // NetworkClient没有直接的sendMessage方法,它通过RPC调用 }); it('应该能够正确断开连接', async () => { await expect(client.disconnect()).resolves.toBeUndefined(); }); it('应该返回正确的认证状态', () => { expect(client.isAuthenticated()).toBe(false); }); it('应该能够获取网络对象列表', () => { const networkObjects = client.getAllNetworkObjects(); expect(Array.isArray(networkObjects)).toBe(true); expect(networkObjects.length).toBe(0); }); }); describe('消息ID生成测试', () => { beforeEach(() => { client = new NetworkClient({ transport: 'websocket', transportConfig: { host: 'localhost', port: 8080 } }); }); it('应该能够生成唯一的消息ID', () => { const messageId1 = (client as any).generateMessageId(); const messageId2 = (client as any).generateMessageId(); expect(typeof messageId1).toBe('string'); expect(typeof messageId2).toBe('string'); expect(messageId1).not.toBe(messageId2); }); it('生成的消息ID应该符合预期格式', () => { const messageId = (client as any).generateMessageId(); // 检查消息ID格式(时间戳 + 随机字符串) expect(messageId).toMatch(/^[a-z0-9]+$/); expect(messageId.length).toBeGreaterThan(10); }); }); describe('错误恢复测试', () => { beforeEach(() => { client = new NetworkClient({ transportType: 'websocket', host: 'localhost', port: 8080, maxReconnectAttempts: 2, reconnectInterval: 100 }); }); it('连接失败后应该尝试重连', async () => { let connectAttempts = 0; const originalWebSocket = (global as any).WebSocket; (global as any).WebSocket = jest.fn().mockImplementation(() => { connectAttempts++; const ws = new originalWebSocket('ws://localhost:8080'); // 模拟连接失败 setTimeout(() => { if (ws.onerror) { ws.onerror(new Event('error')); } }, 0); return ws; }); await expect(client.connect()).rejects.toThrow(); // 等待重连尝试 await new Promise(resolve => setTimeout(resolve, 300)); expect(connectAttempts).toBeGreaterThan(1); // 恢复原始WebSocket (global as any).WebSocket = originalWebSocket; }); it('达到最大重连次数后应该停止重连', async () => { const maxAttempts = 2; client = new NetworkClient({ transportType: 'websocket', host: 'localhost', port: 8080, maxReconnectAttempts: maxAttempts, reconnectInterval: 50 }); let connectAttempts = 0; const originalWebSocket = (global as any).WebSocket; (global as any).WebSocket = jest.fn().mockImplementation(() => { connectAttempts++; const ws = new originalWebSocket('ws://localhost:8080'); setTimeout(() => { if (ws.onerror) { ws.onerror(new Event('error')); } }, 0); return ws; }); await expect(client.connect()).rejects.toThrow(); // 等待所有重连尝试完成 await new Promise(resolve => setTimeout(resolve, 200)); expect(connectAttempts).toBeLessThanOrEqual(maxAttempts + 1); // 恢复原始WebSocket (global as any).WebSocket = originalWebSocket; }); }); describe('内存泄漏防护测试', () => { it('断开连接时应该清理所有资源', async () => { client = new NetworkClient({ transport: 'websocket', transportConfig: { host: 'localhost', port: 8080 } }); const { Emitter } = require('@esengine/ecs-framework'); const emitterInstance = Emitter.mock.results[Emitter.mock.results.length - 1].value; await client.disconnect(); expect(emitterInstance.removeAllListeners).toHaveBeenCalled(); }); it('多次创建和销毁客户端不应该造成内存泄漏', () => { const initialEmitterCallCount = require('@esengine/ecs-framework').Emitter.mock.calls.length; // 创建和销毁多个客户端实例 for (let i = 0; i < 5; i++) { const tempClient = new NetworkClient({ transportType: 'websocket', host: 'localhost', port: 8080 }); tempClient.disconnect().catch(() => {}); } const finalEmitterCallCount = require('@esengine/ecs-framework').Emitter.mock.calls.length; // 验证Emitter实例数量符合预期 expect(finalEmitterCallCount - initialEmitterCallCount).toBe(5); }); }); });