文件
OmniCard/backend/test/modules/ai/deepseek.test.ts

234 行
8.3 KiB
TypeScript

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');
});
});
});