- 模块一: 出海案例作品集、博客动态发布、人名发音语音条、RTL阿语多语言切换 - 模块二: AI顾问人设配置、行业术语库润色、RAG切片沙盒调试、流式会话与意向留资卡片 - 模块三: 全球访客雷达大屏、多维加权意向算分、外部CRM对接推送、团队公海池流转 - 模块四: 移动端NFC安全挂失、NFC动态路由规则配置、动态防伪验签、展会制卡Zip批量导出 - 模块五: 组织架构与名片分配、移动端个人中心设置、离职交接与历史线索迁移、安全审计日志 - 模块六: Stripe国际信用卡订阅Webhook、独立二级域名与SSL检测、坐席配额预警、多币种发票 - 模块七: 纸质名片OCR解析入库、国际商务日程RFC 5545 ICS日历、微官网离线SWR预加载、展会实时排行榜大屏 - 模块八: 全球出海SEO OpenGraph元标签注入与社交爬虫探测、30点端到端全链路业务流集成测试套件
189 行
7.8 KiB
TypeScript
189 行
7.8 KiB
TypeScript
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<CardProfile> = {
|
|
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<CardProfile> = {
|
|
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 = `<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<title>OmniCard</title>
|
|
</head>
|
|
<body>
|
|
<div id="app"></div>
|
|
</body>
|
|
</html>`;
|
|
|
|
it('应将 meta 标签与 title 安全注入到 <head> 中', () => {
|
|
const html = seoService.renderHtmlWithOgMeta(template, mockCard, {
|
|
baseUrl: 'https://omnicard.ai',
|
|
locale: 'en_US',
|
|
});
|
|
|
|
expect(html).toContain('<meta property="og:title" content="Sarah Connor - Chief Technology Officer | Omni Global Tech" />');
|
|
expect(html).toContain('<meta property="og:type" content="profile" />');
|
|
expect(html).toContain('<meta property="og:image" content="https://cdn.omnicard.ai/avatars/sarah.jpg" />');
|
|
expect(html).toContain('<meta name="twitter:card" content="summary_large_image" />');
|
|
expect(html).toContain('<title>Sarah Connor - Chief Technology Officer | Omni Global Tech</title>');
|
|
expect(html).toContain('<html lang="en">');
|
|
});
|
|
|
|
it('当语言为阿拉伯语时,应自动为 <html> 标签注入 dir="rtl" 与 lang="ar"', () => {
|
|
const arCard: Partial<CardProfile> = {
|
|
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('<meta property="og:locale" content="ar_SA" />');
|
|
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<CardProfile> = {
|
|
name: '<script>alert("xss")</script>',
|
|
title: '"><img src=x onerror=alert(1)>',
|
|
company: 'Tech & "Solutions" <LLC>',
|
|
slogan: '\'OR 1=1 -- <iframe src="evil.com">',
|
|
};
|
|
|
|
const meta = seoService.generateOpenGraphMeta(xssCard);
|
|
expect(meta['og:title']).not.toContain('<script>');
|
|
expect(meta['og:title']).not.toContain('onerror=');
|
|
|
|
const template = '<html><head></head><body></body></html>';
|
|
const renderedHtml = seoService.renderHtmlWithOgMeta(template, xssCard);
|
|
|
|
// 验证特殊字符均被 escape,属性双引号不可跳脱
|
|
expect(renderedHtml).not.toContain('<script>alert');
|
|
expect(renderedHtml).not.toContain('<img src=x');
|
|
expect(renderedHtml).not.toContain('<iframe');
|
|
expect(renderedHtml).toContain('"');
|
|
expect(renderedHtml).toContain('<');
|
|
expect(renderedHtml).toContain('>');
|
|
expect(renderedHtml).toContain('&');
|
|
});
|
|
});
|
|
|
|
describe('5. 空值与边界安全兜底', () => {
|
|
it('当 card 为 null 或 undefined 时,应使用默认值兜底且绝不报错', () => {
|
|
const metaNull = seoService.generateOpenGraphMeta(null);
|
|
expect(metaNull['og:title']).toBe('OmniCard AI');
|
|
expect(metaNull['og:description']).toBe(
|
|
'OmniCard AI - Global Digital Business Card & Micro Portal'
|
|
);
|
|
expect(metaNull['og:image']).toBe('https://omnicard.ai/assets/og-default.png');
|
|
expect(metaNull['og:type']).toBe('profile');
|
|
expect(metaNull['twitter:card']).toBe('summary_large_image');
|
|
|
|
const metaUndefined = seoService.generateOpenGraphMeta(undefined, {
|
|
siteName: 'Custom Brand',
|
|
defaultImage: 'https://example.com/fallback.jpg',
|
|
});
|
|
expect(metaUndefined['og:title']).toBe('Custom Brand');
|
|
expect(metaUndefined['og:image']).toBe('https://example.com/fallback.jpg');
|
|
});
|
|
|
|
it('当 template 不含标准 <head> 标签时,renderHtmlWithOgMeta 能够安全兜底注入', () => {
|
|
const rawHtml = '<div>Raw Body Content</div>';
|
|
const result = seoService.renderHtmlWithOgMeta(rawHtml, mockCard);
|
|
expect(result).toContain('<meta property="og:title"');
|
|
expect(result).toContain('<div>Raw Body Content</div>');
|
|
});
|
|
});
|
|
});
|