forked from datawhale/whale-town-end
CRITICAL ISSUES: Database management service with major problems
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
This commit is contained in:
48
test/zulip_integration/README.md
Normal file
48
test/zulip_integration/README.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Zulip集成测试
|
||||
|
||||
## 测试结构
|
||||
|
||||
### 单元测试 (unit/)
|
||||
- `zulip_client.service.spec.ts` - ZulipClientService单元测试
|
||||
- `zulip_client_pool.service.spec.ts` - ZulipClientPoolService单元测试
|
||||
- `zulip.service.spec.ts` - ZulipService单元测试
|
||||
|
||||
### 集成测试 (integration/)
|
||||
- `real_zulip_api.spec.ts` - 真实Zulip API集成测试
|
||||
- `chat_message_integration.spec.ts` - 聊天消息集成测试
|
||||
|
||||
### 端到端测试 (e2e/)
|
||||
- `chat_message_e2e.spec.ts` - 完整聊天流程端到端测试
|
||||
|
||||
### 性能测试 (performance/)
|
||||
- `optimized_chat_performance.spec.ts` - 优化架构性能测试
|
||||
- `load_test.spec.ts` - 负载测试
|
||||
|
||||
### 工具脚本 (tools/)
|
||||
- `simple_connection_test.ts` - 简单连接测试工具
|
||||
- `list_streams.ts` - Stream列表查询工具
|
||||
- `chat_simulation.ts` - 聊天模拟工具
|
||||
|
||||
## 运行测试
|
||||
|
||||
```bash
|
||||
# 运行所有测试
|
||||
npm run test:zulip
|
||||
|
||||
# 运行单元测试
|
||||
npm run test:zulip:unit
|
||||
|
||||
# 运行集成测试(需要真实Zulip配置)
|
||||
npm run test:zulip:integration
|
||||
|
||||
# 运行性能测试
|
||||
npm run test:zulip:performance
|
||||
```
|
||||
|
||||
## 配置要求
|
||||
|
||||
集成测试需要以下环境变量:
|
||||
- `ZULIP_SERVER_URL` - Zulip服务器地址
|
||||
- `ZULIP_BOT_EMAIL` - Bot邮箱
|
||||
- `ZULIP_BOT_API_KEY` - Bot API Key
|
||||
- `ZULIP_TEST_STREAM` - 测试Stream名称
|
||||
448
test/zulip_integration/chat_message_e2e.spec.ts
Normal file
448
test/zulip_integration/chat_message_e2e.spec.ts
Normal file
@@ -0,0 +1,448 @@
|
||||
/**
|
||||
* 聊天消息端到端集成测试
|
||||
*
|
||||
* 功能描述:
|
||||
* - 测试从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);
|
||||
});
|
||||
});
|
||||
358
test/zulip_integration/performance/chat_performance.spec.ts
Normal file
358
test/zulip_integration/performance/chat_performance.spec.ts
Normal file
@@ -0,0 +1,358 @@
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
393
test/zulip_integration/real_zulip_api.spec.ts
Normal file
393
test/zulip_integration/real_zulip_api.spec.ts
Normal file
@@ -0,0 +1,393 @@
|
||||
/**
|
||||
* 真实Zulip API测试
|
||||
*
|
||||
* 功能描述:
|
||||
* - 测试与真实Zulip服务器的HTTP通信
|
||||
* - 验证API请求格式、认证和响应处理
|
||||
* - 需要真实的Zulip服务器配置才能运行
|
||||
*
|
||||
* 注意:
|
||||
* - 这些测试需要设置环境变量:ZULIP_SERVER_URL, ZULIP_BOT_EMAIL, ZULIP_BOT_API_KEY
|
||||
* - 如果没有配置,测试将被跳过
|
||||
* - 测试会在真实服务器上创建消息,请谨慎使用
|
||||
*
|
||||
* @author moyin
|
||||
* @version 1.0.0
|
||||
* @since 2026-01-10
|
||||
*/
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ZulipClientService, ZulipClientConfig, SendMessageResult } from '../../src/core/zulip_core/services/zulip_client.service';
|
||||
|
||||
// 测试配置
|
||||
const REAL_ZULIP_CONFIG = {
|
||||
serverUrl: process.env.ZULIP_SERVER_URL || '',
|
||||
botEmail: process.env.ZULIP_BOT_EMAIL || '',
|
||||
botApiKey: process.env.ZULIP_BOT_API_KEY || '',
|
||||
testStream: process.env.ZULIP_TEST_STREAM || 'test-stream',
|
||||
testTopic: process.env.ZULIP_TEST_TOPIC || 'API Test',
|
||||
};
|
||||
|
||||
// 检查是否有真实配置
|
||||
const hasRealConfig: boolean = !!(REAL_ZULIP_CONFIG.serverUrl &&
|
||||
REAL_ZULIP_CONFIG.botEmail &&
|
||||
REAL_ZULIP_CONFIG.botApiKey);
|
||||
|
||||
describe('Real Zulip API Integration', () => {
|
||||
let service: ZulipClientService;
|
||||
let clientConfig: ZulipClientConfig;
|
||||
|
||||
beforeAll(async () => {
|
||||
if (!hasRealConfig) {
|
||||
console.warn('跳过真实Zulip API测试:缺少环境变量配置');
|
||||
console.warn('需要设置: ZULIP_SERVER_URL, ZULIP_BOT_EMAIL, ZULIP_BOT_API_KEY');
|
||||
return;
|
||||
}
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [ZulipClientService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<ZulipClientService>(ZulipClientService);
|
||||
|
||||
clientConfig = {
|
||||
username: REAL_ZULIP_CONFIG.botEmail,
|
||||
apiKey: REAL_ZULIP_CONFIG.botApiKey,
|
||||
realm: REAL_ZULIP_CONFIG.serverUrl,
|
||||
};
|
||||
});
|
||||
|
||||
// 如果没有真实配置,跳过所有测试
|
||||
const testIf = (condition: boolean) => condition ? it : it.skip;
|
||||
|
||||
describe('API连接测试', () => {
|
||||
testIf(hasRealConfig)('应该能够连接到Zulip服务器', async () => {
|
||||
const clientInstance = await service.createClient('test-user', clientConfig);
|
||||
|
||||
expect(clientInstance).toBeDefined();
|
||||
expect(clientInstance.isValid).toBe(true);
|
||||
expect(clientInstance.config.realm).toBe(REAL_ZULIP_CONFIG.serverUrl);
|
||||
|
||||
// 清理
|
||||
await service.destroyClient(clientInstance);
|
||||
}, 10000);
|
||||
|
||||
testIf(hasRealConfig)('应该能够验证API Key', async () => {
|
||||
const clientInstance = await service.createClient('test-user', clientConfig);
|
||||
|
||||
const isValid = await service.validateApiKey(clientInstance);
|
||||
expect(isValid).toBe(true);
|
||||
|
||||
await service.destroyClient(clientInstance);
|
||||
}, 10000);
|
||||
|
||||
testIf(hasRealConfig)('应该拒绝无效的API Key', async () => {
|
||||
const invalidConfig = {
|
||||
...clientConfig,
|
||||
apiKey: 'invalid-api-key-12345',
|
||||
};
|
||||
|
||||
await expect(service.createClient('test-user', invalidConfig))
|
||||
.rejects.toThrow();
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
describe('消息发送测试', () => {
|
||||
let clientInstance: any;
|
||||
|
||||
beforeEach(async () => {
|
||||
if (!hasRealConfig) return;
|
||||
|
||||
clientInstance = await service.createClient('test-user', clientConfig);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (clientInstance) {
|
||||
await service.destroyClient(clientInstance);
|
||||
}
|
||||
});
|
||||
|
||||
testIf(hasRealConfig)('应该能够发送消息到Zulip', async () => {
|
||||
const testMessage = `Test message from automated test - ${new Date().toISOString()}`;
|
||||
|
||||
const result = await service.sendMessage(
|
||||
clientInstance,
|
||||
REAL_ZULIP_CONFIG.testStream,
|
||||
REAL_ZULIP_CONFIG.testTopic,
|
||||
testMessage
|
||||
);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.messageId).toBeDefined();
|
||||
expect(typeof result.messageId).toBe('number');
|
||||
|
||||
console.log(`消息发送成功,ID: ${result.messageId}`);
|
||||
}, 15000);
|
||||
|
||||
testIf(hasRealConfig)('应该处理不存在的Stream', async () => {
|
||||
const result = await service.sendMessage(
|
||||
clientInstance,
|
||||
'nonexistent-stream-12345',
|
||||
'test-topic',
|
||||
'This should fail'
|
||||
);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBeDefined();
|
||||
console.log(`预期的错误: ${result.error}`);
|
||||
}, 10000);
|
||||
|
||||
testIf(hasRealConfig)('应该能够发送包含特殊字符的消息', async () => {
|
||||
const specialMessage = `特殊字符测试 🎮🎯🚀 @#$%^&*() - ${new Date().toISOString()}`;
|
||||
|
||||
const result = await service.sendMessage(
|
||||
clientInstance,
|
||||
REAL_ZULIP_CONFIG.testStream,
|
||||
REAL_ZULIP_CONFIG.testTopic,
|
||||
specialMessage
|
||||
);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.messageId).toBeDefined();
|
||||
}, 10000);
|
||||
|
||||
testIf(hasRealConfig)('应该能够发送Markdown格式的消息', async () => {
|
||||
const markdownMessage = `
|
||||
# Markdown测试消息
|
||||
|
||||
**粗体文本** 和 *斜体文本*
|
||||
|
||||
- 列表项 1
|
||||
- 列表项 2
|
||||
|
||||
\`代码块\`
|
||||
|
||||
> 引用文本
|
||||
|
||||
[链接](https://example.com)
|
||||
|
||||
时间戳: ${new Date().toISOString()}
|
||||
`.trim();
|
||||
|
||||
const result = await service.sendMessage(
|
||||
clientInstance,
|
||||
REAL_ZULIP_CONFIG.testStream,
|
||||
REAL_ZULIP_CONFIG.testTopic,
|
||||
markdownMessage
|
||||
);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.messageId).toBeDefined();
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
describe('事件队列测试', () => {
|
||||
let clientInstance: any;
|
||||
|
||||
beforeEach(async () => {
|
||||
if (!hasRealConfig) return;
|
||||
|
||||
clientInstance = await service.createClient('test-user', clientConfig);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (clientInstance) {
|
||||
await service.destroyClient(clientInstance);
|
||||
}
|
||||
});
|
||||
|
||||
testIf(hasRealConfig)('应该能够注册事件队列', async () => {
|
||||
const result = await service.registerQueue(clientInstance, ['message']);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.queueId).toBeDefined();
|
||||
expect(result.lastEventId).toBeDefined();
|
||||
expect(typeof result.lastEventId).toBe('number');
|
||||
|
||||
console.log(`队列注册成功,ID: ${result.queueId}, 最后事件ID: ${result.lastEventId}`);
|
||||
}, 10000);
|
||||
|
||||
testIf(hasRealConfig)('应该能够获取事件', async () => {
|
||||
// 先注册队列
|
||||
const registerResult = await service.registerQueue(clientInstance, ['message']);
|
||||
expect(registerResult.success).toBe(true);
|
||||
|
||||
// 获取事件(非阻塞模式)
|
||||
const eventsResult = await service.getEvents(clientInstance, true);
|
||||
|
||||
expect(eventsResult.success).toBe(true);
|
||||
expect(Array.isArray(eventsResult.events)).toBe(true);
|
||||
|
||||
console.log(`获取到 ${eventsResult.events?.length || 0} 个事件`);
|
||||
}, 10000);
|
||||
|
||||
testIf(hasRealConfig)('应该能够注销事件队列', async () => {
|
||||
// 先注册队列
|
||||
const registerResult = await service.registerQueue(clientInstance, ['message']);
|
||||
expect(registerResult.success).toBe(true);
|
||||
|
||||
// 注销队列
|
||||
const deregisterResult = await service.deregisterQueue(clientInstance);
|
||||
expect(deregisterResult).toBe(true);
|
||||
expect(clientInstance.queueId).toBeUndefined();
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
describe('HTTP请求详细测试', () => {
|
||||
testIf(hasRealConfig)('应该发送正确格式的HTTP请求', async () => {
|
||||
// 这个测试验证HTTP请求的具体格式
|
||||
const clientInstance = await service.createClient('test-user', clientConfig);
|
||||
|
||||
// 监听HTTP请求(这需要拦截zulip-js的请求)
|
||||
const originalSend = clientInstance.client.messages.send;
|
||||
let capturedRequest: any = null;
|
||||
|
||||
clientInstance.client.messages.send = jest.fn().mockImplementation(async (params) => {
|
||||
capturedRequest = params;
|
||||
return originalSend.call(clientInstance.client.messages, params);
|
||||
});
|
||||
|
||||
const testMessage = `HTTP格式测试 - ${new Date().toISOString()}`;
|
||||
|
||||
const result = await service.sendMessage(
|
||||
clientInstance,
|
||||
REAL_ZULIP_CONFIG.testStream,
|
||||
REAL_ZULIP_CONFIG.testTopic,
|
||||
testMessage
|
||||
);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(capturedRequest).toBeDefined();
|
||||
expect(capturedRequest.type).toBe('stream');
|
||||
expect(capturedRequest.to).toBe(REAL_ZULIP_CONFIG.testStream);
|
||||
expect(capturedRequest.subject).toBe(REAL_ZULIP_CONFIG.testTopic);
|
||||
expect(capturedRequest.content).toBe(testMessage);
|
||||
|
||||
await service.destroyClient(clientInstance);
|
||||
}, 15000);
|
||||
});
|
||||
|
||||
describe('错误处理测试', () => {
|
||||
testIf(hasRealConfig)('应该处理网络超时', async () => {
|
||||
const clientInstance = await service.createClient('test-user', clientConfig);
|
||||
|
||||
// 模拟网络超时(通过修改客户端配置或使用无效的服务器地址)
|
||||
const timeoutConfig = {
|
||||
...clientConfig,
|
||||
realm: 'https://timeout-test.invalid-domain-12345.com',
|
||||
};
|
||||
|
||||
try {
|
||||
const timeoutClient = await service.createClient('timeout-test', timeoutConfig);
|
||||
// 如果到达这里,说明没有超时,跳过测试
|
||||
await service.destroyClient(timeoutClient);
|
||||
console.log('网络超时测试跳过:连接成功');
|
||||
} catch (error) {
|
||||
// 预期的超时错误
|
||||
expect(error).toBeDefined();
|
||||
console.log(`预期的超时错误: ${(error as Error).message}`);
|
||||
}
|
||||
|
||||
await service.destroyClient(clientInstance);
|
||||
}, 20000);
|
||||
|
||||
testIf(hasRealConfig)('应该处理认证错误', async () => {
|
||||
const invalidConfig = {
|
||||
...clientConfig,
|
||||
apiKey: 'definitely-invalid-api-key-12345',
|
||||
};
|
||||
|
||||
try {
|
||||
await service.createClient('auth-test', invalidConfig);
|
||||
fail('应该抛出认证错误');
|
||||
} catch (error) {
|
||||
expect(error).toBeDefined();
|
||||
expect((error as Error).message).toContain('API Key验证失败');
|
||||
console.log(`预期的认证错误: ${(error as Error).message}`);
|
||||
}
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
describe('性能测试', () => {
|
||||
testIf(hasRealConfig)('应该测量消息发送性能', async () => {
|
||||
const clientInstance = await service.createClient('perf-test', clientConfig);
|
||||
const messageCount = 10; // 减少数量以避免对服务器造成压力
|
||||
|
||||
const startTime = Date.now();
|
||||
const promises: Promise<SendMessageResult>[] = [];
|
||||
|
||||
for (let i = 0; i < messageCount; i++) {
|
||||
promises.push(
|
||||
service.sendMessage(
|
||||
clientInstance,
|
||||
REAL_ZULIP_CONFIG.testStream,
|
||||
'Performance Test',
|
||||
`Performance test message ${i} - ${new Date().toISOString()}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
const endTime = Date.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
// 验证所有消息都成功发送
|
||||
results.forEach((result, index) => {
|
||||
expect(result.success).toBe(true);
|
||||
console.log(`消息 ${index}: ID ${result.messageId}`);
|
||||
});
|
||||
|
||||
const avgTime = duration / messageCount;
|
||||
console.log(`发送${messageCount}条消息耗时: ${duration}ms, 平均: ${avgTime.toFixed(2)}ms/条`);
|
||||
|
||||
// 性能断言(根据网络情况调整)
|
||||
expect(avgTime).toBeLessThan(2000); // 平均每条消息不超过2秒
|
||||
|
||||
await service.destroyClient(clientInstance);
|
||||
}, 30000);
|
||||
});
|
||||
|
||||
// 清理测试:在所有测试完成后清理测试数据
|
||||
describe('清理测试', () => {
|
||||
testIf(hasRealConfig)('应该发送清理完成消息', async () => {
|
||||
const clientInstance = await service.createClient('cleanup-test', clientConfig);
|
||||
|
||||
const cleanupMessage = `
|
||||
🧹 自动化测试完成 - ${new Date().toISOString()}
|
||||
|
||||
本次测试运行的消息已发送完毕。
|
||||
如果看到此消息,说明Zulip API集成测试成功完成。
|
||||
|
||||
测试包括:
|
||||
- ✅ API连接和认证
|
||||
- ✅ 消息发送和格式化
|
||||
- ✅ 事件队列管理
|
||||
- ✅ 错误处理
|
||||
- ✅ 性能测试
|
||||
|
||||
所有测试消息可以安全删除。
|
||||
`.trim();
|
||||
|
||||
const result = await service.sendMessage(
|
||||
clientInstance,
|
||||
REAL_ZULIP_CONFIG.testStream,
|
||||
'Test Cleanup',
|
||||
cleanupMessage
|
||||
);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
console.log('清理消息发送成功');
|
||||
|
||||
await service.destroyClient(clientInstance);
|
||||
}, 10000);
|
||||
});
|
||||
});
|
||||
|
||||
// 导出配置检查函数,供其他测试使用
|
||||
export function hasZulipConfig(): boolean {
|
||||
return hasRealConfig;
|
||||
}
|
||||
|
||||
export function getZulipTestConfig() {
|
||||
return REAL_ZULIP_CONFIG;
|
||||
}
|
||||
Reference in New Issue
Block a user