文件
OmniCard/backend/test/modules/ai/glossary.test.ts
wikiboss eec259a3a7 feat: 完成全系统30个功能点开发与E2E全链路验收 (8轮调度32个子代理闭环)
- 模块一: 出海案例作品集、博客动态发布、人名发音语音条、RTL阿语多语言切换
- 模块二: AI顾问人设配置、行业术语库润色、RAG切片沙盒调试、流式会话与意向留资卡片
- 模块三: 全球访客雷达大屏、多维加权意向算分、外部CRM对接推送、团队公海池流转
- 模块四: 移动端NFC安全挂失、NFC动态路由规则配置、动态防伪验签、展会制卡Zip批量导出
- 模块五: 组织架构与名片分配、移动端个人中心设置、离职交接与历史线索迁移、安全审计日志
- 模块六: Stripe国际信用卡订阅Webhook、独立二级域名与SSL检测、坐席配额预警、多币种发票
- 模块七: 纸质名片OCR解析入库、国际商务日程RFC 5545 ICS日历、微官网离线SWR预加载、展会实时排行榜大屏
- 模块八: 全球出海SEO OpenGraph元标签注入与社交爬虫探测、30点端到端全链路业务流集成测试套件
2026-09-20 23:47:47 +08:00

273 行
9.3 KiB
TypeScript

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

