import * as crypto from 'crypto'; import * as AdmZip from 'adm-zip'; // ========================================== // 1. 实体与契约导入 // ========================================== import { CardInfoEntity } from '../../src/modules/card/entity/info'; import { CardBentoEntity } from '../../src/modules/card/entity/bento'; import { CardCasesEntity } from '../../src/modules/card/entity/cases'; import { CardArticleEntity } from '../../src/modules/card/entity/article'; import { CardLocaleEntity } from '../../src/modules/card/entity/locale'; import { CrmTrackEntity } from '../../src/modules/crm/entity/track'; import { CrmLeadEntity } from '../../src/modules/crm/entity/lead'; import { CrmLeadAuditEntity } from '../../src/modules/crm/entity/audit'; import { AiConciergeEntity } from '../../src/modules/ai/entity/concierge'; import { AiGlossaryEntity } from '../../src/modules/ai/entity/glossary'; import { NfcDeviceEntity } from '../../src/modules/nfc/entity/device'; import { NfcRuleEntity } from '../../src/modules/nfc/entity/rule'; import { BaseSysDepartmentEntity } from '../../src/modules/base/entity/sys/department'; import { BaseSysUserEntity } from '../../src/modules/base/entity/sys/user'; import { TeamDomainEntity } from '../../src/modules/team/entity/domain'; import { TeamSubscriptionEntity } from '../../src/modules/team/entity/subscription'; import { TeamInvoiceEntity } from '../../src/modules/team/entity/invoice'; import { TeamHandoverLogEntity } from '../../src/modules/team/entity/handover-log'; import { BentoBlockType, BentoColSpan, BentoRowSpan, RadarTrack, StripeWebhookEvent, } from '../../src/comm/types/omni'; // ========================================== // 2. 真实服务类导入 (全真实调用,拒绝 mock 空壳) // ========================================== import { CardBentoService } from '../../src/modules/card/service/bento'; import { CardArticleService } from '../../src/modules/card/service/article'; import { CardLocaleService } from '../../src/modules/card/service/locale'; import { CardCalendarService, generateIcsEvent, parseIcsEvent, } from '../../src/modules/card/service/calendar'; import { CardPortalService } from '../../src/modules/card/service/portal'; import { NfcAntiTamperService } from '../../src/modules/nfc/service/anti-tamper'; import { CrmRadarService } from '../../src/modules/crm/service/radar'; import { CrmRadarPushService, RadarPushPayload } from '../../src/modules/crm/service/push'; import { AiConciergeService } from '../../src/modules/ai/service/concierge'; import { AiKnowledgeService } from '../../src/modules/ai/service/knowledge'; import { AiGlossaryService } from '../../src/modules/ai/service/glossary'; import { AiChatService } from '../../src/modules/ai/service/chat'; import { CrmLeadScoreService } from '../../src/modules/crm/service/lead-score'; import { CrmSyncService } from '../../src/modules/crm/service/sync'; import { CrmLeadPoolService } from '../../src/modules/crm/service/lead-pool'; import { TeamHandoverService } from '../../src/modules/team/service/handover'; import { NfcDeviceService } from '../../src/modules/nfc/service/device'; import { NfcRuleService } from '../../src/modules/nfc/service/rule'; import { NfcSecurityService } from '../../src/modules/nfc/service/security'; import { NfcBatchExportService } from '../../src/modules/nfc/service/batch-export'; import { TeamMembersService } from '../../src/modules/team/service/members'; import { TeamDomainService } from '../../src/modules/team/service/domain'; import { TeamStripeService } from '../../src/modules/team/service/stripe'; import { TeamQuotaService } from '../../src/modules/team/service/quota'; import { TeamInvoiceService } from '../../src/modules/team/service/invoice'; /** * 高保真通用的内存仓储工厂,真实执行增删改查、排序、OR/AND过滤及关联查找 */ class MemoryRepository { public data: T[] = []; private currentId = 1; constructor(initial: Partial[] = []) { this.data = initial.map(item => this.createEntity(item)); if (this.data.length > 0) { this.currentId = Math.max(...this.data.map(d => Number(d.id) || 0)) + 1; } } private createEntity(item: Partial): T { const entity = { id: item.id || this.currentId++, createTime: item.createTime || new Date(), updateTime: new Date(), status: (item as any).status !== undefined ? (item as any).status : 1, ...item, } as unknown as T; return entity; } private matchCondition(item: any, where: any): boolean { if (!where) return true; if (Array.isArray(where)) { return where.some(cond => this.matchCondition(item, cond)); } for (const key of Object.keys(where)) { const expected = where[key]; if (expected === undefined) continue; // 支持 TypeORM 操作符或带 _value 的过滤 if (expected && typeof expected === 'object' && ('_value' in expected || '_type' in expected)) { const val = expected._value !== undefined ? expected._value : expected; if (expected._type === 'moreThanOrEqual' || expected._type === 'moreThan') { if (new Date(item[key]) < new Date(val)) return false; } else if (item[key] !== val) { return false; } } else if (item[key] !== expected) { return false; } } return true; } async find(options?: any): Promise { let result = [...this.data]; if (options?.where) { result = result.filter(item => this.matchCondition(item, options.where)); } if (options?.order) { const orderKeys = Object.keys(options.order); result.sort((a: any, b: any) => { for (const key of orderKeys) { const dir = String(options.order[key] || 'ASC').toUpperCase(); if (a[key] < b[key]) return dir === 'ASC' ? -1 : 1; if (a[key] > b[key]) return dir === 'ASC' ? 1 : -1; } return 0; }); } return result.map(d => ({ ...d })); } async findOne(options?: any): Promise { const list = await this.find(options); return list.length > 0 ? ({ ...list[0] } as T) : null; } async findOneBy(where: any): Promise { return this.findOne({ where }); } async save(entityOrEntities: any): Promise { if (Array.isArray(entityOrEntities)) { const list: T[] = []; for (const item of entityOrEntities) { list.push(await this.save(item)); } return list; } const item = entityOrEntities; if (item.id) { const index = this.data.findIndex(d => Number(d.id) === Number(item.id)); if (index !== -1) { this.data[index] = { ...this.data[index], ...item, updateTime: new Date(), }; return { ...this.data[index] }; } } const newEntity = this.createEntity(item); this.data.push(newEntity); return { ...newEntity }; } async insert(item: any): Promise<{ identifiers: [{ id: any }] }> { const saved = await this.save(item); return { identifiers: [{ id: saved.id }] }; } async count(options?: any): Promise { const list = await this.find(options); return list.length; } async delete(condition: any): Promise<{ affected?: number }> { const initialLen = this.data.length; if (typeof condition === 'number' || typeof condition === 'string') { this.data = this.data.filter(d => Number(d.id) !== Number(condition)); } else if (Array.isArray(condition)) { const idSet = new Set(condition.map(id => Number(id))); this.data = this.data.filter(d => !idSet.has(Number(d.id))); } else if (condition && typeof condition === 'object') { this.data = this.data.filter(item => !this.matchCondition(item, condition)); } return { affected: initialLen - this.data.length }; } async update(criteria: any, partial: any): Promise<{ affected?: number }> { const id = typeof criteria === 'object' ? criteria.id : criteria; const target = this.data.find(d => Number(d.id) === Number(id)); if (target) { if (typeof criteria === 'object') { for (const k of Object.keys(criteria)) { if (target[k] !== criteria[k]) return { affected: 0 }; } } Object.assign(target, partial, { updateTime: new Date() }); return { affected: 1 }; } return { affected: 0 }; } clear(): void { this.data = []; this.currentId = 1; } } describe('OmniCard AI 30-Point End-to-End Pipeline Integration Suite', () => { // 全局持久化数据仓储 (在 6 大主干流各环节中无缝贯通) let cardRepo: MemoryRepository; let bentoRepo: MemoryRepository; let casesRepo: MemoryRepository; let articleRepo: MemoryRepository; let localeRepo: MemoryRepository; let trackRepo: MemoryRepository; let leadRepo: MemoryRepository; let leadAuditRepo: MemoryRepository; let conciergeRepo: MemoryRepository; let glossaryRepo: MemoryRepository; let nfcDeviceRepo: MemoryRepository; let nfcRuleRepo: MemoryRepository; let deptRepo: MemoryRepository; let userRepo: MemoryRepository; let domainRepo: MemoryRepository; let subRepo: MemoryRepository; let invoiceRepo: MemoryRepository; let handoverLogRepo: MemoryRepository; // 业务服务实例 (真实逻辑) let bentoService: CardBentoService; let articleService: CardArticleService; let localeService: CardLocaleService; let calendarService: CardCalendarService; let portalService: CardPortalService; let nfcAntiTamperService: NfcAntiTamperService; let crmRadarService: CrmRadarService; let crmRadarPushService: CrmRadarPushService; let aiConciergeService: AiConciergeService; let aiKnowledgeService: AiKnowledgeService; let aiGlossaryService: AiGlossaryService; let aiChatService: AiChatService; let crmLeadScoreService: CrmLeadScoreService; let crmSyncService: CrmSyncService; let crmLeadPoolService: CrmLeadPoolService; let teamHandoverService: TeamHandoverService; let nfcDeviceService: NfcDeviceService; let nfcRuleService: NfcRuleService; let nfcSecurityService: NfcSecurityService; let nfcBatchExportService: NfcBatchExportService; let teamMembersService: TeamMembersService; let teamDomainService: TeamDomainService; let teamStripeService: TeamStripeService; let teamQuotaService: TeamQuotaService; let teamInvoiceService: TeamInvoiceService; // 全链路贯穿共享上下文数据 (跨流传递) let sharedTenantId: number; let sharedCardId: number; let sharedCardSlug: string; let sharedSalesUserId: number; let sharedSuccessorUserId: number; let sharedNfcUid: string; let sharedNfcSecret: string; let sharedDeviceToken: string; let sharedVisitorId: string; let sharedLeadId: number; beforeAll(() => { // 1. 初始化所有仓储 cardRepo = new MemoryRepository(); bentoRepo = new MemoryRepository(); casesRepo = new MemoryRepository(); articleRepo = new MemoryRepository(); localeRepo = new MemoryRepository(); trackRepo = new MemoryRepository(); leadRepo = new MemoryRepository(); leadAuditRepo = new MemoryRepository(); conciergeRepo = new MemoryRepository(); glossaryRepo = new MemoryRepository(); nfcDeviceRepo = new MemoryRepository(); nfcRuleRepo = new MemoryRepository(); deptRepo = new MemoryRepository(); userRepo = new MemoryRepository(); domainRepo = new MemoryRepository(); subRepo = new MemoryRepository(); invoiceRepo = new MemoryRepository(); handoverLogRepo = new MemoryRepository(); // 2. 初始化各真实业务 Service 并挂载真实仓储 bentoService = new CardBentoService(); bentoService.cardBentoEntity = bentoRepo as any; articleService = new CardArticleService(); articleService.cardArticleEntity = articleRepo as any; localeService = new CardLocaleService(); localeService.cardLocaleEntity = localeRepo as any; calendarService = new CardCalendarService(); portalService = new CardPortalService(); portalService.cardInfoEntity = cardRepo as any; portalService.cardBentoEntity = bentoRepo as any; CardPortalService.clearAllCache(); nfcAntiTamperService = new NfcAntiTamperService(); crmRadarService = new CrmRadarService(); crmRadarService.crmTrackEntity = trackRepo as any; crmRadarPushService = new CrmRadarPushService(); aiConciergeService = new AiConciergeService(); aiConciergeService.aiConciergeEntity = conciergeRepo as any; aiKnowledgeService = new AiKnowledgeService(); aiGlossaryService = new AiGlossaryService(); aiGlossaryService.aiGlossaryEntity = glossaryRepo as any; aiChatService = new AiChatService(); crmLeadScoreService = new CrmLeadScoreService(); crmSyncService = new CrmSyncService(); crmLeadPoolService = new CrmLeadPoolService(); crmLeadPoolService.crmLeadEntity = leadRepo as any; crmLeadPoolService.crmLeadAuditEntity = leadAuditRepo as any; teamHandoverService = new TeamHandoverService(); teamHandoverService.cardInfoEntity = cardRepo as any; teamHandoverService.teamHandoverLogEntity = handoverLogRepo as any; teamHandoverService.crmLeadEntity = leadRepo as any; teamHandoverService.crmLeadAuditEntity = leadAuditRepo as any; nfcDeviceService = new NfcDeviceService(); nfcDeviceService.nfcDeviceEntity = nfcDeviceRepo as any; nfcRuleService = new NfcRuleService(); nfcRuleService.nfcRuleEntity = nfcRuleRepo as any; nfcSecurityService = new NfcSecurityService(); nfcSecurityService.nfcDeviceEntity = nfcDeviceRepo as any; nfcBatchExportService = new NfcBatchExportService(); teamMembersService = new TeamMembersService(); teamMembersService.baseSysDepartmentEntity = deptRepo as any; teamMembersService.baseSysUserEntity = userRepo as any; teamMembersService.cardInfoEntity = cardRepo as any; teamDomainService = new TeamDomainService(); teamDomainService.teamDomainEntity = domainRepo as any; teamStripeService = new TeamStripeService(); teamStripeService.teamSubscriptionEntity = subRepo as any; teamStripeService.webhookSecret = 'whsec_e2e_live_test_secret_key_888'; teamQuotaService = new TeamQuotaService(); teamQuotaService.cardInfoEntity = cardRepo as any; teamQuotaService.baseSysUserEntity = userRepo as any; teamQuotaService.nfcDeviceEntity = nfcDeviceRepo as any; teamInvoiceService = new TeamInvoiceService(); teamInvoiceService.teamInvoiceEntity = invoiceRepo as any; // 基础全局种子变量 sharedTenantId = 1001; sharedSalesUserId = 801; sharedSuccessorUserId = 802; sharedNfcUid = '04:A1:B2:C3:D4:E5:F6'; sharedNfcSecret = 'k9#mZ$8vP!xL2@qR5*wY8^uN1(bC4)eT'; sharedVisitorId = 'vis_dubai_investor_99'; }); // ========================================================================= // 主干流 1:微官网搭建与展品套件 (P01 ~ P08) // 名片 ➔ Bento 布局 ➔ 案例作品 ➔ 博客发布 ➔ 双语发音 ➔ RTL 多语言 ➔ ICS 日历 ➔ SEO OG // ========================================================================= describe('主干流 1: 微官网搭建与展品套件 (Points 01-08)', () => { it('P01: 创建名片基础档案与发布状态', async () => { const newCard = await cardRepo.save({ tenantId: sharedTenantId, userId: sharedSalesUserId, slug: 'alex-zheng', name: 'Alex Zheng', title: 'VP of Global Expansion', company: 'OmniCard Global Inc.', slogan: 'Empowering Global Business Connections', avatarUrl: 'https://cdn.omnicard.ai/avatars/alex.png', phone: '+86-13800138000', email: 'alex@omnicard.ai', whatsapp: '+8613800138000', linkedin: 'https://linkedin.com/in/alex-zheng', timezone: 'Asia/Shanghai', audioPronunciationUrl: 'https://cdn.omnicard.ai/audio/alex-pronunciation.mp3', status: 'published', themeCode: 'corporate_navy', remark: '内部高管名片,国际业务核心', internalConfig: '{"rateLimit": 1000, "debug": false}', }); expect(newCard.id).toBeDefined(); expect(newCard.slug).toBe('alex-zheng'); expect(newCard.status).toBe('published'); sharedCardId = newCard.id; sharedCardSlug = newCard.slug; }); it('P02: Bento Grid 便当盒栅格多维布局编排与物理尺寸自动约束', async () => { const layoutBlocks = [ { type: 'hero' as BentoBlockType, colSpan: 2 as BentoColSpan, rowSpan: 1 as BentoRowSpan, title: '个人简介与身份卡', content: '专注于中东与欧洲数字商务合作', status: 1, }, { type: 'social' as BentoBlockType, colSpan: 1 as BentoColSpan, rowSpan: 1 as BentoRowSpan, title: '商务社交矩阵', content: 'WhatsApp & LinkedIn', status: 1, }, { type: 'case' as BentoBlockType, colSpan: 2 as BentoColSpan, rowSpan: 2 as BentoRowSpan, title: '新能源国际合作案例', content: '协助头部光伏企业落地阿布扎比', status: 1, }, { type: 'article' as BentoBlockType, colSpan: 1 as BentoColSpan, rowSpan: 1 as BentoRowSpan, title: '2026全球数字名片白皮书', content: '深度解读全球智能名片获客漏斗', status: 1, }, ]; // 真实调用 CardBentoService.batchSaveLayout 自动重排与持久化 const savedBlocks = await bentoService.batchSaveLayout(sharedCardId, layoutBlocks); expect(savedBlocks).toHaveLength(4); expect(savedBlocks[0].sortOrder).toBe(0); expect(savedBlocks[0].type).toBe('hero'); expect(savedBlocks[1].sortOrder).toBe(1); expect(savedBlocks[1].type).toBe('social'); // 真实从数据源验证排序 const activeBlocks = await bentoService.getBlocksByCardId(sharedCardId, { status: 1 }); expect(activeBlocks.length).toBe(4); expect(activeBlocks[2].type).toBe('case'); }); it('P03: 展厅案例作品集持久化与 KPI 指标挂载', async () => { const demoCase = await casesRepo.save({ cardId: sharedCardId, title: '阿联酋新能源峰会智能签到', clientName: 'Gulf Solar Tech', metricsKpi: '触碰转化率 +320%', coverImage: 'https://cdn.omnicard.ai/cases/gulf-solar.jpg', mediaUrl: 'https://cdn.omnicard.ai/videos/summit-recap.mp4', status: 1, sortOrder: 1, }); expect(demoCase.id).toBeDefined(); expect(demoCase.metricsKpi).toBe('触碰转化率 +320%'); }); it('P04: 博客文章发布与 SEO OpenGraph 社交卡片自动提炼', async () => { const rawHtmlContent = `

