import { CardBatchLocaleService, BatchLocaleMemberCard, validateLocaleFields, } from '../../../src/modules/card/service/batch-locale'; import { CardLocaleEntity } from '../../../src/modules/card/entity/locale'; describe('Point 43: 出海团队多语言名片批量生成向导与多语种字段镜像校验', () => { let service: CardBatchLocaleService; let mockRepository: any; let savedRecords: CardLocaleEntity[]; beforeEach(() => { savedRecords = []; mockRepository = { findOneBy: jest.fn(async ({ cardId, locale }: any) => { return savedRecords.find(r => r.cardId === cardId && r.locale === locale) || null; }), find: jest.fn(async ({ where }: any) => { if (where && where.cardId) { return savedRecords.filter(r => r.cardId === where.cardId); } return savedRecords; }), save: jest.fn(async (entity: any) => { const existingIdx = savedRecords.findIndex( r => r.cardId === entity.cardId && r.locale === entity.locale ); if (existingIdx >= 0) { savedRecords[existingIdx] = { ...savedRecords[existingIdx], ...entity }; return savedRecords[existingIdx]; } const created = { id: savedRecords.length + 1, ...entity }; savedRecords.push(created); return created; }), }; service = new CardBatchLocaleService(); service.cardLocaleEntity = mockRepository; }); describe('1. validateLocaleFields (多语言字段规范与合法性校验)', () => { it('正常字段应校验通过', () => { const res = validateLocaleFields({ name: 'Sarah Chen', title: 'VP of Global BD', company: 'OmniCard AI', slogan: 'Connect the world with intelligent cards', }); expect(res.valid).toBe(true); expect(res.errors).toHaveLength(0); }); it('姓名为空或超长应精准拦截', () => { const emptyRes = validateLocaleFields({ name: ' ' }); expect(emptyRes.valid).toBe(false); expect(emptyRes.errors).toContain('姓名不能为空'); const longRes = validateLocaleFields({ name: 'A'.repeat(65) }); expect(longRes.valid).toBe(false); expect(longRes.errors).toContain('姓名长度不能超过64字符'); }); it('职位、公司与标语超长时应拦截', () => { const res = validateLocaleFields({ name: 'Test', title: 'T'.repeat(129), company: 'C'.repeat(129), slogan: 'S'.repeat(256), }); expect(res.valid).toBe(false); expect(res.errors.length).toBeGreaterThanOrEqual(3); }); }); describe('2. batchGenerateMemberLocales (团队成员多语言名片批量生成向导)', () => { const mockMembers: BatchLocaleMemberCard[] = [ { cardId: 101, baseFields: { name: '张三', title: '出海销售总监', company: '深圳数字智能出海科技', slogan: '智能连接全球', }, }, { cardId: 102, baseFields: { name: '李四', title: '中东区域代表', company: '深圳数字智能出海科技', slogan: '专注海湾六国', }, }, ]; it('应成功为团队成员批量生成指定目标语言分支 (en, ar)', async () => { const result = await service.batchGenerateMemberLocales({ members: mockMembers, targetLocales: ['en', 'ar'], }); expect(result.totalTasks).toBe(4); // 2 members * 2 locales expect(result.successTasks).toBe(4); expect(result.failedTasks).toBe(0); expect(result.completionRate).toBe(1.0); expect(savedRecords.length).toBe(4); // 验证保存的记录属性 const en101 = savedRecords.find(r => r.cardId === 101 && r.locale === 'en'); expect(en101).toBeDefined(); expect(en101?.name).toBe('张三'); const ar102 = savedRecords.find(r => r.cardId === 102 && r.locale === 'ar'); expect(ar102).toBeDefined(); expect(ar102?.name).toBe('李四'); }); it('支持传入语言自定义词典或转换映射处理器 (localeTransformer)', async () => { const result = await service.batchGenerateMemberLocales({ members: [mockMembers[0]], targetLocales: ['en'], localeTransformer: (locale, base) => { if (locale === 'en') { return { name: 'Alex Zhang', title: 'Global Sales Director', company: 'OmniCard Tech Co., Ltd.', slogan: 'Connecting globally with AI cards', }; } return base; }, }); expect(result.successTasks).toBe(1); const enCard = savedRecords.find(r => r.cardId === 101 && r.locale === 'en'); expect(enCard?.name).toBe('Alex Zhang'); expect(enCard?.title).toBe('Global Sales Director'); }); it('遇到非法非法参数时应记录错误且不阻断其它任务', async () => { const invalidMembers: BatchLocaleMemberCard[] = [ { cardId: 999, baseFields: { name: '' }, // 姓名为空导致失败 }, mockMembers[0], ]; const result = await service.batchGenerateMemberLocales({ members: invalidMembers, targetLocales: ['en'], }); expect(result.totalTasks).toBe(2); expect(result.successTasks).toBe(1); expect(result.failedTasks).toBe(1); expect(result.errors.length).toBe(1); expect(result.errors[0].cardId).toBe(999); expect(result.completionRate).toBe(0.5); }); }); describe('3. inspectTeamLocaleCoverage (出海团队语言覆盖率与就绪度诊断)', () => { it('应精准统计团队名片各语言的覆盖率与缺失名单', async () => { savedRecords.push( { id: 1, cardId: 101, locale: 'en', name: 'Alex' } as CardLocaleEntity, { id: 2, cardId: 101, locale: 'ar', name: 'Alex' } as CardLocaleEntity, { id: 3, cardId: 102, locale: 'en', name: 'John' } as CardLocaleEntity ); const coverage = await service.inspectTeamLocaleCoverage( [101, 102], ['en', 'ar', 'es'] ); expect(coverage.teamMemberCount).toBe(2); expect(coverage.locales['en'].coverageRate).toBe(1.0); // 101, 102 都有 en expect(coverage.locales['en'].missingCardIds).toHaveLength(0); expect(coverage.locales['ar'].coverageRate).toBe(0.5); // 仅 101 有 expect(coverage.locales['ar'].missingCardIds).toEqual([102]); expect(coverage.locales['es'].coverageRate).toBe(0.0); // 都没有 expect(coverage.locales['es'].missingCardIds).toEqual([101, 102]); }); }); });