/** * 并发用户测试 * * 功能描述: * - 测试系统在多用户并发场景下的正确性和稳定性 * - 验证并发位置更新的数据一致性 * - 确保会话管理在高并发下的可靠性 * - 测试系统的并发处理能力和性能表现 * * 测试场景: * - 大量用户同时连接和断开 * - 多用户同时加入/离开会话 * - 并发位置更新和广播 * - 数据竞争和一致性验证 * - 系统资源使用和清理 * * 验证属性: * - Property 17: Concurrent update handling (并发更新处理) * - Property 5: Position storage consistency (位置存储一致性) * - Property 8: Session-scoped broadcasting (会话范围广播) * - Property 1: User session membership consistency (用户会话成员一致性) * * 最近修改: * - 2026-01-08: 文件重命名 - 修正kebab-case为snake_case命名规范 (修改者: moyin) * * @author moyin * @version 1.0.1 * @since 2026-01-08 * @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 '../../src/business/location_broadcast/location_broadcast.module'; interface TestUser { id: string; client: Socket; sessionId?: string; position?: { x: number; y: number; mapId: string }; connected: boolean; joined: boolean; } describe('并发用户测试', () => { let app: INestApplication; let authToken: string; let port: number; let activeTimers: Set = new Set(); // 全局定时器管理 const createTimer = (callback: () => void, delay: number): NodeJS.Timeout => { const timer = setTimeout(() => { activeTimers.delete(timer); callback(); }, delay); activeTimers.add(timer); return timer; }; const clearTimer = (timer: NodeJS.Timeout): void => { clearTimeout(timer); activeTimers.delete(timer); }; const clearAllTimers = (): void => { activeTimers.forEach(timer => clearTimeout(timer)); activeTimers.clear(); }; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [LocationBroadcastModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); await app.listen(0); port = app.getHttpServer().address().port; authToken = 'test-jwt-token'; }); afterAll(async () => { clearAllTimers(); await app.close(); }); afterEach(() => { clearAllTimers(); }); /** * 创建测试用户连接 */ const createTestUser = (userId: string): Promise => { return new Promise((resolve, reject) => { const client = io(`http://localhost:${port}/location-broadcast`, { auth: { token: authToken }, transports: ['websocket'], }); const user: TestUser = { id: userId, client, connected: false, joined: false, }; let timeoutId: NodeJS.Timeout | null = null; client.on('connect', () => { user.connected = true; if (timeoutId) { clearTimer(timeoutId); timeoutId = null; } resolve(user); }); client.on('connect_error', (error) => { if (timeoutId) { clearTimer(timeoutId); timeoutId = null; } reject(error); }); // 超时保护 timeoutId = createTimer(() => { if (!user.connected) { client.disconnect(); reject(new Error('Connection timeout')); } timeoutId = null; }, 5000); }); }; /** * 用户加入会话 */ const joinSession = (user: TestUser, sessionId: string, initialPosition?: { x: number; y: number; mapId: string }): Promise => { return new Promise((resolve, reject) => { user.sessionId = sessionId; user.position = initialPosition || { x: Math.random() * 1000, y: Math.random() * 1000, mapId: 'plaza' }; let timeoutId: NodeJS.Timeout | null = null; user.client.emit('join_session', { type: 'join_session', sessionId, initialPosition: user.position, }); user.client.on('session_joined', () => { user.joined = true; if (timeoutId) { clearTimer(timeoutId); timeoutId = null; } resolve(); }); user.client.on('error', (error) => { if (timeoutId) { clearTimer(timeoutId); timeoutId = null; } reject(error); }); // 超时保护 timeoutId = createTimer(() => { if (!user.joined) { reject(new Error('Join session timeout')); } timeoutId = null; }, 5000); }); }; /** * 清理用户连接 */ const cleanupUsers = (users: TestUser[]) => { users.forEach(user => { if (user.client && user.client.connected) { user.client.disconnect(); } }); }; describe('大规模并发连接测试', () => { it('应该支持100个用户同时连接', async () => { const userCount = 100; const users: TestUser[] = []; const startTime = Date.now(); try { // 并发创建用户连接 const connectionPromises = Array.from({ length: userCount }, (_, i) => createTestUser(`concurrent-user-${i}`) ); const connectedUsers = await Promise.all(connectionPromises); users.push(...connectedUsers); const connectionTime = Date.now() - startTime; console.log(`${userCount} users connected in ${connectionTime}ms`); console.log(`Average connection time: ${(connectionTime / userCount).toFixed(2)}ms per user`); // 验证所有用户都已连接 expect(users.length).toBe(userCount); users.forEach(user => { expect(user.connected).toBe(true); expect(user.client.connected).toBe(true); }); // 连接时间应该在合理范围内(每个用户平均不超过100ms) expect(connectionTime / userCount).toBeLessThan(100); } finally { cleanupUsers(users); } }, 30000); it('应该支持用户快速连接和断开', async () => { const userCount = 50; const users: TestUser[] = []; try { // 快速连接 for (let i = 0; i < userCount; i++) { const user = await createTestUser(`rapid-user-${i}`); users.push(user); // 立即断开一半用户 if (i % 2 === 0) { user.client.disconnect(); user.connected = false; } } // 等待系统处理断开连接 await new Promise(resolve => setTimeout(resolve, 1000)); // 验证剩余用户仍然连接 const connectedUsers = users.filter(user => user.connected); expect(connectedUsers.length).toBe(userCount / 2); connectedUsers.forEach(user => { expect(user.client.connected).toBe(true); }); } finally { cleanupUsers(users); } }, 20000); }); describe('并发会话管理测试', () => { it('应该支持多用户同时加入同一会话', async () => { const userCount = 50; const sessionId = 'concurrent-session-001'; const users: TestUser[] = []; try { // 创建用户连接 for (let i = 0; i < userCount; i++) { const user = await createTestUser(`session-user-${i}`); users.push(user); } const startTime = Date.now(); // 并发加入会话 const joinPromises = users.map(user => joinSession(user, sessionId, { x: Math.random() * 1000, y: Math.random() * 1000, mapId: 'plaza' }) ); await Promise.all(joinPromises); const joinTime = Date.now() - startTime; console.log(`${userCount} users joined session in ${joinTime}ms`); // 验证所有用户都成功加入会话 users.forEach(user => { expect(user.joined).toBe(true); expect(user.sessionId).toBe(sessionId); }); // 加入时间应该在合理范围内 expect(joinTime).toBeLessThan(10000); } finally { cleanupUsers(users); } }, 30000); }); });