全球获客新引擎:2026 数字名片实战洞察

在全球数字化与国际商务展会场景中,传统的纸质名片不仅浪费印刷成本,且存在高达 88% 的即时丢弃率。通过引入具备 NFC 极速碰触、多语言自适应以及 24 小时 AI 自动留资的智能微官网,销售代表能够在 30 秒内建立品牌信任,并让每一位国际客商的浏览轨迹实时映射在销售雷达上,实现端到端的精准线索转化。

`; const articleEntity = await articleRepo.save({ cardId: sharedCardId, title: '全球获客新引擎:2026 数字名片实战洞察', content: rawHtmlContent, coverImage: 'https://cdn.omnicard.ai/articles/og-banner.png', isPublished: true, }); // 真实调用 CardArticleService 提取 OpenGraph 社交卡片元数据 const ogMeta = articleService.generateOgMeta(articleEntity); expect(ogMeta.title).toBe(articleEntity.title); expect(ogMeta.image).toBe('https://cdn.omnicard.ai/articles/og-banner.png'); expect(ogMeta.description).toContain('在全球数字化与国际商务展会场景中'); expect(ogMeta.description.length).toBeLessThanOrEqual(160); expect(ogMeta.description).not.toContain('

'); }); it('P05: 双语人名读音与语音条配置真实性校验', async () => { const card = await cardRepo.findOneBy({ id: sharedCardId }); expect(card).not.toBeNull(); expect(card?.audioPronunciationUrl).toBe( 'https://cdn.omnicard.ai/audio/alex-pronunciation.mp3' ); expect(card?.audioPronunciationUrl).toMatch(/\.mp3$/i); }); it('P06: RTL 多语言阿语分支独立配置与检索 (ar-AE)', async () => { const arabicLocale = await localeService.upsert({ cardId: sharedCardId, locale: 'ar-AE', name: 'أليكس تشنغ', title: 'نائب الرئيس للتوسع العالمي', company: 'أومني كارد جلوبال', slogan: 'خبير في الحلول الرقمية الذكية للشركات العابرة للحدود', }); expect(arabicLocale.cardId).toBe(sharedCardId); expect(arabicLocale.locale).toBe('ar-AE'); // 真实检索阿语内容 const queried = await localeService.getByCardIdAndLocale(sharedCardId, 'ar-AE'); expect(queried).not.toBeNull(); expect(queried?.title).toBe('نائب الرئيس للتوسع العالمي'); }); it('P07: 国际商务日程 RFC 5545 ICS 日历订阅事件生成与解析', () => { const meetingStart = new Date('2026-10-15T10:00:00.000Z'); const meetingEnd = new Date('2026-10-15T11:00:00.000Z'); const icsString = generateIcsEvent({ uid: 'omnicard-meet-20261015-001@omnicard.ai', summary: 'OmniCard AI 商务合作洽谈会 (Alex Zheng & Dubai Partner)', description: '探讨中东区域独家分销与 NFC 智能硬件批量发行方案。\n请提前 5 分钟接入。', location: '迪拜世界贸易中心 (DWTC) 会议厅 3B', startTime: meetingStart, endTime: meetingEnd, organizer: { name: 'Alex Zheng', email: 'alex@omnicard.ai', }, url: 'https://omnicard.ai/p/alex-zheng', }); expect(icsString).toContain('BEGIN:VCALENDAR'); expect(icsString).toContain('BEGIN:VEVENT'); expect(icsString).toContain('UID:omnicard-meet-20261015-001@omnicard.ai'); expect(icsString).toContain('SUMMARY:OmniCard AI 商务合作洽谈会'); expect(icsString).toContain('END:VEVENT'); expect(icsString).toContain('END:VCALENDAR'); // 真实反向解析校验 const parsed = parseIcsEvent(icsString); expect(parsed.uid).toBe('omnicard-meet-20261015-001@omnicard.ai'); expect(parsed.summary).toContain('Alex Zheng & Dubai Partner'); expect(parsed.location).toContain('DWTC'); }); it('P08: 访客端公开微官网全景聚合输出与敏感信息脱敏校验', async () => { const portalDetail = await portalService.getPortalDetail(sharedCardSlug, 'ar-AE'); expect(portalDetail.id).toBe(sharedCardId); expect(portalDetail.name).toBe('Alex Zheng'); expect(portalDetail.slug).toBe('alex-zheng'); expect(portalDetail.audioPronunciationUrl).toBeDefined(); expect(portalDetail.bentoBlocks.length).toBe(4); expect(portalDetail.suggestedLocale).toBe('ar-AE'); // 安全脱敏校验:内部机密字段严禁暴露给访客前端 expect((portalDetail as any).internalConfig).toBeUndefined(); expect((portalDetail as any).remark).toBeUndefined(); expect((portalDetail as any).userId).toBeUndefined(); }); }); // ========================================================================= // 主干流 2:触卡与访客雷达 (P09 ~ P12) // NFC 防伪验签 ➔ 访客雷达埋点 ➔ 国家分布聚合 ➔ 实时推送 // ========================================================================= describe('主干流 2: 触卡与访客雷达 (Points 09-12)', () => { it('P09: NFC 实体卡动态防伪 HMAC-SHA256 签名生成、验签与防重放', () => { // 1. 生成动态防伪令牌 (时间戳 + HMAC 签名) sharedDeviceToken = nfcAntiTamperService.generateDeviceToken( sharedNfcUid, sharedCardId, sharedNfcSecret ); expect(sharedDeviceToken).toContain('.'); const [ts, sig] = sharedDeviceToken.split('.'); expect(Number(ts)).toBeGreaterThan(0); expect(sig).toHaveLength(64); // 2. 真实首次验签通过 const validRes = nfcAntiTamperService.verifyDeviceToken( sharedNfcUid, sharedCardId, sharedDeviceToken, sharedNfcSecret ); expect(validRes.valid).toBe(true); // 3. 防重放攻击测试:相同令牌二次使用应直接拦截 const replayRes = nfcAntiTamperService.verifyDeviceToken( sharedNfcUid, sharedCardId, sharedDeviceToken, sharedNfcSecret ); expect(replayRes.valid).toBe(false); expect(replayRes.reason).toMatch(/重放|已使用/); // 4. 恶意篡改拦截:篡改 UID 进行验签被拦截并入安全审计日志 const fakeToken = nfcAntiTamperService.generateDeviceToken( sharedNfcUid, sharedCardId, sharedNfcSecret ); const tamperedRes = nfcAntiTamperService.verifyDeviceToken( '04:FF:FF:FF:FF:FF:FF', sharedCardId, fakeToken, sharedNfcSecret ); expect(tamperedRes.valid).toBe(false); expect(tamperedRes.reason).toMatch(/签名无效|篡改/); const logs = nfcAntiTamperService.getAuditLogs(); expect(logs.length).toBeGreaterThanOrEqual(2); }); it('P10: 访客雷达实时足迹埋点与 IP 地理位置推断 (recordTrack)', async () => { // 模拟迪拜买家触碰 NFC 卡后产生的浏览埋点 const track1 = await crmRadarService.recordTrack({ cardId: sharedCardId, visitorId: sharedVisitorId, ip: '194.170.1.1', country: 'United Arab Emirates', city: 'Dubai', touchType: 'nfc', stayDurationMs: 320000, // 320 秒 (深度停留) scrollDepth: 95, viewedBlocks: ['hero', 'social', 'case', 'article'], tenantId: sharedTenantId, }); expect(track1.id).toBeDefined(); expect(track1.touchType).toBe('nfc'); expect(track1.country).toBe('United Arab Emirates'); expect(track1.stayDurationMs).toBe(320000); // 模拟另外两名国际访客浏览 (新加坡与美国) await crmRadarService.recordTrack({ cardId: sharedCardId, visitorId: 'vis_sg_01', ip: '202.166.1.1', country: 'Singapore', city: 'Singapore', touchType: 'qr', stayDurationMs: 150000, scrollDepth: 70, viewedBlocks: ['hero', 'case'], tenantId: sharedTenantId, }); await crmRadarService.recordTrack({ cardId: sharedCardId, visitorId: 'vis_us_02', ip: '8.8.8.8', country: 'United States', city: 'Mountain View', touchType: 'link', stayDurationMs: 45000, scrollDepth: 50, viewedBlocks: ['hero'], tenantId: sharedTenantId, }); }); it('P11: 全球雷达热力分布与多国访客聚合统计', async () => { const distribution = await crmRadarService.getCountryDistribution(sharedCardId); expect(distribution).toHaveLength(3); // 验证国家与人次统计 const countries = distribution.map(d => d.country); expect(countries).toContain('United Arab Emirates'); expect(countries).toContain('Singapore'); expect(countries).toContain('United States'); // 验证平均停留时长最高者为阿联酋访客 (320000ms) const uaeItem = distribution.find(d => d.country === 'United Arab Emirates'); expect(uaeItem?.avgStayDurationMs).toBe(320000); expect(uaeItem?.percentage).toBeCloseTo(33.33, 0); }); it('P12: 实时雷达 Push 广播通知流订阅与派发', done => { const sampleTrack: RadarTrack = { id: 99, tenantId: sharedTenantId, cardId: sharedCardId, visitorId: sharedVisitorId, ip: '194.170.1.1', country: 'United Arab Emirates', city: 'Dubai', touchType: 'nfc', stayDurationMs: 320000, scrollDepth: 95, viewedBlocks: ['hero', 'case'], createdAt: new Date().toISOString(), }; // 销售管家端订阅该名片的雷达事件 crmRadarPushService.subscribe(sharedCardId, (payload: RadarPushPayload) => { expect(payload.event).toBe('radar.visitor'); expect(payload.cardId).toBe(sharedCardId); expect(payload.country).toBe('United Arab Emirates'); expect(payload.touchType).toBe('nfc'); expect(payload.stayDurationMs).toBe(320000); done(); }); // 触发雷达广播推送 crmRadarPushService.publish(sampleTrack); }); }); // ========================================================================= // 主干流 3:AI 智能中枢 (P13 ~ P17) // 人设配置 ➔ RAG 切片与沙盒召回 ➔ 术语库润色 ➔ 流式对话与留资卡片 // ========================================================================= describe('主干流 3: AI 智能中枢 (Points 13-17)', () => { it('P13: 名片 AI 顾问人设、基调与留资触发阈值配置', async () => { const config = await aiConciergeService.saveByCardId(sharedCardId, { personaType: 'trade_expert', welcomeMsg: '您好!我是 Alex 的 AI 商务顾问,请问有什么可以协助您的?', triggerLeadThreshold: 75, promptTemplate: 'You are an export trade consultant specialized in clean energy solutions.', toneStyle: 'professional', isEnabled: true, }); expect(config.cardId).toBe(sharedCardId); expect(config.personaType).toBe('trade_expert'); expect(config.triggerLeadThreshold).toBe(75); const queried = await aiConciergeService.getByCardId(sharedCardId); expect(queried?.welcomeMsg).toContain('Alex 的 AI 商务顾问'); }); it('P14: 知识库 RAG 滑动窗口文本分块与重叠切片算法', () => { const rawDocText = ` OmniCard AI 致力于为全球商务团队提供次世代数字名片与微官网解决方案。 支持中东、欧洲及北美地区的主流商务支付与 CRM 系统无缝互联。 我们针对新能源、智能硬件及全球电商客户提供定制化的展会获客方案, 配合实体 NFC 智能卡片,可实现秒级留资并自动将高意向线索同步至公海池。 `.trim(); const chunks = aiKnowledgeService.splitTextToChunks(rawDocText, 50, 15); expect(chunks.length).toBeGreaterThan(1); expect(chunks[0].index).toBe(0); expect(chunks[0].length).toBeLessThanOrEqual(50); // 验证滑动重叠覆盖 expect(chunks[0].content.slice(-15)).toBe(chunks[1].content.slice(0, 15)); }); it('P15: 知识库沙盒召回模拟与关键词相似度排序', () => { const testChunks = [ { index: 0, content: '公司主营产品包含新能源储能电站与光伏逆变器全球解决方案。', length: 30, }, { index: 1, content: '在中东阿联酋及沙特地区,我们提供 7x24 小时的当地运维支持。', length: 31, }, { index: 2, content: '欧洲市场支持符合 GDPR 的数据合规导出与多语言商务发票。', length: 29, }, ]; // 访客搜索:“中东阿联酋支持” const query = '中东 阿联酋 支持'; const results = aiKnowledgeService.simulateRetrieval(query, testChunks, 2); expect(results).toHaveLength(2); // 第一个命中必须是第 1 块 (中东阿联酋运维支持) expect(results[0].index).toBe(1); expect(results[0].content).toContain('中东阿联酋'); expect(results[0].score).toBeGreaterThan(0.5); }); it('P16: 行业专有术语库 (Glossary) 导入与国际多语言润色替换', async () => { const importRes = await aiGlossaryService.batchImport([ { sourceTerm: 'digital business card', targetTerm: '智能数字名片', sourceLang: 'en', targetLang: 'zh-CN', domain: 'tech', notes: '品牌核心定义', }, { sourceTerm: 'NFC card fleet', targetTerm: 'NFC 智能卡舰队', sourceLang: 'en', targetLang: 'zh-CN', domain: 'hardware', }, ]); expect(importRes.inserted).toBe(2); // 真实调用术语库替换 const replaced = await aiGlossaryService.replaceTerms( 'Welcome to our digital business card solution', 'zh-CN', 'tech' ); expect(replaced).toContain('智能数字名片'); }); it('P17: AI 顾问流式对话意图识别与高意向留资卡片引导', async () => { // 访客询问价格与合作方案 const reply = await aiChatService.ask({ cardId: sharedCardId, locale: 'zh-CN', messages: [ { role: 'user', content: '请问你们 Enterprise 企业版怎么报价?我们想洽谈合作。' }, ], }); expect(reply.shouldCollectLead).toBe(true); expect(reply.reply).toContain('留下您的姓名与联系方式'); }); }); // ========================================================================= // 主干流 4:客户 CRM 转化 (P18 ~ P22) // 多维加权意向算分 ➔ 外部 CRM 推送 ➔ 销售公海池流转 ➔ 离职交接 // ========================================================================= describe('主干流 4: 客户 CRM 转化 (Points 18-22)', () => { it('P18: 多维加权意向算分引擎 (CrmLeadScoreService)', () => { // 结合前面访客行为计算加权得分: // 停留 >300s (stayScore: 40) + 浏览4个模块 (moduleScore: 20) + 交互全触发 (interactionScore: 40) = 100分 const scoreResult = crmLeadScoreService.calculateScore({ stayDurationSeconds: 320, viewedBlockCount: 4, clickedWhatsApp: true, downloadedVCard: true, submittedLead: true, }); expect(scoreResult.score).toBe(100); expect(scoreResult.level).toBe('Hot'); expect(scoreResult.breakdown.stayScore).toBe(40); expect(scoreResult.breakdown.moduleScore).toBe(20); expect(scoreResult.breakdown.interactionScore).toBe(80); }); it('P19: 沉淀高意向潜客全景画像建档', async () => { const lead = await leadRepo.save({ cardId: sharedCardId, userId: sharedSalesUserId, // 初始归属于销售 Alex name: 'Tariq Al-Mansoor', company: 'Gulf Horizon Trading LLC', phone: '+971-50-9876543', email: 'tariq@gulfhorizon.ae', intentScore: 95, status: 'hot', source: 'nfc', isPool: false, lastFollowUpTime: new Date(), notes: '阿联酋大客户,重点推进企业微官网采购', }); expect(lead.id).toBeDefined(); expect(lead.intentScore).toBe(95); expect(lead.status).toBe('hot'); sharedLeadId = lead.id; }); it('P20: 国际主流外部 CRM (HubSpot & Salesforce) 格式映射与 Webhook 签名生成', () => { const currentLead = leadRepo.data.find(l => l.id === sharedLeadId); expect(currentLead).toBeDefined(); // 1. HubSpot Contacts v3 契约转换 const hubspotPayload = crmSyncService.formatToHubSpot(currentLead!); expect(hubspotPayload.properties.email).toBe('tariq@gulfhorizon.ae'); expect(hubspotPayload.properties.firstname).toBe('Tariq'); expect(hubspotPayload.properties.lastname).toBe('Al-Mansoor'); expect(hubspotPayload.properties.company).toBe('Gulf Horizon Trading LLC'); expect(hubspotPayload.properties.lifecyclestage).toBe('lead'); // 2. Salesforce Lead sObject 契约转换 const sfPayload = crmSyncService.formatToSalesforce(currentLead!); expect(sfPayload.FirstName).toBe('Tariq'); expect(sfPayload.LastName).toBe('Al-Mansoor'); expect(sfPayload.Email).toBe('tariq@gulfhorizon.ae'); expect(sfPayload.Status).toBe('Open - Not Contacted'); // 3. Webhook 签名生成与验证 (HMAC-SHA256) const webhookSecret = 'crm_webhook_secret_key_999'; const webhookSignature = crmSyncService.generateSignature( JSON.stringify(currentLead), webhookSecret ); expect(webhookSignature).toHaveLength(64); }); it('P21: 团队公海池流转与 CAS 销售抢单认领', async () => { // 1. 销售将线索释放退回到企业公海池 const poolResult = await crmLeadPoolService.recycleToPool( sharedLeadId, '转交团队公海供区域销售抢单' ); expect(poolResult.isPool).toBe(true); expect(poolResult.userId).toBeNull(); // 验证产生审计日志 const auditsAfterPool = leadAuditRepo.data.filter(a => a.leadId === sharedLeadId); expect(auditsAfterPool.some(a => a.action === 'recycle')).toBe(true); // 2. 另一位新接手销售 (sharedSuccessorUserId) 抢单捞取线索 const claimed = await crmLeadPoolService.claimLead(sharedLeadId, sharedSuccessorUserId); expect(claimed.isPool).toBe(false); expect(claimed.userId).toBe(sharedSuccessorUserId); // 验证抢单后不可被重复认领 await expect( crmLeadPoolService.claimLead(sharedLeadId, 9999) ).rejects.toThrow(/已被其他销售认领|不在公海池/); }); it('P22: 员工离职资产交接工单、短链安全重定向与历史线索迁移', async () => { // 模拟原销售 Alex (userId: 801) 离职,交接给接任者 (userId: 802) // 名片短链重定向到接替人的名片,历史线索全部迁移 const handoverLog = await teamHandoverService.executeHandover({ fromUserId: sharedSalesUserId, toUserId: sharedSuccessorUserId, cardId: sharedCardId, redirectType: 'card', redirectTarget: 'successor-card-slug', leadAction: 'transfer', operatorId: 1, remark: 'Alex 正常离职交接,客户与名片一键转接给 Bob', }); expect(handoverLog.id).toBeDefined(); expect(handoverLog.handoverNo).toMatch(/^HO\d+/); expect(handoverLog.status).toBe('completed'); // 验证名片重定向配置生效 const updatedCard = await cardRepo.findOneBy({ id: sharedCardId }); expect(updatedCard?.redirectType).toBe('card'); expect(updatedCard?.redirectTarget).toBe('successor-card-slug'); expect(updatedCard?.userId).toBe(sharedSuccessorUserId); }); }); // ========================================================================= // 主干流 5:NFC 硬件生命周期 (P23 ~ P26) // 设备入库 ➔ 场景规则匹配 ➔ 远程挂失解冻 ➔ 批量制卡 Zip 导出 // ========================================================================= describe('主干流 5: NFC 硬件生命周期 (Points 23-26)', () => { it('P23: NFC 实体卡设备台账入库与绑定', async () => { const device = await nfcDeviceRepo.save({ tenantId: sharedTenantId, uid: sharedNfcUid, cardId: sharedCardId, batchNo: 'EXPO-2026-DXB', material: 'metal', status: 'bound', sceneMode: 'business', }); expect(device.id).toBeDefined(); expect(device.uid).toBe(sharedNfcUid); expect(device.material).toBe('metal'); expect(device.status).toBe('bound'); }); it('P24: 场景化动态时间规则匹配与名片路由 (NfcRuleService)', async () => { // 配置规则:工作日 09:00 - 18:00 路由到商务微官网 (cardId: 101) // 其他非工作时段路由到个人社交名片 (cardId: 202) await nfcRuleRepo.save({ tenantId: sharedTenantId, ruleName: '日间商务展会模式', targetCardId: sharedCardId, sceneMode: 'business', startTime: '09:00', endTime: '18:00', enabled: true, }); await nfcRuleRepo.save({ tenantId: sharedTenantId, ruleName: '晚间晚宴社交模式', targetCardId: 202, sceneMode: 'social', startTime: '19:00', endTime: '23:59', enabled: true, }); // 1. 模拟上午 11:30 触碰卡片 const dayResult = await nfcRuleService.resolveScene({ tenantId: sharedTenantId, now: '2026-09-21T11:30:00', }); expect(dayResult).not.toBeNull(); expect(dayResult?.targetCardId).toBe(sharedCardId); expect(dayResult?.sceneMode).toBe('business'); // 2. 模拟晚上 21:00 触碰卡片 const nightResult = await nfcRuleService.resolveScene({ tenantId: sharedTenantId, now: '2026-09-21T21:00:00', }); expect(nightResult).not.toBeNull(); expect(nightResult?.targetCardId).toBe(202); expect(nightResult?.sceneMode).toBe('social'); }); it('P25: NFC 实体卡远程一键挂失冻结与解冻恢复 (NfcSecurityService)', async () => { // 1. 模拟卡片在展会遗失,远程一键挂失冻结 const reportLostRes = await nfcSecurityService.freeze({ uid: sharedNfcUid }); expect(reportLostRes.status).toBe('suspended'); // 查库验证设备状态被冻结为 suspended const lostDevice = await nfcDeviceRepo.findOneBy({ uid: sharedNfcUid }); expect(lostDevice?.status).toBe('suspended'); // 2. 找回卡片后远程解冻,恢复绑定正常状态 (active) const recoverRes = await nfcSecurityService.unfreeze({ uid: sharedNfcUid }); expect(recoverRes.status).toBe('active'); const activeDevice = await nfcDeviceRepo.findOneBy({ uid: sharedNfcUid }); expect(activeDevice?.status).toBe('active'); }); it( 'P26: 展会批量制卡清单 CSV 与二维码离线 ZIP 压缩包归档导出', async () => { const mockBatchDevices = [ { uid: sharedNfcUid, slug: sharedCardSlug, cardTitle: 'Alex Zheng - VP Global', }, { uid: '04:11:22:33:44:55:66', slug: 'bob-engineer', cardTitle: 'Bob Successor - Lead Dev', }, ]; const batchResult = await nfcBatchExportService.generateBatchPack( 'EXPO-2026-DXB', mockBatchDevices ); expect(batchResult.totalCount).toBe(2); expect(Buffer.isBuffer(batchResult.zipBuffer)).toBe(true); expect(batchResult.zipBuffer.length).toBeGreaterThan(0); // 解压验证 ZIP 归档包结构 const zip = new AdmZip(batchResult.zipBuffer); const zipEntries = zip.getEntries(); expect(zipEntries.length).toBeGreaterThanOrEqual(3); // 校验存在 manifest.csv 清单文件 const manifestEntry = zipEntries.find(e => e.entryName.includes('manifest.csv')); expect(manifestEntry).toBeDefined(); const csvText = manifestEntry?.getData().toString('utf8'); expect(csvText).toContain('出厂防伪码'); expect(csvText).toContain(sharedNfcUid); expect(csvText).toContain('Alex Zheng - VP Global'); }, 15000); }); // ========================================================================= // 主干流 6:多租户与商业化 (P27 ~ P30) // 组织架构与名片分配 ➔ 独立域名与 SSL 校验 ➔ Stripe 订阅 Webhook ➔ 坐席配额核销 ➔ 国际多币种发票 // ========================================================================= describe('主干流 6: 多租户与商业化 (Points 27-30)', () => { it('P27: 组织架构部门树多层级递归与成员名片分配 (TeamMembersService)', async () => { // 1. 初始化部门树结构:总部 -> 国际销售中心 -> 中东拓展组 const headDept = await deptRepo.save({ name: '全球总部', parentId: null as any, orderNum: 1, }); const salesDept = await deptRepo.save({ name: '全球拓展事业群', parentId: headDept.id, orderNum: 2, }); const meDept = await deptRepo.save({ name: '中东战区组', parentId: salesDept.id, orderNum: 3, }); // 2. 获取树形结构验证 const tree = await teamMembersService.getDepartmentTree(); expect(tree.length).toBeGreaterThanOrEqual(1); const rootNode = tree.find(t => t.id === headDept.id); expect(rootNode).toBeDefined(); expect(rootNode?.children?.length).toBeGreaterThanOrEqual(1); // 3. 将销售成员 Bob 挂载到中东战区组并分配专属名片 await userRepo.save({ id: sharedSuccessorUserId, name: 'Bob Successor', username: 'bob_global', departmentId: meDept.id, status: 1, }); const assigned = await teamMembersService.assignCard({ memberId: sharedSuccessorUserId, cardId: sharedCardId, }); expect(assigned.cardId).toBe(sharedCardId); expect(assigned.memberId).toBe(sharedSuccessorUserId); }); it('P28: 企业独立二级域名绑定向导与 SSL 证书有效性状态检测 (TeamDomainService)', async () => { const domainName = 'card.omnicard-global.com'; // 1. 域名规范严格正则校验 (拦截非法端口、协议与保留地址) const validCheck = teamDomainService.validateDomainFormat(domainName); expect(validCheck.valid).toBe(true); const invalidCheck = teamDomainService.validateDomainFormat('http://bad.domain.com:8080'); expect(invalidCheck.valid).toBe(false); // 2. 绑定域名与生成 CNAME 解析目标 const bound = await domainRepo.save({ tenantId: sharedTenantId, customDomain: domainName, cnameTarget: 'cname.omnicard.ai', sslStatus: 'pending', }); expect(bound.customDomain).toBe(domainName); expect(bound.cnameTarget).toBe('cname.omnicard.ai'); // 3. SSL 证书校验检测 const sslResult = await teamDomainService.checkSslCertificate(domainName, 443, { mockCert: { issuer: "Let's Encrypt Authority X3", validFrom: new Date(Date.now() - 30 * 86400000).toISOString(), validTo: new Date(Date.now() + 60 * 86400000).toISOString(), authorized: true, }, }); expect(sslResult.valid).toBe(true); expect(sslResult.daysRemaining).toBeGreaterThan(0); expect(sslResult.issuer).toBe("Let's Encrypt Authority X3"); }); it('P29: Stripe 国际信用卡结算会话创建与 Webhook 自动化入账 (TeamStripeService)', async () => { // 1. 创建 Enterprise 年付结算会话 const session = await teamStripeService.createCheckoutSession({ tenantId: sharedTenantId, plan: 'enterprise', billingCycle: 'yearly', }); expect(session.amount).toBe(99000); // $990.00 / 年 expect(session.sessionId).toMatch(/^cs_test_/); expect(session.url).toContain(session.sessionId); // 2. 模拟 Stripe 官方 Webhook 支付成功通知 const webhookPayload: StripeWebhookEvent = { id: 'evt_stripe_payment_2026_001', type: 'checkout.session.completed', created: Math.floor(Date.now() / 1000), data: { object: { id: session.sessionId, client_reference_id: String(sharedTenantId), customer: 'cus_stripe_dubai_001', subscription: 'sub_enterprise_live_001', metadata: { tenantId: String(sharedTenantId), plan: 'enterprise', billingCycle: 'yearly', }, }, }, }; // 签名生成与真实 Webhook 处理 const headerTimestamp = Math.floor(Date.now() / 1000); const rawPayloadString = JSON.stringify(webhookPayload); const hmac = crypto.createHmac('sha256', teamStripeService.webhookSecret); hmac.update(`${headerTimestamp}.${rawPayloadString}`, 'utf8'); const signature = hmac.digest('hex'); const stripeSigHeader = `t=${headerTimestamp},v1=${signature}`; const verified = teamStripeService.verifyWebhookSignature( rawPayloadString, stripeSigHeader ); expect(verified).toBe(true); // 处理入账流转 const handleResult = await teamStripeService.handleWebhookEvent(webhookPayload); expect(handleResult.success).toBe(true); expect(handleResult.status).toBe('active'); expect(handleResult.plan).toBe('enterprise'); // 校验租户订阅数据已真实写入 const subscription = await subRepo.findOneBy({ tenantId: sharedTenantId }); expect(subscription?.status).toBe('active'); expect(subscription?.planType).toBe('enterprise'); }); it('P30: 坐席配额核销、80% 警戒线预警、升舱差价折算与国际多币种标准发票', async () => { // 1. 配额检查:企业版已用 85 个坐席 / 上限 100 个 -> 触发 80% 警戒线 const quotaCheck = await teamQuotaService.checkQuota(sharedTenantId, 'seats', { plan: 'enterprise', currentUsed: 85, }); expect(quotaCheck.limit).toBe(100); expect(quotaCheck.used).toBe(85); expect(quotaCheck.percentage).toBe(85); expect(quotaCheck.warning).toBe(true); // 超过 80% 预警 expect(quotaCheck.exceeded).toBe(false); // 2. 套餐升级差价折算 (Pro 升级至 Enterprise,剩余 180 天 / 全年 365 天) const proration = teamQuotaService.calculateUpgradeProration( 'pro', 'enterprise', 180, 365 ); expect(proration.upgradeDiffAmount).toBeGreaterThan(0); expect(proration.targetPlan).toBe('enterprise'); // 3. 国际多币种发票生成与税额精确拆解 const taxCalc = teamInvoiceService.calculateTaxAndTotals({ subtotal: 990.0, taxRate: 0.05, // 5% 增值税 isTaxInclusive: false, }); expect(taxCalc.subtotal).toBe(990.0); expect(taxCalc.taxAmount).toBe(49.5); expect(taxCalc.totalAmount).toBe(1039.5); const invoiceDate = new Date('2026-09-20'); const invoiceNo = teamInvoiceService.generateInvoiceNumber(invoiceDate, 1); expect(invoiceNo).toBe('INV-20260920-0001'); const invoice = await teamInvoiceService.generateInvoice(sharedTenantId, { orderId: 'ORDER-2026-STRIPE-001', currency: 'USD', subtotal: 990.0, taxRate: 0.05, title: 'OmniCard Enterprise Annual Subscription (100 Seats)', items: [ { description: 'Enterprise Plan (Yearly)', quantity: 1, unitPrice: 990.0, amount: 990.0, }, ], billingInfo: { companyName: 'Gulf Horizon Trading LLC', taxId: 'AE-VAT-1002938481', email: 'tariq@gulfhorizon.ae', address: 'Downtown Dubai, UAE', }, }); expect(invoice.id).toBeDefined(); expect(invoice.invoiceNo).toMatch(/^INV-\d{8}-\d{4}$/); expect(invoice.currency).toBe('USD'); expect(invoice.currencySymbol).toBe('$'); expect(invoice.totalAmount).toBe(1039.5); expect(invoice.status).toBe('paid'); expect(invoice.htmlTemplate).toContain('INV-'); expect(invoice.textTemplate).toContain('Gulf Horizon Trading LLC'); }); }); });