feat: 同步管理端、用户端与后端业务,并接通线上 Web 接口
补齐平台订阅、外部登录、CRM 同步与用户端页面;Nginx 转发用户端接口,登录页使用 Google 与 Apple 标志。 Co-authored-by: Cursor <cursoragent@cursor.com>
这个提交包含在:
@@ -166,16 +166,18 @@ describe('Point 29-A: SEO & OpenGraph 社交预览元标签注入引擎', () =>
|
||||
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');
|
||||
// 无真实头像/封面时不得填假 CDN / 占位图
|
||||
expect(metaNull['og:image']).toBe('');
|
||||
expect(metaNull['twitter:image']).toBe('');
|
||||
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');
|
||||
expect(metaUndefined['og:image']).toBe('');
|
||||
expect(metaUndefined['twitter:image']).toBe('');
|
||||
});
|
||||
|
||||
it('当 template 不含标准 <head> 标签时,renderHtmlWithOgMeta 能够安全兜底注入', () => {
|
||||
@@ -185,4 +187,112 @@ describe('Point 29-A: SEO & OpenGraph 社交预览元标签注入引擎', () =>
|
||||
expect(result).toContain('<div>Raw Body Content</div>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('6. 无真实图片时 og:image / twitter:image 必须为空(禁止假图)', () => {
|
||||
it('generateOgMeta:无 avatarUrl 时 image 为空,不得回退到 default-card-og / og-default', () => {
|
||||
const og = seoService.generateOgMeta(
|
||||
{
|
||||
id: 1,
|
||||
name: 'No Avatar',
|
||||
title: 'Engineer',
|
||||
company: 'Omni',
|
||||
status: 'active' as any,
|
||||
bentoBlocks: [],
|
||||
},
|
||||
'https://omnicard.ai'
|
||||
);
|
||||
expect(og.image).toBe('');
|
||||
expect(og.image).not.toMatch(/og-default|default-card-og|placeholder|example\.com|unsplash|picsum/i);
|
||||
});
|
||||
|
||||
it('generateOgMeta:仅接受真实 http(s) 头像,相对路径与非 http 协议视为无图', () => {
|
||||
const relative = seoService.generateOgMeta({
|
||||
id: 2,
|
||||
name: 'Rel',
|
||||
title: 'T',
|
||||
company: 'C',
|
||||
avatarUrl: '/static/avatar.png',
|
||||
status: 'active' as any,
|
||||
bentoBlocks: [],
|
||||
});
|
||||
expect(relative.image).toBe('');
|
||||
|
||||
const blank = seoService.generateOgMeta({
|
||||
id: 2,
|
||||
name: 'Blank',
|
||||
title: 'T',
|
||||
company: 'C',
|
||||
avatarUrl: ' ',
|
||||
status: 'active' as any,
|
||||
bentoBlocks: [],
|
||||
});
|
||||
expect(blank.image).toBe('');
|
||||
|
||||
const dataUri = seoService.generateOgMeta({
|
||||
id: 2,
|
||||
name: 'Data',
|
||||
title: 'T',
|
||||
company: 'C',
|
||||
avatarUrl: 'data:image/png;base64,AAAA',
|
||||
status: 'active' as any,
|
||||
bentoBlocks: [],
|
||||
});
|
||||
expect(dataUri.image).toBe('');
|
||||
|
||||
const real = seoService.generateOgMeta({
|
||||
id: 3,
|
||||
name: 'Https',
|
||||
title: 'T',
|
||||
company: 'C',
|
||||
avatarUrl: 'https://cdn.omnicard.ai/avatars/real.jpg',
|
||||
status: 'active' as any,
|
||||
bentoBlocks: [],
|
||||
});
|
||||
expect(real.image).toBe('https://cdn.omnicard.ai/avatars/real.jpg');
|
||||
});
|
||||
|
||||
it('generateOpenGraphMeta:无头像时 og:image / twitter:image 为空,忽略假 defaultImage 占位', () => {
|
||||
const meta = seoService.generateOpenGraphMeta(
|
||||
{ name: 'Empty Pic', title: 'CTO', company: 'X' },
|
||||
{
|
||||
baseUrl: 'https://omnicard.ai',
|
||||
defaultImage: 'https://omnicard.ai/assets/og-default.png',
|
||||
}
|
||||
);
|
||||
expect(meta['og:image']).toBe('');
|
||||
expect(meta['twitter:image']).toBe('');
|
||||
expect(JSON.stringify(meta)).not.toMatch(
|
||||
/og-default|default-card-og|placeholder|picsum|unsplash/i
|
||||
);
|
||||
});
|
||||
|
||||
it('renderHtmlWithOgMeta:无真实图片时不输出 og:image / twitter:image 标签', () => {
|
||||
const template = `<!DOCTYPE html><html><head><title>t</title></head><body></body></html>`;
|
||||
const html = seoService.renderHtmlWithOgMeta(
|
||||
template,
|
||||
{ name: 'No Image Card' },
|
||||
{ baseUrl: 'https://omnicard.ai' }
|
||||
);
|
||||
expect(html).not.toContain('property="og:image"');
|
||||
expect(html).not.toContain('name="twitter:image"');
|
||||
expect(html).not.toMatch(/og-default|default-card-og/i);
|
||||
});
|
||||
|
||||
it('renderCardHtml:无头像时 og:image / twitter:image content 为空', () => {
|
||||
const html = seoService.renderCardHtml(
|
||||
{
|
||||
id: 9,
|
||||
name: 'Blank',
|
||||
title: 'Dev',
|
||||
company: 'ACME',
|
||||
status: 'active' as any,
|
||||
bentoBlocks: [],
|
||||
},
|
||||
{ baseUrl: 'https://omnicard.ai' }
|
||||
);
|
||||
expect(html).toContain('<meta property="og:image" content="" />');
|
||||
expect(html).toContain('<meta name="twitter:image" content="" />');
|
||||
expect(html).not.toMatch(/og-default|default-card-og|placeholder\.com|example\.com\/.*\.(png|jpe?g)/i);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
在新工单中引用
屏蔽一个用户