WARNING: This commit contains code with significant issues that need immediate attention: 1. Type Safety Issues: - Unused import ZulipAccountsService causing compilation warnings - Implicit 'any' type in formatZulipAccount method parameter - Type inconsistencies in service injections 2. Service Integration Problems: - Inconsistent service interface usage - Missing proper type definitions for injected services - Potential runtime errors due to type mismatches 3. Code Quality Issues: - Violation of TypeScript strict mode requirements - Inconsistent error handling patterns - Missing proper interface implementations Files affected: - src/business/admin/database_management.service.ts (main issue) - Multiple test files and service implementations - Configuration and documentation updates Next steps required: 1. Fix TypeScript compilation errors 2. Implement proper type safety 3. Resolve service injection inconsistencies 4. Add comprehensive error handling 5. Update tests to match new implementations Impact: High - affects admin functionality and system stability Priority: Urgent - requires immediate review and fixes Author: moyin Date: 2026-01-10
358 lines
11 KiB
TypeScript
358 lines
11 KiB
TypeScript
/**
|
||
* Zulip聊天性能测试
|
||
*
|
||
* 功能描述:
|
||
* - 测试优化后聊天架构的性能表现
|
||
* - 验证游戏内实时广播 + Zulip异步同步的效果
|
||
* - 测试高并发场景下的系统稳定性
|
||
*
|
||
* 测试场景:
|
||
* - 单用户消息发送性能
|
||
* - 多用户并发聊天性能
|
||
* - 大量消息批量处理性能
|
||
* - 内存使用和资源清理
|
||
*
|
||
* @author moyin
|
||
* @version 1.0.0
|
||
* @since 2026-01-10
|
||
*/
|
||
|
||
import { Test, TestingModule } from '@nestjs/testing';
|
||
import { ZulipService } from '../../../src/business/zulip/zulip.service';
|
||
import { ZulipClientPoolService } from '../../../src/core/zulip_core/services/zulip_client_pool.service';
|
||
import { SessionManagerService } from '../../../src/business/zulip/services/session_manager.service';
|
||
import { MessageFilterService } from '../../../src/business/zulip/services/message_filter.service';
|
||
|
||
// 模拟WebSocket网关
|
||
class MockWebSocketGateway {
|
||
private sentMessages: Array<{ socketId: string; data: any }> = [];
|
||
private broadcastMessages: Array<{ mapId: string; data: any }> = [];
|
||
|
||
sendToPlayer(socketId: string, data: any): void {
|
||
this.sentMessages.push({ socketId, data });
|
||
}
|
||
|
||
broadcastToMap(mapId: string, data: any, excludeId?: string): void {
|
||
this.broadcastMessages.push({ mapId, data });
|
||
}
|
||
|
||
getSentMessages() { return this.sentMessages; }
|
||
getBroadcastMessages() { return this.broadcastMessages; }
|
||
clearMessages() {
|
||
this.sentMessages = [];
|
||
this.broadcastMessages = [];
|
||
}
|
||
}
|
||
|
||
describe('Zulip聊天性能测试', () => {
|
||
let zulipService: ZulipService;
|
||
let sessionManager: SessionManagerService;
|
||
let mockWebSocketGateway: MockWebSocketGateway;
|
||
let mockZulipClientPool: any;
|
||
|
||
beforeAll(async () => {
|
||
// 创建模拟服务
|
||
mockZulipClientPool = {
|
||
sendMessage: jest.fn().mockResolvedValue({
|
||
success: true,
|
||
messageId: 'zulip-msg-123',
|
||
}),
|
||
createUserClient: jest.fn(),
|
||
destroyUserClient: jest.fn(),
|
||
};
|
||
|
||
const mockSessionManager = {
|
||
getSession: jest.fn().mockResolvedValue({
|
||
sessionId: 'test-session',
|
||
userId: 'user-123',
|
||
username: 'TestPlayer',
|
||
currentMap: 'whale_port',
|
||
position: { x: 100, y: 200 },
|
||
}),
|
||
injectContext: jest.fn().mockResolvedValue({
|
||
stream: 'Whale Port',
|
||
topic: 'Town Square Chat',
|
||
}),
|
||
getSocketsInMap: jest.fn().mockResolvedValue(['socket-1', 'socket-2', 'socket-3']),
|
||
createSession: jest.fn(),
|
||
destroySession: jest.fn(),
|
||
updatePlayerPosition: jest.fn().mockResolvedValue(true),
|
||
};
|
||
|
||
const mockMessageFilter = {
|
||
validateMessage: jest.fn().mockResolvedValue({
|
||
allowed: true,
|
||
filteredContent: null,
|
||
}),
|
||
};
|
||
|
||
const module: TestingModule = await Test.createTestingModule({
|
||
providers: [
|
||
ZulipService,
|
||
{
|
||
provide: 'ZULIP_CLIENT_POOL_SERVICE',
|
||
useValue: mockZulipClientPool,
|
||
},
|
||
{
|
||
provide: SessionManagerService,
|
||
useValue: mockSessionManager,
|
||
},
|
||
{
|
||
provide: MessageFilterService,
|
||
useValue: mockMessageFilter,
|
||
},
|
||
{
|
||
provide: 'API_KEY_SECURITY_SERVICE',
|
||
useValue: {
|
||
getApiKey: jest.fn().mockResolvedValue({
|
||
success: true,
|
||
apiKey: 'test-api-key',
|
||
}),
|
||
},
|
||
},
|
||
{
|
||
provide: 'LoginCoreService',
|
||
useValue: {
|
||
verifyToken: jest.fn().mockResolvedValue({
|
||
sub: 'user-123',
|
||
username: 'TestPlayer',
|
||
email: 'test@example.com',
|
||
}),
|
||
},
|
||
},
|
||
],
|
||
}).compile();
|
||
|
||
zulipService = module.get<ZulipService>(ZulipService);
|
||
sessionManager = module.get<SessionManagerService>(SessionManagerService);
|
||
|
||
// 设置WebSocket网关
|
||
mockWebSocketGateway = new MockWebSocketGateway();
|
||
zulipService.setWebSocketGateway(mockWebSocketGateway as any);
|
||
});
|
||
|
||
beforeEach(() => {
|
||
jest.clearAllMocks();
|
||
mockWebSocketGateway.clearMessages();
|
||
});
|
||
|
||
describe('单用户消息发送性能', () => {
|
||
it('应该在50ms内完成游戏内广播', async () => {
|
||
const startTime = Date.now();
|
||
|
||
const result = await zulipService.sendChatMessage({
|
||
socketId: 'test-socket',
|
||
content: 'Performance test message',
|
||
scope: 'local',
|
||
});
|
||
|
||
const duration = Date.now() - startTime;
|
||
|
||
expect(result.success).toBe(true);
|
||
expect(duration).toBeLessThan(50); // 游戏内广播应该在50ms内完成
|
||
|
||
console.log(`游戏内广播耗时: ${duration}ms`);
|
||
});
|
||
|
||
it('应该异步处理Zulip同步,不阻塞游戏聊天', async () => {
|
||
// 模拟Zulip同步延迟
|
||
mockZulipClientPool.sendMessage.mockImplementation(() =>
|
||
new Promise(resolve => setTimeout(() => resolve({
|
||
success: true,
|
||
messageId: 'delayed-msg',
|
||
}), 200))
|
||
);
|
||
|
||
const startTime = Date.now();
|
||
|
||
const result = await zulipService.sendChatMessage({
|
||
socketId: 'test-socket',
|
||
content: 'Async test message',
|
||
scope: 'local',
|
||
});
|
||
|
||
const duration = Date.now() - startTime;
|
||
|
||
expect(result.success).toBe(true);
|
||
expect(duration).toBeLessThan(100); // 不应该等待Zulip同步完成
|
||
|
||
console.log(`异步处理耗时: ${duration}ms`);
|
||
});
|
||
});
|
||
|
||
describe('多用户并发聊天性能', () => {
|
||
it('应该处理50个并发消息', async () => {
|
||
const messageCount = 50;
|
||
const startTime = Date.now();
|
||
|
||
const promises = Array.from({ length: messageCount }, (_, i) =>
|
||
zulipService.sendChatMessage({
|
||
socketId: `socket-${i}`,
|
||
content: `Concurrent message ${i}`,
|
||
scope: 'local',
|
||
})
|
||
);
|
||
|
||
const results = await Promise.all(promises);
|
||
const duration = Date.now() - startTime;
|
||
|
||
// 验证所有消息都成功处理
|
||
expect(results).toHaveLength(messageCount);
|
||
results.forEach(result => {
|
||
expect(result.success).toBe(true);
|
||
});
|
||
|
||
const avgTimePerMessage = duration / messageCount;
|
||
console.log(`处理${messageCount}条并发消息耗时: ${duration}ms, 平均每条: ${avgTimePerMessage.toFixed(2)}ms`);
|
||
|
||
// 期望平均每条消息处理时间不超过20ms
|
||
expect(avgTimePerMessage).toBeLessThan(20);
|
||
}, 10000);
|
||
|
||
it('应该正确广播给地图内的所有玩家', async () => {
|
||
await zulipService.sendChatMessage({
|
||
socketId: 'sender-socket',
|
||
content: 'Broadcast test message',
|
||
scope: 'local',
|
||
});
|
||
|
||
// 验证广播消息
|
||
const broadcastMessages = mockWebSocketGateway.getBroadcastMessages();
|
||
expect(broadcastMessages).toHaveLength(1);
|
||
|
||
const broadcastMessage = broadcastMessages[0];
|
||
expect(broadcastMessage.mapId).toBe('whale_port');
|
||
expect(broadcastMessage.data.t).toBe('chat_render');
|
||
expect(broadcastMessage.data.txt).toBe('Broadcast test message');
|
||
});
|
||
});
|
||
|
||
describe('批量消息处理性能', () => {
|
||
it('应该高效处理大量消息', async () => {
|
||
const batchSize = 100;
|
||
const startTime = Date.now();
|
||
|
||
// 创建批量消息
|
||
const batchPromises = Array.from({ length: batchSize }, (_, i) =>
|
||
zulipService.sendChatMessage({
|
||
socketId: 'batch-socket',
|
||
content: `Batch message ${i}`,
|
||
scope: 'local',
|
||
})
|
||
);
|
||
|
||
const results = await Promise.all(batchPromises);
|
||
const duration = Date.now() - startTime;
|
||
|
||
// 验证处理结果
|
||
expect(results).toHaveLength(batchSize);
|
||
results.forEach((result, index) => {
|
||
expect(result.success).toBe(true);
|
||
expect(result.messageId).toBeDefined();
|
||
});
|
||
|
||
const throughput = (batchSize / duration) * 1000; // 每秒处理的消息数
|
||
console.log(`批量处理${batchSize}条消息耗时: ${duration}ms, 吞吐量: ${throughput.toFixed(2)} msg/s`);
|
||
|
||
// 期望吞吐量至少达到500 msg/s
|
||
expect(throughput).toBeGreaterThan(500);
|
||
}, 15000);
|
||
});
|
||
|
||
describe('内存使用和资源清理', () => {
|
||
it('应该正确清理会话资源', async () => {
|
||
// 创建多个会话
|
||
const sessionCount = 10;
|
||
const sessionIds = Array.from({ length: sessionCount }, (_, i) => `session-${i}`);
|
||
|
||
// 模拟会话创建
|
||
for (const sessionId of sessionIds) {
|
||
await zulipService.handlePlayerLogin({
|
||
socketId: sessionId,
|
||
token: 'valid-jwt-token',
|
||
});
|
||
}
|
||
|
||
// 清理所有会话
|
||
for (const sessionId of sessionIds) {
|
||
await zulipService.handlePlayerLogout(sessionId);
|
||
}
|
||
|
||
// 验证资源清理
|
||
expect(mockZulipClientPool.destroyUserClient).toHaveBeenCalledTimes(sessionCount);
|
||
});
|
||
|
||
it('应该处理内存压力测试', async () => {
|
||
const initialMemory = process.memoryUsage();
|
||
|
||
// 创建大量临时对象
|
||
const largeDataSet = Array.from({ length: 1000 }, (_, i) => ({
|
||
id: i,
|
||
data: 'x'.repeat(1000), // 1KB per object
|
||
timestamp: new Date(),
|
||
}));
|
||
|
||
// 处理大量消息
|
||
const promises = largeDataSet.map((item, i) =>
|
||
zulipService.sendChatMessage({
|
||
socketId: `memory-test-${i}`,
|
||
content: `Memory test ${item.id}: ${item.data.substring(0, 50)}...`,
|
||
scope: 'local',
|
||
})
|
||
);
|
||
|
||
await Promise.all(promises);
|
||
|
||
// 强制垃圾回收(如果可用)
|
||
if (global.gc) {
|
||
global.gc();
|
||
}
|
||
|
||
const finalMemory = process.memoryUsage();
|
||
const memoryIncrease = finalMemory.heapUsed - initialMemory.heapUsed;
|
||
|
||
console.log(`内存使用增加: ${(memoryIncrease / 1024 / 1024).toFixed(2)} MB`);
|
||
|
||
// 期望内存增加不超过50MB
|
||
expect(memoryIncrease).toBeLessThan(50 * 1024 * 1024);
|
||
}, 20000);
|
||
});
|
||
|
||
describe('错误处理性能', () => {
|
||
it('应该快速处理无效会话', async () => {
|
||
const startTime = Date.now();
|
||
|
||
const result = await zulipService.sendChatMessage({
|
||
socketId: 'invalid-socket',
|
||
content: 'This should fail quickly',
|
||
scope: 'local',
|
||
});
|
||
|
||
const duration = Date.now() - startTime;
|
||
|
||
expect(result.success).toBe(false);
|
||
expect(result.error).toContain('会话不存在');
|
||
expect(duration).toBeLessThan(10); // 错误处理应该很快
|
||
|
||
console.log(`错误处理耗时: ${duration}ms`);
|
||
});
|
||
|
||
it('应该处理Zulip服务异常而不影响游戏聊天', async () => {
|
||
// 模拟Zulip服务异常
|
||
mockZulipClientPool.sendMessage.mockRejectedValue(new Error('Zulip service unavailable'));
|
||
|
||
const result = await zulipService.sendChatMessage({
|
||
socketId: 'test-socket',
|
||
content: 'Message during Zulip outage',
|
||
scope: 'local',
|
||
});
|
||
|
||
// 游戏内聊天应该仍然成功
|
||
expect(result.success).toBe(true);
|
||
|
||
// 验证游戏内广播仍然工作
|
||
const broadcastMessages = mockWebSocketGateway.getBroadcastMessages();
|
||
expect(broadcastMessages).toHaveLength(1);
|
||
});
|
||
});
|
||
}); |