/** * 位置广播端到端测试 * * 功能描述: * - 测试位置广播系统的完整功能 * - 验证WebSocket连接和消息传递 * - 确保会话管理和用户状态同步 * - 测试位置更新和广播机制 * * 最近修改: * - 2026-01-08: 文件重命名 - 修正kebab-case为snake_case命名规范 (修改者: moyin) * * @author original * @version 1.0.1 * @since 2025-01-01 * @lastModified 2026-01-08 */ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import { io, Socket } from 'socket.io-client'; import { LocationBroadcastModule } from '../../location_broadcast.module'; describe('LocationBroadcast (e2e)', () => { let app: INestApplication; let authToken: string; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [LocationBroadcastModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); await app.listen(0); authToken = 'test-jwt-token'; }); afterAll(async () => { await app.close(); }); describe('WebSocket连接测试', () => { it('应该成功建立WebSocket连接', (done) => { const port = app.getHttpServer().address().port; const client = io(`http://localhost:${port}/location-broadcast`, { auth: { token: authToken }, transports: ['websocket'], }); client.on('connect', () => { expect(client.connected).toBe(true); client.disconnect(); done(); }); client.on('connect_error', (error) => { done(error); }); }); it('应该拒绝无效的认证令牌', (done) => { const port = app.getHttpServer().address().port; const client = io(`http://localhost:${port}/location-broadcast`, { auth: { token: 'invalid-token' }, transports: ['websocket'], }); client.on('connect_error', (error) => { expect(error).toBeDefined(); done(); }); client.on('connect', () => { client.disconnect(); done(new Error('应该拒绝无效令牌')); }); }); }); describe('会话管理测试', () => { let client: Socket; beforeEach((done) => { const port = app.getHttpServer().address().port; client = io(`http://localhost:${port}/location-broadcast`, { auth: { token: authToken }, transports: ['websocket'], }); client.on('connect', () => { done(); }); }); afterEach(() => { if (client) { client.disconnect(); } }); it('应该成功加入会话', (done) => { client.emit('join_session', { type: 'join_session', sessionId: 'test-session-001', initialPosition: { x: 100, y: 200, mapId: 'plaza' }, }); client.on('session_joined', (response) => { expect(response.success).toBe(true); expect(response.sessionId).toBe('test-session-001'); done(); }); client.on('error', (error) => { done(error); }); }); it('应该成功离开会话', (done) => { const sessionId = 'test-session-leave'; // 先加入会话 client.emit('join_session', { type: 'join_session', sessionId, initialPosition: { x: 100, y: 200, mapId: 'plaza' }, }); client.on('session_joined', () => { // 然后离开会话 client.emit('leave_session', { type: 'leave_session', sessionId, }); }); client.on('session_left', (response) => { expect(response.success).toBe(true); expect(response.sessionId).toBe(sessionId); done(); }); client.on('error', (error) => { done(error); }); }); }); describe('位置更新测试', () => { let client: Socket; const sessionId = 'position-test-session'; beforeEach((done) => { const port = app.getHttpServer().address().port; client = io(`http://localhost:${port}/location-broadcast`, { auth: { token: authToken }, transports: ['websocket'], }); client.on('connect', () => { client.emit('join_session', { type: 'join_session', sessionId, initialPosition: { x: 100, y: 200, mapId: 'plaza' }, }); }); client.on('session_joined', () => { done(); }); }); afterEach(() => { if (client) { client.disconnect(); } }); it('应该成功更新位置', (done) => { client.emit('position_update', { type: 'position_update', x: 150, y: 250, mapId: 'plaza', }); client.on('position_update_success', (response) => { expect(response.success).toBe(true); expect(response.position.x).toBe(150); expect(response.position.y).toBe(250); done(); }); client.on('error', (error) => { done(error); }); }); it('应该拒绝无效的位置数据', (done) => { client.emit('position_update', { type: 'position_update', x: 'invalid', y: 250, mapId: 'plaza', }); client.on('error', (error) => { expect(error.message).toContain('Invalid position data'); done(); }); client.on('position_update_success', () => { done(new Error('应该拒绝无效位置数据')); }); }); }); describe('位置广播测试', () => { let client1: Socket; let client2: Socket; const sessionId = 'broadcast-test-session'; beforeEach((done) => { const port = app.getHttpServer().address().port; let connectedClients = 0; client1 = io(`http://localhost:${port}/location-broadcast`, { auth: { token: authToken }, transports: ['websocket'], }); client2 = io(`http://localhost:${port}/location-broadcast`, { auth: { token: authToken }, transports: ['websocket'], }); const checkConnections = () => { connectedClients++; if (connectedClients === 2) { // 两个客户端都加入同一会话 client1.emit('join_session', { type: 'join_session', sessionId, initialPosition: { x: 100, y: 200, mapId: 'plaza' }, }); client2.emit('join_session', { type: 'join_session', sessionId, initialPosition: { x: 300, y: 400, mapId: 'plaza' }, }); } }; let joinedClients = 0; const checkJoins = () => { joinedClients++; if (joinedClients === 2) { done(); } }; client1.on('connect', checkConnections); client2.on('connect', checkConnections); client1.on('session_joined', checkJoins); client2.on('session_joined', checkJoins); }); afterEach(() => { if (client1) client1.disconnect(); if (client2) client2.disconnect(); }); it('应该向同会话的其他用户广播位置更新', (done) => { // client2 监听广播 client2.on('position_broadcast', (broadcast) => { expect(broadcast.userId).toBeDefined(); expect(broadcast.position.x).toBe(150); expect(broadcast.position.y).toBe(250); done(); }); // client1 更新位置 client1.emit('position_update', { type: 'position_update', x: 150, y: 250, mapId: 'plaza', }); }); it('不应该向不同会话的用户广播位置更新', (done) => { const differentSessionId = 'different-session'; let broadcastReceived = false; // client2 加入不同会话 client2.emit('leave_session', { type: 'leave_session', sessionId, }); client2.on('session_left', () => { client2.emit('join_session', { type: 'join_session', sessionId: differentSessionId, initialPosition: { x: 300, y: 400, mapId: 'plaza' }, }); }); client2.on('session_joined', () => { // 监听广播 client2.on('position_broadcast', () => { broadcastReceived = true; }); // client1 更新位置 client1.emit('position_update', { type: 'position_update', x: 150, y: 250, mapId: 'plaza', }); // 等待一段时间确认没有收到广播 const timeoutId = setTimeout(() => { expect(broadcastReceived).toBe(false); done(); }, 1000); }); }); }); describe('心跳机制测试', () => { let client: Socket; beforeEach((done) => { const port = app.getHttpServer().address().port; client = io(`http://localhost:${port}/location-broadcast`, { auth: { token: authToken }, transports: ['websocket'], }); client.on('connect', () => { done(); }); }); afterEach(() => { if (client) { client.disconnect(); } }); it('应该响应心跳消息', (done) => { const timestamp = Date.now(); client.emit('heartbeat', { type: 'heartbeat', timestamp, }); client.on('heartbeat_response', (response) => { expect(response.timestamp).toBe(timestamp); expect(response.serverTime).toBeDefined(); done(); }); client.on('error', (error) => { done(error); }); }); }); describe('错误处理测试', () => { let client: Socket; beforeEach((done) => { const port = app.getHttpServer().address().port; client = io(`http://localhost:${port}/location-broadcast`, { auth: { token: authToken }, transports: ['websocket'], }); client.on('connect', () => { done(); }); }); afterEach(() => { if (client) { client.disconnect(); } }); it('应该处理无效的消息格式', (done) => { client.emit('invalid_message', 'not an object'); client.on('error', (error) => { expect(error.message).toContain('Invalid message format'); done(); }); }); it('应该处理未知的消息类型', (done) => { client.emit('unknown_type', { type: 'unknown_message_type', data: 'test', }); client.on('error', (error) => { expect(error.message).toContain('Unknown message type'); done(); }); }); it('应该处理在未加入会话时的位置更新', (done) => { client.emit('position_update', { type: 'position_update', x: 100, y: 200, mapId: 'plaza', }); client.on('error', (error) => { expect(error.message).toContain('Not in session'); done(); }); }); }); });