import { CardSeoService, SeoOptions } from '../../../src/modules/card/service/seo'; import { CardProfile } from '../../../src/comm/types/omni'; describe('Point 29-A: SEO & OpenGraph 社交预览元标签注入引擎', () => { let seoService: CardSeoService; beforeEach(() => { seoService = new CardSeoService(); }); const mockCard: Partial = { id: 101, name: 'Sarah Connor', title: 'Chief Technology Officer', company: 'Omni Global Tech', slogan: 'Building the next generation of AI-driven digital identities.', avatarUrl: 'https://cdn.omnicard.ai/avatars/sarah.jpg', slug: 'sarah-connor', }; describe('1. generateOpenGraphMeta 标签生成规范', () => { it('应为完整名片生成标准化的 OpenGraph 与 Twitter Card 标签集合', () => { const options: SeoOptions = { baseUrl: 'https://omnicard.ai', locale: 'en_US', siteName: 'OmniCard AI', }; const meta = seoService.generateOpenGraphMeta(mockCard, options); // OpenGraph 基础标准规范 expect(meta['og:title']).toBe('Sarah Connor - Chief Technology Officer | Omni Global Tech'); expect(meta['og:description']).toBe( 'Building the next generation of AI-driven digital identities.' ); expect(meta['og:image']).toBe('https://cdn.omnicard.ai/avatars/sarah.jpg'); expect(meta['og:url']).toBe('https://omnicard.ai/c/sarah-connor'); expect(meta['og:type']).toBe('profile'); expect(meta['og:locale']).toBe('en_US'); expect(meta['og:site_name']).toBe('OmniCard AI'); // Twitter Card 协议 expect(meta['twitter:card']).toBe('summary_large_image'); expect(meta['twitter:title']).toBe('Sarah Connor - Chief Technology Officer | Omni Global Tech'); expect(meta['twitter:description']).toBe( 'Building the next generation of AI-driven digital identities.' ); expect(meta['twitter:image']).toBe('https://cdn.omnicard.ai/avatars/sarah.jpg'); }); it('当提供 currentUrl 时,og:url 应优先采用 currentUrl', () => { const meta = seoService.generateOpenGraphMeta(mockCard, { currentUrl: 'https://custom.domain.com/profile/sarah', }); expect(meta['og:url']).toBe('https://custom.domain.com/profile/sarah'); }); it('当缺少 slogan 时,og:description 应优雅拼接 title 与 company', () => { const cardWithoutSlogan: Partial = { name: 'Alex Rivera', title: 'Global Sales Director', company: 'Nexa Global', }; const meta = seoService.generateOpenGraphMeta(cardWithoutSlogan); expect(meta['og:description']).toBe('Global Sales Director @ Nexa Global'); expect(meta['og:title']).toBe('Alex Rivera - Global Sales Director | Nexa Global'); }); }); describe('2. 多语言规范与 OpenGraph Locale 格式化', () => { it('应将各类格式的多语言编码格式化为 RFC/OpenGraph 标准 ll_CC 格式', () => { expect(seoService.formatOgLocale('zh-CN')).toBe('zh_CN'); expect(seoService.formatOgLocale('en-US')).toBe('en_US'); expect(seoService.formatOgLocale('ar-SA')).toBe('ar_SA'); expect(seoService.formatOgLocale('ja')).toBe('ja_JP'); expect(seoService.formatOgLocale('en')).toBe('en_US'); expect(seoService.formatOgLocale('zh')).toBe('zh_CN'); expect(seoService.formatOgLocale('ar')).toBe('ar_SA'); expect(seoService.formatOgLocale(undefined)).toBe('en_US'); }); }); describe('3. renderHtmlWithOgMeta 模板注入与 RTL 支持', () => { const template = ` OmniCard
`; it('应将 meta 标签与 title 安全注入到 中', () => { const html = seoService.renderHtmlWithOgMeta(template, mockCard, { baseUrl: 'https://omnicard.ai', locale: 'en_US', }); expect(html).toContain(''); expect(html).toContain(''); expect(html).toContain(''); expect(html).toContain(''); expect(html).toContain('Sarah Connor - Chief Technology Officer | Omni Global Tech'); expect(html).toContain(''); }); it('当语言为阿拉伯语时,应自动为 标签注入 dir="rtl" 与 lang="ar"', () => { const arCard: Partial = { name: 'طارق المنصور', title: 'مدير تطوير الأعمال', company: 'التقنية العالمية', }; const html = seoService.renderHtmlWithOgMeta(template, arCard, { locale: 'ar-SA', }); expect(html).toContain('dir="rtl"'); expect(html).toContain('lang="ar"'); expect(html).toContain(''); expect(html).toContain('طارق المنصور'); }); it('当显式传入 options.isRtl = true 时,也应注入 dir="rtl"', () => { const html = seoService.renderHtmlWithOgMeta(template, mockCard, { isRtl: true, }); expect(html).toContain('dir="rtl"'); }); }); describe('4. 防 XSS 注入与特殊字符安全转义', () => { it('当姓名、职位或描述包含恶意 HTML/JS 脚本或注入符号时,应进行严格转义', () => { const xssCard: Partial = { name: '', title: '">', company: 'Tech & "Solutions" ', slogan: '\'OR 1=1 --