文件
OmniCard/backend/test/modules/crm/btp-20-push-webhook.test.ts

124 行
4.2 KiB
TypeScript

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);
});
});