import * as fs from 'fs'; import * as path from 'path'; import { CoolCommException } from '@cool-midway/core'; import { CardInfoEntity } from '../../../src/modules/card/entity/info'; import { CardInfoService } from '../../../src/modules/card/service/info'; import { AdminCardInfoController } from '../../../src/modules/card/controller/admin/info'; /** * 内存 Repository,仅覆盖 findOneBy */ class MemoryCardInfoRepo { private data: CardInfoEntity[] = []; constructor(initialData: Array> = []) { this.data = initialData.map(item => { const entity = new CardInfoEntity(); Object.assign(entity, item); return entity; }); } async findOneBy( where: Partial> ): Promise { const found = this.data.find(item => { if (where.id === undefined) { return false; } return Number(item.id) === Number(where.id); }); return found ? ({ ...found } as CardInfoEntity) : null; } } describe('后台名片 vCard 导出 API', () => { const controllerFile = path.join( __dirname, '../../../src/modules/card/controller/admin/info.ts' ); const serviceFile = path.join( __dirname, '../../../src/modules/card/service/info.ts' ); describe('1. 路由与方法约定', () => { it('admin info 控制器应声明 /vcard 导出接口', () => { expect(fs.existsSync(controllerFile)).toBe(true); const content = fs.readFileSync(controllerFile, 'utf-8'); expect(content).toMatch(/@Post\(\s*['"]\/vcard['"]/); expect(content).toMatch(/exportVCard|exportVcard/); }); it('CardInfoService 应提供 exportVCard 方法', () => { expect(fs.existsSync(serviceFile)).toBe(true); const content = fs.readFileSync(serviceFile, 'utf-8'); expect(content).toContain('exportVCard'); expect(content).toContain('generateVCard'); expect(content).not.toContain("FN:假"); }); }); describe('2. exportVCard 业务', () => { let service: CardInfoService; beforeEach(() => { service = new CardInfoService(); service.cardInfoEntity = new MemoryCardInfoRepo([ { id: 42, name: 'Alex Morgan', title: 'CTO', company: 'OmniCard AI', phone: '+86-13800138000', email: 'alex@omnicard.ai', slug: 'alex-morgan', status: 'published', }, ]) as unknown as CardInfoService['cardInfoEntity']; }); it('名片不存在时应抛出 CoolCommException,不得返回写死姓名', async () => { await expect(service.exportVCard(99999)).rejects.toThrow(CoolCommException); await expect(service.exportVCard(99999)).rejects.toThrow(/不存在|not found/i); }); it('名片存在时返回的 vcf 必须含 BEGIN:VCARD 且包含真实姓名', async () => { const vcf = await service.exportVCard(42); expect(vcf).toContain('BEGIN:VCARD'); expect(vcf).toContain('END:VCARD'); expect(vcf).toContain('FN:Alex Morgan'); expect(vcf).not.toContain('假姓名'); }); }); describe('3. 控制器委派', () => { it('vcard 接口应按 id 调用 service.exportVCard 并 ok 返回', async () => { const controller = new AdminCardInfoController(); const exportVCard = jest.fn().mockResolvedValue( 'BEGIN:VCARD\r\nVERSION:4.0\r\nFN:Alex Morgan\r\nEND:VCARD\r\n' ); controller.cardInfoService = { exportVCard, } as unknown as CardInfoService; controller.ok = jest.fn((data: unknown) => ({ code: 1000, data })) as never; const result = await controller.vcard({ id: 42 }); expect(exportVCard).toHaveBeenCalledWith(42); expect(controller.ok).toHaveBeenCalled(); const okArg = (controller.ok as jest.Mock).mock.calls[0][0] as { vcard?: string; filename?: string; }; expect(okArg.vcard).toContain('BEGIN:VCARD'); expect(okArg.filename).toMatch(/\.vcf$/); expect(result).toEqual({ code: 1000, data: okArg, }); }); it('缺少 id 时应抛 CoolCommException', async () => { const controller = new AdminCardInfoController(); controller.cardInfoService = { exportVCard: jest.fn(), } as unknown as CardInfoService; await expect(controller.vcard({})).rejects.toThrow(CoolCommException); await expect(controller.vcard({ id: 0 })).rejects.toThrow(CoolCommException); }); }); });