import * as fs from 'fs';
import * as path from 'path';
import { CONTROLLER_KEY, getClassMetadata } from '@midwayjs/core';
import { BaseController, CoolCommException, CurdOption } from '@cool-midway/core';
import { Repository } from 'typeorm';
import { AiGlossaryEntity } from '../../../src/modules/ai/entity/glossary';
import { AiGlossaryService } from '../../../src/modules/ai/service/glossary';
import { AiLocalizeService } from '../../../src/modules/ai/service/localize';
import {
AdminAiGlossaryController,
adminAiGlossaryCurdOption,
} from '../../../src/modules/ai/controller/admin/glossary';
import { AiGlossaryItem } from '../../../src/comm/types/omni';
interface CoolControllerMeta {
prefix?: string;
curdOption?: CurdOption;
}
async function waitCoolMeta(
target: new (...args: never[]) => unknown
): Promise<CoolControllerMeta> {
const started = Date.now();
while (Date.now() - started < 2000) {
const meta = getClassMetadata(CONTROLLER_KEY, target) as
| CoolControllerMeta
| undefined;
if (meta?.curdOption) {
return meta;
}
await new Promise<void>(resolve => setImmediate(resolve));
}
throw new Error(`${target.name} 的 CoolController 元数据未就绪`);
}
function createMockRepo(seedItems: AiGlossaryEntity[] = []): Repository<AiGlossaryEntity> {
let list = [...seedItems];
let autoId = 100;
return {
find: jest.fn(async (options?: { where?: Record<string, unknown> }) => {
if (!options?.where) {
return list;
}
return list.filter(item => {
for (const [k, v] of Object.entries(options.where ?? {})) {
if (v !== undefined && (item as unknown as Record<string, unknown>)[k] !== v) {
return false;
}
}
return true;
});
}),
save: jest.fn(async (entityOrEntities: AiGlossaryEntity | AiGlossaryEntity[]) => {
if (Array.isArray(entityOrEntities)) {
for (const item of entityOrEntities) {
if (!item.id) {
item.id = ++autoId;
}
list.push(item);
}
return entityOrEntities;
}
if (!entityOrEntities.id) {
entityOrEntities.id = ++autoId;
}
list.push(entityOrEntities);
return entityOrEntities;
}),
insert: jest.fn(async (item: AiGlossaryEntity) => {
if (!item.id) {
item.id = ++autoId;
}
list.push(item);
return { identifiers: [{ id: item.id }] };
}),
} as unknown as Repository<AiGlossaryEntity>;
}
describe('Point 06: 后台多语言 AI 润色中心与行业术语库 (Glossary)', () => {
describe('1. 实体 AiGlossaryEntity 定义与字段校验', () => {
it('实体属性与映射表名 ai_glossary 校验', () => {
const entity = new AiGlossaryEntity();
entity.sourceTerm = 'digital business card';
entity.targetTerm = '智能数字名片';
entity.sourceLang = 'en';
entity.targetLang = 'zh-CN';
entity.domain = 'tech';
entity.notes = '核心品牌术语';
expect(entity.sourceTerm).toBe('digital business card');
expect(entity.targetTerm).toBe('智能数字名片');
expect(entity.sourceLang).toBe('en');
expect(entity.targetLang).toBe('zh-CN');
expect(entity.domain).toBe('tech');
expect(entity.notes).toBe('核心品牌术语');
});
});
describe('2. AiGlossaryService 批量导入与增删改查', () => {
let service: AiGlossaryService;
let mockRepo: Repository<AiGlossaryEntity>;
beforeEach(() => {
service = new AiGlossaryService();
mockRepo = createMockRepo();
service.aiGlossaryEntity = mockRepo;
});
it('batchImport 空列表应抛异常或返回0', async () => {
await expect(service.batchImport([])).rejects.toThrow(CoolCommException);
});
it('batchImport 忽略缺少 sourceTerm 或 targetTerm 的无效项', async () => {
const result = await service.batchImport([
{ sourceTerm: '', targetTerm: '无效' },
{ sourceTerm: 'CRM', targetTerm: '' },
{ sourceTerm: 'NFC Card', targetTerm: 'NFC 实体卡', targetLang: 'zh-CN' },
]);
expect(result.total).toBe(3);
expect(result.inserted).toBe(1);
});
it('batchImport 批量导入多项成功', async () => {
const items: Array<Partial<AiGlossaryItem>> = [
{ sourceTerm: 'OmniCard', targetTerm: '奥秘名片', targetLang: 'zh-CN' },
{ sourceTerm: 'Radar', targetTerm: '访客雷达', targetLang: 'zh-CN', domain: 'crm' },
];
const result = await service.batchImport(items);
expect(result.inserted).toBe(2);
expect(result.total).toBe(2);
});
});
describe('3. AiGlossaryService 术语匹配替换 (贪心长词优先 & 领域匹配)', () => {
let service: AiGlossaryService;
beforeEach(() => {
const seed: AiGlossaryEntity[] = [
{
id: 1,
sourceTerm: 'digital card',
targetTerm: '电子名片',
targetLang: 'zh-CN',
domain: 'general',
} as AiGlossaryEntity,
{
id: 2,
sourceTerm: 'smart digital card',
targetTerm: '全球智能数字名片',
targetLang: 'zh-CN',
domain: 'tech',
} as AiGlossaryEntity,
{
id: 3,
sourceTerm: 'lead',
targetTerm: '高意向商机',
targetLang: 'zh-CN',
domain: 'crm',
} as AiGlossaryEntity,
{
id: 4,
sourceTerm: 'lead',
targetTerm: '线索',
targetLang: 'zh-CN',
domain: 'general',
} as AiGlossaryEntity,
{
id: 5,
sourceTerm: 'بطاقة',
targetTerm: 'بطاقة ذكية',
targetLang: 'ar-SA',
} as AiGlossaryEntity,
];
service = new AiGlossaryService();
const mockRepo = createMockRepo(seed);
service.aiGlossaryEntity = mockRepo;
});
it('长词优先贪心匹配:smart digital card 应被替换为 全球智能数字名片 而非 电子名片', async () => {
const input = 'This is our smart digital card platform.';
const output = await service.replaceTerms(input, 'zh-CN', 'tech');
expect(output).toContain('全球智能数字名片');
expect(output).not.toContain('电子名片');
});
it('领域匹配优先:domain 为 crm 时 lead 替换为 高意向商机,否则为 线索', async () => {
const input = 'We captured a new lead yesterday.';
const crmOutput = await service.replaceTerms(input, 'zh-CN', 'crm');
expect(crmOutput).toContain('高意向商机');
const generalOutput = await service.replaceTerms(input, 'zh-CN', 'general');
expect(generalOutput).toContain('线索');
});
it('跨目标语言隔离:目标语言 ar-SA 不应应用 zh-CN 术语', async () => {
const input = 'smart digital card and بطاقة';
const output = await service.replaceTerms(input, 'ar-SA');
expect(output).toContain('بطاقة ذكية');
expect(output).not.toContain('全球智能数字名片');
});
});
describe('4. AiLocalizeService 引用术语库并强行替换', () => {
let localizeService: AiLocalizeService;
let glossaryService: AiGlossaryService;
beforeEach(() => {
glossaryService = new AiGlossaryService();
const seed: AiGlossaryEntity[] = [
{
id: 1,
sourceTerm: 'digital cards',
targetTerm: '智能数字名片',
targetLang: 'zh-CN',
} as AiGlossaryEntity,
];
const mockRepo = createMockRepo(seed);
glossaryService.aiGlossaryEntity = mockRepo;
localizeService = new AiLocalizeService();
localizeService.aiGlossaryService = glossaryService;
});
it('润色时应将术语强制替换为术语库配置的目标词', async () => {
const result = await localizeService.localize({
text: 'We help global teams share digital cards.',
targetLocale: 'zh-CN',
tone: 'professional',
});
expect(result.locale).toBe('zh-CN');
expect(result.text).toContain('智能数字名片');
});
});
describe('5. AdminAiGlossaryController 路由与元数据校验', () => {
const controllerFile = path.join(
__dirname,
'../../../src/modules/ai/controller/admin/glossary.ts'
);
let meta: CoolControllerMeta;
let option: CurdOption;
beforeAll(async () => {
meta = await waitCoolMeta(AdminAiGlossaryController);
option = meta.curdOption as CurdOption;
});
it('文件路径必须符合 /admin/ai/glossary 规范', () => {
expect(fs.existsSync(controllerFile)).toBe(true);
expect(controllerFile.replace(/\\/g, '/')).toMatch(
/modules\/ai\/controller\/admin\/glossary\.ts$/
);
});
it('路由前缀必须为 /admin/ai/glossary', () => {
expect(meta.prefix).toBe('/admin/ai/glossary');
});
it('控制器应继承 BaseController 并开启 CRUD 与 serviceApis', () => {
expect(AdminAiGlossaryController.prototype).toBeInstanceOf(BaseController);
expect(option.service).toBe(AiGlossaryService);
expect(adminAiGlossaryCurdOption.service).toBe(AiGlossaryService);
expect(option.entity).toBe(AiGlossaryEntity);
expect(option.api).toEqual(
expect.arrayContaining(['add', 'delete', 'update', 'info', 'list', 'page'])
);
expect(option.serviceApis).toEqual(expect.arrayContaining(['batchImport']));
});
});
});