- 模块一: 出海案例作品集、博客动态发布、人名发音语音条、RTL阿语多语言切换 - 模块二: AI顾问人设配置、行业术语库润色、RAG切片沙盒调试、流式会话与意向留资卡片 - 模块三: 全球访客雷达大屏、多维加权意向算分、外部CRM对接推送、团队公海池流转 - 模块四: 移动端NFC安全挂失、NFC动态路由规则配置、动态防伪验签、展会制卡Zip批量导出 - 模块五: 组织架构与名片分配、移动端个人中心设置、离职交接与历史线索迁移、安全审计日志 - 模块六: Stripe国际信用卡订阅Webhook、独立二级域名与SSL检测、坐席配额预警、多币种发票 - 模块七: 纸质名片OCR解析入库、国际商务日程RFC 5545 ICS日历、微官网离线SWR预加载、展会实时排行榜大屏 - 模块八: 全球出海SEO OpenGraph元标签注入与社交爬虫探测、30点端到端全链路业务流集成测试套件
109 行
3.1 KiB
TypeScript
109 行
3.1 KiB
TypeScript
import {
|
|
CrmRadarPushService,
|
|
RadarPushPayload,
|
|
} from '../../../src/modules/crm/service/push';
|
|
import { RadarTrack } from '../../../src/comm/types/omni';
|
|
|
|
function makeTrack(overrides: Partial<RadarTrack> = {}): RadarTrack {
|
|
return {
|
|
id: 1,
|
|
tenantId: 10,
|
|
cardId: 101,
|
|
visitorId: 'vis_abc_123',
|
|
ip: '8.8.8.8',
|
|
country: 'US',
|
|
city: 'Mountain View',
|
|
touchType: 'nfc',
|
|
stayDurationMs: 12000,
|
|
scrollDepth: 40,
|
|
viewedBlocks: ['hero'],
|
|
createdAt: '2026-09-20T10:00:00.000Z',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('Task 35: 雷达 Push 通知内存渠道 (CrmRadarPushService)', () => {
|
|
const payloadKeys: Array<keyof RadarPushPayload> = [
|
|
'event',
|
|
'cardId',
|
|
'visitorId',
|
|
'country',
|
|
'city',
|
|
'touchType',
|
|
'stayDurationMs',
|
|
];
|
|
|
|
let service: CrmRadarPushService;
|
|
|
|
beforeEach(() => {
|
|
service = new CrmRadarPushService();
|
|
});
|
|
|
|
it('buildPayload 字段齐全且 event 为 radar.visitor', () => {
|
|
const track = makeTrack();
|
|
const payload = service.buildPayload(track);
|
|
|
|
for (const key of payloadKeys) {
|
|
expect(payload).toHaveProperty(key);
|
|
}
|
|
expect(Object.keys(payload).sort()).toEqual([...payloadKeys].sort());
|
|
expect(payload.event).toBe('radar.visitor');
|
|
expect(payload.cardId).toBe(track.cardId);
|
|
expect(payload.visitorId).toBe(track.visitorId);
|
|
expect(payload.country).toBe(track.country);
|
|
expect(payload.city).toBe(track.city);
|
|
expect(payload.touchType).toBe(track.touchType);
|
|
expect(payload.stayDurationMs).toBe(track.stayDurationMs);
|
|
});
|
|
|
|
it('subscribe 后 publish 能收到对应名片的载荷', () => {
|
|
const received: RadarPushPayload[] = [];
|
|
const track = makeTrack({ cardId: 202, visitorId: 'vis_sub' });
|
|
|
|
service.subscribe(202, payload => {
|
|
received.push(payload);
|
|
});
|
|
service.publish(track);
|
|
|
|
expect(received).toHaveLength(1);
|
|
expect(received[0].event).toBe('radar.visitor');
|
|
expect(received[0].cardId).toBe(202);
|
|
expect(received[0].visitorId).toBe('vis_sub');
|
|
});
|
|
|
|
it('不同 cardId 的订阅互不串扰', () => {
|
|
const cardA: RadarPushPayload[] = [];
|
|
const cardB: RadarPushPayload[] = [];
|
|
|
|
service.subscribe(301, payload => {
|
|
cardA.push(payload);
|
|
});
|
|
service.subscribe('302', payload => {
|
|
cardB.push(payload);
|
|
});
|
|
|
|
service.publish(makeTrack({ cardId: 301, visitorId: 'vis_a' }));
|
|
service.publish(makeTrack({ cardId: 302, visitorId: 'vis_b' }));
|
|
|
|
expect(cardA).toHaveLength(1);
|
|
expect(cardA[0].visitorId).toBe('vis_a');
|
|
expect(cardB).toHaveLength(1);
|
|
expect(cardB[0].visitorId).toBe('vis_b');
|
|
});
|
|
|
|
it('unsubscribe 后不再收到后续 publish', () => {
|
|
const received: RadarPushPayload[] = [];
|
|
const listener = (payload: RadarPushPayload) => {
|
|
received.push(payload);
|
|
};
|
|
|
|
service.subscribe(401, listener);
|
|
service.publish(makeTrack({ cardId: 401, visitorId: 'vis_before' }));
|
|
service.unsubscribe(401, listener);
|
|
service.publish(makeTrack({ cardId: 401, visitorId: 'vis_after' }));
|
|
|
|
expect(received).toHaveLength(1);
|
|
expect(received[0].visitorId).toBe('vis_before');
|
|
});
|
|
});
|