forked from datawhale/whale-town-end
test:添加位置广播系统端到端测试
- 添加并发用户测试场景 - 实现数据库恢复集成测试 - 重命名登录测试文件以符合命名规范
This commit is contained in:
432
test/location_broadcast/location_broadcast.e2e_spec.ts
Normal file
432
test/location_broadcast/location_broadcast.e2e_spec.ts
Normal file
@@ -0,0 +1,432 @@
|
||||
/**
|
||||
* 位置广播端到端测试
|
||||
*
|
||||
* 功能描述:
|
||||
* - 测试位置广播系统的完整功能
|
||||
* - 验证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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user