import * as fs from 'fs'; import * as path from 'path'; import { CoolCommException, BaseController } from '@cool-midway/core'; import { CardPortalService } from '../../src/modules/card/service/portal'; import { CardQrcodeService } from '../../src/modules/card/service/qrcode'; import { OpenCardQrcodeController } from '../../src/modules/card/controller/open/qrcode'; import { buildCardPublicUrl, DEFAULT_CARD_PUBLIC_BASE, } from '../../src/comm/qrcode/qrcode'; import { CardPortalDetail } from '../../src/comm/types/omni'; describe('T06: 动态短链二维码 PNG/SVG(公开免登录)', () => { const publishedPortal: CardPortalDetail = { id: 101, slug: 'alex-zheng', name: 'Alex Zheng', title: 'VP of Global Expansion', company: 'OmniCard Global Inc.', status: 'published', bentoBlocks: [], }; const draftPortal: CardPortalDetail = { id: 102, slug: 'draft-user', name: 'Draft User', title: 'Engineer', company: 'OmniCard', status: 'draft', bentoBlocks: [], }; let cardQrcodeService: CardQrcodeService; let getPortalDetail: jest.Mock; beforeEach(() => { getPortalDetail = jest.fn(); cardQrcodeService = new CardQrcodeService(); cardQrcodeService.cardPortalService = { getPortalDetail, } as unknown as CardPortalService; }); describe('1. 公开短链地址', () => { it('无环境变量时使用 https://card.omnicard.ai/{slug},slug 优先否则 id', () => { const prev = process.env.CARD_PUBLIC_BASE_URL; delete process.env.CARD_PUBLIC_BASE_URL; expect(DEFAULT_CARD_PUBLIC_BASE).toBe('https://card.omnicard.ai'); expect(buildCardPublicUrl('alex-zheng')).toBe( 'https://card.omnicard.ai/alex-zheng' ); expect(buildCardPublicUrl(101)).toBe('https://card.omnicard.ai/101'); if (prev === undefined) { delete process.env.CARD_PUBLIC_BASE_URL; } else { process.env.CARD_PUBLIC_BASE_URL = prev; } }); }); describe('2. 已发布名片生成二维码', () => { it('已发布名片能生成含 slug 的 SVG(包含 { getPortalDetail.mockResolvedValue(publishedPortal); const result = await cardQrcodeService.generateSvg('alex-zheng'); expect(getPortalDetail).toHaveBeenCalledWith('alex-zheng'); expect(result.url).toBe('https://card.omnicard.ai/alex-zheng'); expect(result.url).toContain('alex-zheng'); expect(result.svg).toContain(' { getPortalDetail.mockResolvedValue(publishedPortal); const result = await cardQrcodeService.generatePng('alex-zheng'); expect(result.url).toBe('https://card.omnicard.ai/alex-zheng'); expect(result.png.startsWith('data:image/png;base64,')).toBe(true); }); it('无 slug 时二维码内容回退为名片 id', async () => { getPortalDetail.mockResolvedValue({ ...publishedPortal, slug: undefined, id: 101, }); const result = await cardQrcodeService.generateSvg(101); expect(result.url).toBe('https://card.omnicard.ai/101'); expect(result.svg).toContain(' { it('draft 名片抛错', async () => { getPortalDetail.mockImplementation(async () => { throw new CoolCommException('名片未发布或已下线', 404); }); await expect(cardQrcodeService.generateSvg('draft-user')).rejects.toThrow( CoolCommException ); await expect(cardQrcodeService.generateSvg('draft-user')).rejects.toThrow( /未发布或已下线/ ); expect(getPortalDetail).toHaveBeenCalledWith('draft-user'); getPortalDetail.mockResolvedValue(draftPortal); await expect(cardQrcodeService.generatePng('draft-user')).rejects.toThrow( /未发布或已下线/ ); }); it('缺少标识抛错', async () => { await expect(cardQrcodeService.generateSvg('')).rejects.toThrow( CoolCommException ); await expect(cardQrcodeService.generateSvg(' ')).rejects.toThrow( /缺少名片标识/ ); await expect( cardQrcodeService.generateSvg(undefined as unknown as string) ).rejects.toThrow(/缺少名片标识/); expect(getPortalDetail).not.toHaveBeenCalled(); }); }); describe('4. 公开控制器路由约定', () => { const controllerFile = path.join( __dirname, '../../src/modules/card/controller/open/qrcode.ts' ); it('文件路径应符合 /open/card/qrcode 自动路由约定', () => { expect(fs.existsSync(controllerFile)).toBe(true); expect(controllerFile.replace(/\\/g, '/')).toMatch( /modules\/card\/controller\/open\/qrcode\.ts$/ ); }); it('控制器应继承 BaseController 并忽略 Token', () => { expect(OpenCardQrcodeController.prototype).toBeInstanceOf(BaseController); const source = fs.readFileSync(controllerFile, 'utf8'); expect(source).toContain('TagTypes.IGNORE_TOKEN'); expect(source).toContain("@Get('/svg'"); expect(source).toContain("@Get('/png'"); }); it('GET /open/card/qrcode/svg 应返回 { data: { svg, url } }', async () => { getPortalDetail.mockResolvedValue(publishedPortal); const controller = new OpenCardQrcodeController(); controller.cardQrcodeService = cardQrcodeService; const res = (await controller.svg('alex-zheng')) as unknown as { code: number; data: { svg: string; url: string }; }; expect(res.code).toBe(1000); expect(res.data.url).toContain('alex-zheng'); expect(res.data.svg).toContain(' { getPortalDetail.mockResolvedValue(publishedPortal); const controller = new OpenCardQrcodeController(); controller.cardQrcodeService = cardQrcodeService; const res = (await controller.png('alex-zheng')) as unknown as { code: number; data: { png: string; url: string }; }; expect(res.code).toBe(1000); expect(res.data.png.startsWith('data:image/png;base64,')).toBe(true); }); }); });