import { DeepSeekService } from '../../../src/modules/ai/service/deepseek'; import { DeepSeekConfig } from '../../../src/comm/types/omni'; describe('T31 DeepSeek 大模型集成与适配服务 (deepseek.ts)', () => { let service: DeepSeekService; beforeEach(() => { // 先清理环境变量,防止跨测试用例污染 delete process.env.DEEPSEEK_API_KEY; delete process.env.DEEPSEEK_BASE_URL; delete process.env.DEEPSEEK_MODEL; service = new DeepSeekService(); }); describe('1. 配置管理与状态检测 (Config & Readiness)', () => { it('1.1 初始状态默认配置验证', () => { const config = service.getConfig(); expect(config.baseUrl).toBe('https://api.deepseek.com'); expect(config.model).toBe('deepseek-chat'); expect(config.temperature).toBe(0.7); expect(config.timeoutMs).toBe(30000); expect(service.isConfigured()).toBe(false); }); it('1.2 优先从环境变量读取 DEEPSEEK_API_KEY', () => { process.env.DEEPSEEK_API_KEY = 'sk-env-test-key-123'; process.env.DEEPSEEK_BASE_URL = 'https://custom.deepseek.com'; process.env.DEEPSEEK_MODEL = 'deepseek-reasoner'; const envService = new DeepSeekService(); expect(envService.isConfigured()).toBe(true); expect(envService.getConfig().apiKey).toBe('sk-env-test-key-123'); expect(envService.getConfig().baseUrl).toBe('https://custom.deepseek.com'); expect(envService.getConfig().model).toBe('deepseek-reasoner'); }); it('1.3 updateConfig 动态更新与合并配置', () => { service.updateConfig({ apiKey: 'sk-dynamic-key-456', model: 'deepseek-reasoner', temperature: 0.2, }); expect(service.isConfigured()).toBe(true); const cfg = service.getConfig(); expect(cfg.apiKey).toBe('sk-dynamic-key-456'); expect(cfg.model).toBe('deepseek-reasoner'); expect(cfg.temperature).toBe(0.2); expect(cfg.baseUrl).toBe('https://api.deepseek.com'); // 保留默认值 }); }); describe('2. 提示词动态拼装 (Prompt Builders)', () => { it('2.1 buildSystemPrompt 组装名片主、人设与知识库切片', () => { const card = { name: '张总', company: '华强出海科技', title: '海外商务总监', }; const concierge = { personaType: 'trade_expert' as const, toneStyle: 'professional' as const, promptTemplate: '你是一名资深出海贸易专家,精通中东与东南亚市场。', }; const chunks = [ { index: 1, content: '支持信用证结算与 FOB 迪拜港条款。' }, { index: 2, content: '最小起订量 (MOQ) 为 500 台。' }, ]; const systemPrompt = service.buildSystemPrompt(card, concierge, chunks); expect(systemPrompt).toContain('张总'); expect(systemPrompt).toContain('华强出海科技'); expect(systemPrompt).toContain('海外商务总监'); expect(systemPrompt).toContain('你是一名资深出海贸易专家'); expect(systemPrompt).toContain('支持信用证结算与 FOB 迪拜港条款。'); expect(systemPrompt).toContain('最小起订量 (MOQ) 为 500 台。'); }); it('2.2 buildLocalizePrompt 组装多语言润色与术语库指令', () => { const prompt = service.buildLocalizePrompt( '欢迎参观我们位于迪拜的展位', 'en-US', 'professional', ['展位 -> Booth', '迪拜 -> Dubai'] ); expect(prompt).toContain('欢迎参观我们位于迪拜的展位'); expect(prompt).toContain('en-US'); expect(prompt).toContain('professional'); expect(prompt).toContain('Booth'); expect(prompt).toContain('Dubai'); }); it('2.3 buildCopilotPrompt 组装销售破冰首语生成指令', () => { const lead = { name: 'Ahmed', company: 'Gulf Energy', phone: '+971501234567', demand: '储能集装箱报价', }; const tracks = [ { module: 'case_energy', duration: 180 }, { module: 'price_table', duration: 90 }, ]; const prompt = service.buildCopilotPrompt(lead, tracks); expect(prompt).toContain('Ahmed'); expect(prompt).toContain('Gulf Energy'); expect(prompt).toContain('储能集装箱报价'); expect(prompt).toContain('case_energy'); }); }); describe('3. 文本补全调用与高可用降级 (chatCompletion & Fallback)', () => { it('3.1 未配置 API Key 时,提供 fallbackReply 则安全返回兜底内容', async () => { expect(service.isConfigured()).toBe(false); const result = await service.chatCompletion( { messages: [{ role: 'user', content: '你们的产品价格是多少?' }], }, '这是本地规则引擎的兜底回复' ); expect(result).toBe('这是本地规则引擎的兜底回复'); }); it('3.2 未配置 API Key 且未提供 fallbackReply 时安全降级或抛出清晰提示', async () => { await expect( service.chatCompletion({ messages: [{ role: 'user', content: '你好' }], }) ).rejects.toThrow('DeepSeek API Key 未配置'); }); it('3.3 模拟正常调用 DeepSeek 官方接口返回生成文本', async () => { service.updateConfig({ apiKey: 'sk-mock-valid-key' }); // 模拟底层 HTTP client const mockPost = jest.fn().mockResolvedValue({ status: 200, data: { id: 'chatcmpl-test-123', model: 'deepseek-chat', choices: [ { index: 0, message: { role: 'assistant', content: '您好!我是华强科技的 AI 顾问,很高兴为您服务。', }, finish_reason: 'stop', }, ], }, }); (service as any).httpClient = { post: mockPost }; const reply = await service.chatCompletion({ messages: [{ role: 'user', content: '您好' }], }); expect(reply).toBe('您好!我是华强科技的 AI 顾问,很高兴为您服务。'); expect(mockPost).toHaveBeenCalledWith( 'https://api.deepseek.com/chat/completions', expect.objectContaining({ model: 'deepseek-chat', messages: [{ role: 'user', content: '您好' }], }), expect.objectContaining({ headers: expect.objectContaining({ Authorization: 'Bearer sk-mock-valid-key', }), }) ); }); it('3.4 API 调用异常且提供 fallbackReply 时平滑降级', async () => { service.updateConfig({ apiKey: 'sk-mock-valid-key' }); const mockPost = jest.fn().mockRejectedValue(new Error('Network Timeout')); (service as any).httpClient = { post: mockPost }; const reply = await service.chatCompletion( { messages: [{ role: 'user', content: '报价' }] }, '网络异常时的平滑降级回复' ); expect(reply).toBe('网络异常时的平滑降级回复'); }); }); describe('4. 连通性测试 (testConnection)', () => { it('4.1 未配置 API Key 且未传入临时 Key 时测试失败', async () => { const res = await service.testConnection(); expect(res.success).toBe(false); expect(res.message).toContain('API Key 未提供'); }); it('4.2 传入测试 Key 成功握手并统计延迟', async () => { const mockPost = jest.fn().mockResolvedValue({ status: 200, data: { choices: [ { message: { content: 'pong' }, finish_reason: 'stop' }, ], }, }); (service as any).httpClient = { post: mockPost }; const res = await service.testConnection('sk-test-tmp-key'); expect(res.success).toBe(true); expect(typeof res.latencyMs).toBe('number'); expect(res.latencyMs).toBeGreaterThanOrEqual(0); expect(res.model).toBe('deepseek-chat'); }); it('4.3 官方 API 返回 401 鉴权失败捕获', async () => { const mockPost = jest.fn().mockRejectedValue({ response: { status: 401, data: { error: { message: 'Authentication Fails (invalid api key)' } }, }, }); (service as any).httpClient = { post: mockPost }; const res = await service.testConnection('sk-invalid-key'); expect(res.success).toBe(false); expect(res.message).toContain('Authentication Fails'); }); }); });