test:添加位置广播系统端到端测试

- 添加并发用户测试场景
- 实现数据库恢复集成测试
- 重命名登录测试文件以符合命名规范
This commit is contained in:
moyin
2026-01-08 23:06:11 +08:00
parent c31cbe559d
commit 71bc317c57
7 changed files with 2580 additions and 0 deletions

View File

@@ -0,0 +1,306 @@
/**
* 并发用户测试
*
* 功能描述:
* - 测试系统在多用户并发场景下的正确性和稳定性
* - 验证并发位置更新的数据一致性
* - 确保会话管理在高并发下的可靠性
* - 测试系统的并发处理能力和性能表现
*
* 测试场景:
* - 大量用户同时连接和断开
* - 多用户同时加入/离开会话
* - 并发位置更新和广播
* - 数据竞争和一致性验证
* - 系统资源使用和清理
*
* 验证属性:
* - 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<NodeJS.Timeout> = 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<TestUser> => {
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<void> => {
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);
});
});