/** * 聊天消息端到端集成测试 * * 功能描述: * - 测试从WebSocket接收消息到Zulip服务器发送的完整流程 * - 验证消息路由、过滤、认证等中间环节 * - 测试真实的网络请求和响应处理 * * 测试范围: * - WebSocket → ZulipService → ZulipClientPool → ZulipClient → Zulip API * * @author moyin * @version 1.0.0 * @since 2026-01-10 */ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import { ZulipService } from '../../src/business/zulip/zulip.service'; import { ZulipClientPoolService } from '../../src/core/zulip_core/services/zulip_client_pool.service'; import { ZulipClientService, ZulipClientInstance } from '../../src/core/zulip_core/services/zulip_client.service'; import { SessionManagerService } from '../../src/business/zulip/services/session_manager.service'; import { AppModule } from '../../src/app.module'; describe('ChatMessage E2E Integration', () => { let app: INestApplication; let zulipService: ZulipService; let zulipClientPool: ZulipClientPoolService; let zulipClient: ZulipClientService; let sessionManager: SessionManagerService; // 模拟的Zulip客户端 let mockZulipSdkClient: any; // 测试数据 const testUserId = 'test-user-12345'; const testSocketId = 'ws_test_socket_123'; const testConfig = { username: 'test-bot@example.com', apiKey: 'test-api-key-abcdef', realm: 'https://test-zulip.example.com', }; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); // 获取服务实例 zulipService = moduleFixture.get(ZulipService); zulipClientPool = moduleFixture.get(ZulipClientPoolService); zulipClient = moduleFixture.get(ZulipClientService); sessionManager = moduleFixture.get(SessionManagerService); await app.init(); }); afterAll(async () => { await app.close(); }); beforeEach(() => { // 创建模拟的zulip-js客户端 mockZulipSdkClient = { config: testConfig, users: { me: { getProfile: jest.fn().mockResolvedValue({ result: 'success', email: testConfig.username, full_name: 'Test Bot', user_id: 123, }), }, }, messages: { send: jest.fn().mockResolvedValue({ result: 'success', id: 12345, }), }, queues: { register: jest.fn().mockResolvedValue({ result: 'success', queue_id: 'test-queue-123', last_event_id: 0, }), deregister: jest.fn().mockResolvedValue({ result: 'success', }), }, events: { retrieve: jest.fn().mockResolvedValue({ result: 'success', events: [], }), }, }; // Mock zulip-js模块 jest.spyOn(zulipClient as any, 'loadZulipModule').mockResolvedValue(() => mockZulipSdkClient); }); afterEach(() => { jest.clearAllMocks(); }); describe('完整的聊天消息流程', () => { it('应该成功处理从登录到消息发送的完整流程', async () => { // 1. 模拟用户登录 const loginResult = await zulipService.handlePlayerLogin({ socketId: testSocketId, token: 'valid-jwt-token', // 这里需要有效的JWT token }); // 验证登录成功(可能需要根据实际JWT验证逻辑调整) if (loginResult.success) { expect(loginResult.userId).toBeDefined(); expect(loginResult.sessionId).toBeDefined(); // 2. 发送聊天消息 const chatResult = await zulipService.sendChatMessage({ socketId: testSocketId, content: 'Hello from E2E test!', scope: 'local', }); // 验证消息发送成功 expect(chatResult.success).toBe(true); expect(chatResult.messageId).toBeDefined(); // 验证Zulip API被正确调用 expect(mockZulipSdkClient.messages.send).toHaveBeenCalledWith({ type: 'stream', to: expect.any(String), // Stream名称 subject: expect.any(String), // Topic名称 content: 'Hello from E2E test!', }); } else { // 如果登录失败,跳过测试或使用模拟会话 console.warn('登录失败,使用模拟会话进行测试'); // 创建模拟会话 await sessionManager.createSession( testSocketId, testUserId, 'test-queue-123', 'TestUser', 'whale_port', { x: 0, y: 0 } ); // 发送消息 const chatResult = await zulipService.sendChatMessage({ socketId: testSocketId, content: 'Hello from E2E test with mock session!', scope: 'local', }); expect(chatResult.success).toBe(true); } }, 15000); it('应该正确处理不同消息范围的路由', async () => { // 创建测试会话 await sessionManager.createSession( testSocketId, testUserId, 'test-queue-123', 'TestUser', 'whale_port', { x: 0, y: 0 } ); // 测试本地消息 const localResult = await zulipService.sendChatMessage({ socketId: testSocketId, content: 'Local message test', scope: 'local', }); expect(localResult.success).toBe(true); // 验证消息被发送到正确的Stream const localCall = mockZulipSdkClient.messages.send.mock.calls.find( (call: any) => call[0].content === 'Local message test' ); expect(localCall).toBeDefined(); expect(localCall[0].to).toBe('Whale Port'); // 应该路由到地图对应的Stream // 测试全局消息 const globalResult = await zulipService.sendChatMessage({ socketId: testSocketId, content: 'Global message test', scope: 'global', }); expect(globalResult.success).toBe(true); // 验证全局消息路由 const globalCall = mockZulipSdkClient.messages.send.mock.calls.find( (call: any) => call[0].content === 'Global message test' ); expect(globalCall).toBeDefined(); }); it('应该处理消息过滤和验证', async () => { // 创建测试会话 await sessionManager.createSession( testSocketId, testUserId, 'test-queue-123', 'TestUser', 'whale_port', { x: 0, y: 0 } ); // 测试正常消息 const normalResult = await zulipService.sendChatMessage({ socketId: testSocketId, content: 'This is a normal message', scope: 'local', }); expect(normalResult.success).toBe(true); // 测试空消息 const emptyResult = await zulipService.sendChatMessage({ socketId: testSocketId, content: '', scope: 'local', }); expect(emptyResult.success).toBe(false); // 测试过长消息 const longMessage = 'A'.repeat(2000); // 假设限制是1000字符 const longResult = await zulipService.sendChatMessage({ socketId: testSocketId, content: longMessage, scope: 'local', }); // 根据实际过滤规则验证结果 console.log('Long message result:', longResult); }); it('应该处理Zulip API错误', async () => { // 创建测试会话 await sessionManager.createSession( testSocketId, testUserId, 'test-queue-123', 'TestUser', 'whale_port', { x: 0, y: 0 } ); // 模拟Zulip API错误 mockZulipSdkClient.messages.send.mockResolvedValueOnce({ result: 'error', msg: 'Stream does not exist', code: 'STREAM_NOT_FOUND', }); const result = await zulipService.sendChatMessage({ socketId: testSocketId, content: 'This message will fail', scope: 'local', }); // 验证错误处理(根据实际业务逻辑,可能返回成功但记录错误) expect(result).toBeDefined(); }); it('应该处理网络异常', async () => { // 创建测试会话 await sessionManager.createSession( testSocketId, testUserId, 'test-queue-123', 'TestUser', 'whale_port', { x: 0, y: 0 } ); // 模拟网络异常 mockZulipSdkClient.messages.send.mockRejectedValueOnce(new Error('Network timeout')); const result = await zulipService.sendChatMessage({ socketId: testSocketId, content: 'This will timeout', scope: 'local', }); // 验证网络异常处理 expect(result).toBeDefined(); }); }); describe('客户端池管理', () => { it('应该正确管理用户的Zulip客户端', async () => { // 创建用户客户端 const clientInstance = await zulipClientPool.createUserClient(testUserId, testConfig); expect(clientInstance).toBeDefined(); expect(clientInstance.userId).toBe(testUserId); expect(clientInstance.isValid).toBe(true); // 验证客户端可以发送消息 const sendResult = await zulipClientPool.sendMessage( testUserId, 'test-stream', 'test-topic', 'Test message from pool' ); expect(sendResult.success).toBe(true); expect(mockZulipSdkClient.messages.send).toHaveBeenCalledWith({ type: 'stream', to: 'test-stream', subject: 'test-topic', content: 'Test message from pool', }); // 清理客户端 await zulipClientPool.destroyUserClient(testUserId); }); it('应该处理多用户并发', async () => { const userIds = ['user1', 'user2', 'user3']; const clients: ZulipClientInstance[] = []; // 创建多个用户客户端 for (const userId of userIds) { const client = await zulipClientPool.createUserClient(userId, { ...testConfig, username: `${userId}@example.com`, }); clients.push(client); } // 并发发送消息 const sendPromises = userIds.map(userId => zulipClientPool.sendMessage( userId, 'concurrent-stream', 'concurrent-topic', `Message from ${userId}` ) ); const results = await Promise.all(sendPromises); // 验证所有消息都成功发送 results.forEach(result => { expect(result.success).toBe(true); }); // 清理所有客户端 for (const userId of userIds) { await zulipClientPool.destroyUserClient(userId); } }); }); describe('事件队列集成', () => { it('应该正确处理事件队列生命周期', async () => { // 创建客户端 const clientInstance = await zulipClientPool.createUserClient(testUserId, testConfig); // 验证队列已注册 expect(clientInstance.queueId).toBeDefined(); expect(mockZulipSdkClient.queues.register).toHaveBeenCalled(); // 模拟接收事件 const mockEvents = [ { id: 1, type: 'message', message: { id: 98765, sender_email: 'other-user@example.com', content: 'Hello from other user', stream_id: 1, subject: 'Test Topic', }, }, ]; mockZulipSdkClient.events.retrieve.mockResolvedValueOnce({ result: 'success', events: mockEvents, }); // 获取事件 const userClient: ZulipClientInstance | null = await zulipClientPool.getUserClient(testUserId); if (userClient) { const eventsResult = await zulipClient.getEvents(userClient, true); expect(eventsResult.success).toBe(true); expect(eventsResult.events).toEqual(mockEvents); } // 清理 await zulipClientPool.destroyUserClient(testUserId); expect(mockZulipSdkClient.queues.deregister).toHaveBeenCalled(); }); }); describe('性能测试', () => { it('应该在高负载下保持性能', async () => { const messageCount = 50; const startTime = Date.now(); // 创建测试会话 await sessionManager.createSession( testSocketId, testUserId, 'test-queue-123', 'TestUser', 'whale_port', { x: 0, y: 0 } ); // 发送大量消息 const promises = Array.from({ length: messageCount }, (_, i) => zulipService.sendChatMessage({ socketId: testSocketId, content: `Performance test message ${i}`, scope: 'local', }) ); const results = await Promise.all(promises); const endTime = Date.now(); const duration = endTime - startTime; // 验证所有消息处理完成 expect(results).toHaveLength(messageCount); // 性能检查 const avgTimePerMessage = duration / messageCount; console.log(`处理${messageCount}条消息耗时: ${duration}ms, 平均每条: ${avgTimePerMessage.toFixed(2)}ms`); // 期望平均每条消息处理时间不超过100ms expect(avgTimePerMessage).toBeLessThan(100); }, 30000); }); });