import * as crypto from 'crypto'; import { CrmSyncService } from '../../../src/modules/crm/service/sync'; import { CrmRadarPushService } from '../../../src/modules/crm/service/push'; import { AdminCrmLeadController } from '../../../src/modules/crm/controller/admin/lead'; import { CrmLeadEntity } from '../../../src/modules/crm/entity/lead'; import { RadarTrack } from '../../../src/comm/types/omni'; describe('【BTP-20】线索流转自动化与外部 Webhook / 实时推送集成测试', () => { let syncService: CrmSyncService; let pushService: CrmRadarPushService; let mockLead: CrmLeadEntity; beforeEach(() => { syncService = new CrmSyncService(); pushService = new CrmRadarPushService(); mockLead = new CrmLeadEntity(); mockLead.id = 101; mockLead.name = 'John Doe'; mockLead.company = 'Acme Global Corp'; mockLead.email = 'john.doe@acme.com'; mockLead.phone = '+1-555-0199'; mockLead.status = 'hot'; mockLead.intentScore = 88; mockLead.source = 'nfc'; }); it('1. Webhook HMAC-SHA256 签名生成与防伪防篡改验证', () => { const secret = 'whsec_omnicard_lead_flow_test_2026'; const payload = { event: 'crm.lead.status_changed', timestamp: 1789924567, data: { leadId: mockLead.id, previousStatus: 'new', currentStatus: 'hot', lead: mockLead, }, }; const signature = syncService.generateSignature(payload, secret); expect(signature).toBeDefined(); expect(signature).toHaveLength(64); expect(/^[a-f0-9]{64}$/.test(signature)).toBe(true); // 篡改测试 const tamperedPayload = { ...payload, timestamp: 1789924568 }; const tamperedSignature = syncService.generateSignature(tamperedPayload, secret); expect(tamperedSignature).not.toBe(signature); }); it('2. 国际 CRM 契约格式转换 (HubSpot & Salesforce)', () => { const hubspot = syncService.formatToHubSpot(mockLead); expect(hubspot.properties.firstname).toBe('John'); expect(hubspot.properties.lastname).toBe('Doe'); expect(hubspot.properties.company).toBe('Acme Global Corp'); expect(hubspot.properties.lifecyclestage).toBe('lead'); const sf = syncService.formatToSalesforce(mockLead); expect(sf.FirstName).toBe('John'); expect(sf.LastName).toBe('Doe'); expect(sf.Company).toBe('Acme Global Corp'); expect(sf.Status).toBe('Open - Not Contacted'); }); it('3. 雷达实时 Push 渠道广播与定向名片监听机制 (CrmRadarPushService)', () => { const received: any[] = []; pushService.subscribe(101, (p) => { received.push(p); }); const track: RadarTrack = { id: 1, tenantId: 1, cardId: 101, visitorId: 'vis_dubai_101', ip: '185.220.101.5', country: 'AE', city: 'Dubai', touchType: 'nfc', stayDurationMs: 15400, scrollDepth: 85, viewedBlocks: ['hero', 'form', 'contact'], createdAt: new Date().toISOString(), }; const pushResult = pushService.publish(track); expect(pushResult.event).toBe('radar.visitor'); expect(pushResult.cardId).toBe(101); expect(received).toHaveLength(1); expect(received[0].visitorId).toBe('vis_dubai_101'); expect(received[0].country).toBe('AE'); expect(received[0].city).toBe('Dubai'); }); it('4. 控制器同步接口 syncExternal 校验与 Webhook 调用', async () => { const mockRepo: any = { findOne: jest.fn().mockResolvedValue(mockLead), }; const controller = new AdminCrmLeadController(); controller.crmLeadEntity = mockRepo; controller.crmSyncService = syncService; // 模拟目标 Webhook URL 发送 jest.spyOn(syncService, 'sendWebhook').mockResolvedValueOnce({ success: true, attempts: 1, data: { received: true }, }); const res: any = await controller.syncExternal({ id: 101, target: 'webhook', webhookUrl: 'https://api.omnicard.ai/webhook/lead-flow', webhookSecret: 'secret_lead_flow_2026', }); expect(res.code).toBe(1000); expect(res.data.target).toBe('webhook'); expect(res.data.leadId).toBe(101); expect(res.data.result.success).toBe(true); expect(res.data.result.attempts).toBe(1); }); });