forked from datawhale/whale-town-end
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
448 lines
13 KiB
TypeScript
448 lines
13 KiB
TypeScript
/**
|
||
* 聊天消息端到端集成测试
|
||
*
|
||
* 功能描述:
|
||
* - 测试从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>(ZulipService);
|
||
zulipClientPool = moduleFixture.get<ZulipClientPoolService>(ZulipClientPoolService);
|
||
zulipClient = moduleFixture.get<ZulipClientService>(ZulipClientService);
|
||
sessionManager = moduleFixture.get<SessionManagerService>(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);
|
||
});
|
||
}); |