/** * 真实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); 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[] = []; 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; }