feat(round-16): AI意图情绪分析/展会名片双向互换/雷达爬虫清洗/离线多媒体缓存 (103套件1101测试全绿)

这个提交包含在:
wikiboss
2026-09-21 02:17:06 +08:00
父节点 e6d06f762c
当前提交 8c003b70cd
共有 8 个文件被更改,包括 805 次插入 和 0 次删除

查看文件

@@ -0,0 +1,70 @@
import {
AiSentimentIntentAnalyzer,
analyzeConversationIntent,
analyzeConversationSentiment,
ConversationAnalysisResult,
} from '../../../src/modules/ai/service/sentiment-intent';
describe('Point 61 (P1): AI 对话客商意图与情绪极性分析引擎', () => {
let analyzer: AiSentimentIntentAnalyzer;
beforeEach(() => {
analyzer = new AiSentimentIntentAnalyzer();
});
describe('1. analyzeConversationIntent (出海商务意图识别)', () => {
it('包含价格、报价、起订量或购买关键词应识别为 PURCHASE_INTENT', () => {
const msgs = [
'Hello, what is the MOQ for 500 NFC metal cards?',
'Can you provide a wholesale quotation and delivery time to Dubai?',
];
const intent = analyzeConversationIntent(msgs);
expect(intent.primaryIntent).toBe('PURCHASE_INTENT');
expect(intent.confidence).toBeGreaterThanOrEqual(0.8);
});
it('包含代理、渠道分销或合作伙伴意向应识别为 PARTNER_INQUIRY', () => {
const msgs = [
'We are a leading distributor in Saudi Arabia.',
'Interested in becoming your regional partner and reseller.',
];
const intent = analyzeConversationIntent(msgs);
expect(intent.primaryIntent).toBe('PARTNER_INQUIRY');
});
it('普通闲聊应识别为 GENERAL_CHAT', () => {
const msgs = ['Hi there', 'Just browsing your digital card showcase'];
const intent = analyzeConversationIntent(msgs);
expect(intent.primaryIntent).toBe('GENERAL_CHAT');
});
});
describe('2. analyzeConversationSentiment (客商商业情绪极性分析)', () => {
it('热情积极、赞美或明确赞同应识别为 positive', () => {
const msgs = ['Great product! Exactly what our international sales team needs.'];
const sentiment = analyzeConversationSentiment(msgs);
expect(sentiment.polarity).toBe('positive');
expect(sentiment.score).toBeGreaterThan(0.5);
});
it('抱怨、价格过贵或批评应识别为 negative', () => {
const msgs = ['Too expensive, not happy with the slow response.', 'Bad experience.'];
const sentiment = analyzeConversationSentiment(msgs);
expect(sentiment.polarity).toBe('negative');
expect(sentiment.score).toBeLessThan(0);
});
});
describe('3. AiSentimentIntentAnalyzer 综合画像报告与 CRM 加分', () => {
it('高价值积极意向对话应输出线索加权建议 (+25~35分)', async () => {
const result = await analyzer.evaluateConversationSession([
'We want to purchase 1,000 smart cards for our Dubai exhibition.',
'Looking forward to signing the contract next week.',
]);
expect(result.intent.primaryIntent).toBe('PURCHASE_INTENT');
expect(result.sentiment.polarity).toBe('positive');
expect(result.recommendedLeadScoreBoost).toBeGreaterThanOrEqual(25);
});
});
});

查看文件

@@ -0,0 +1,63 @@
import {
CardMutualExchangeService,
createMutualExchangeInvitation,
acceptMutualExchangeInvitation,
MutualExchangeInvitation,
} from '../../../src/modules/card/service/mutual-exchange';
describe('Point 62 (P1): 展会名片双向互换与 CRM 互加好友验证', () => {
let service: CardMutualExchangeService;
beforeEach(() => {
service = new CardMutualExchangeService();
});
describe('1. 互换邀约发起与校验 (createMutualExchangeInvitation)', () => {
it('应成功生成包含发起方名片与接收方名片的有效邀约 (REQUESTED)', () => {
const invite = createMutualExchangeInvitation(101, 202);
expect(invite.id).toBeDefined();
expect(invite.senderCardId).toBe(101);
expect(invite.recipientCardId).toBe(202);
expect(invite.status).toBe('REQUESTED');
expect(invite.exchangeCode).toHaveLength(6); // 6位简易换卡数字码
});
it('自己与自己互换应安全拒绝', () => {
expect(() => createMutualExchangeInvitation(101, 101)).toThrow(/不能与自己互换/);
});
});
describe('2. 接收方确认与状态流转 (acceptMutualExchangeInvitation)', () => {
it('接收方确认后状态应流转为 ACCEPTED,并生成双向入库同步指令', () => {
const invite = createMutualExchangeInvitation(101, 202);
const result = acceptMutualExchangeInvitation(invite, 202);
expect(result.invitation.status).toBe('ACCEPTED');
expect(result.syncActions.length).toBe(2);
expect(result.syncActions[0].targetCardId).toBe(101); // 同步到 101 的 CRM
expect(result.syncActions[1].targetCardId).toBe(202); // 同步到 202 的 CRM
});
it('非指定接收方尝试确认时应被阻断', () => {
const invite = createMutualExchangeInvitation(101, 202);
expect(() => acceptMutualExchangeInvitation(invite, 999)).toThrow(/无权操作/);
});
});
describe('3. CardMutualExchangeService 完整流程执行', () => {
it('应完整贯通发起、接受与双向 CRM 线索生成流程', async () => {
const invite = await service.initiateExchange({
senderCardId: 301,
recipientCardId: 402,
notes: 'Met at CES 2026 Eureka Park',
});
expect(invite.status).toBe('REQUESTED');
const outcome = await service.confirmExchange(invite.id, 402);
expect(outcome.status).toBe('ACCEPTED');
expect(outcome.isDualSynced).toBe(true);
});
});
});

查看文件

@@ -0,0 +1,77 @@
import {
RadarBotFilterService,
detectBotUserAgent,
isRateLimitSuspicious,
TrafficFilterResult,
} from '../../../src/modules/crm/service/bot-filter';
describe('Point 63 (P2): 访客雷达异常流量与出海社交抓取机器爬虫清洗过滤', () => {
let service: RadarBotFilterService;
beforeEach(() => {
service = new RadarBotFilterService();
});
describe('1. detectBotUserAgent (爬虫与社交卡片抓取器识别)', () => {
it('应精准识别 Googlebot、Bingbot 与主流搜索引擎爬虫', () => {
const googleUa = 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)';
const res = detectBotUserAgent(googleUa);
expect(res.isBot).toBe(true);
expect(res.botType).toBe('SEARCH_CRAWLER');
});
it('应精准识别 WhatsApp 与 Twitter/LinkedIn 社交预览卡片抓取器', () => {
const waUa = 'WhatsApp/2.21.12.21 A';
const waRes = detectBotUserAgent(waUa);
expect(waRes.isBot).toBe(true);
expect(waRes.botType).toBe('SOCIAL_PREVIEW');
const twUa = 'Twitterbot/1.0';
const twRes = detectBotUserAgent(twUa);
expect(twRes.isBot).toBe(true);
expect(twRes.botType).toBe('SOCIAL_PREVIEW');
});
it('应拦截 curl、Python Requests、Postman 等自动化脚本工具', () => {
expect(detectBotUserAgent('python-requests/2.28.1').isBot).toBe(true);
expect(detectBotUserAgent('curl/7.68.0').isBot).toBe(true);
});
it('真实手机移动端浏览器访问应判定为正常用户 (isBot = false)', () => {
const mobileUa = 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1';
const res = detectBotUserAgent(mobileUa);
expect(res.isBot).toBe(false);
expect(res.botType).toBeUndefined();
});
});
describe('2. isRateLimitSuspicious (超高频空刷异常检测)', () => {
it('1秒内发生超过 10 次访问请求应判定为异常刷量', () => {
const timestamps = Array(12).fill(Date.now());
expect(isRateLimitSuspicious(timestamps)).toBe(true);
});
it('正常人类浏览间隔应判定为合法流量', () => {
const timestamps = [Date.now() - 5000, Date.now() - 2000, Date.now()];
expect(isRateLimitSuspicious(timestamps)).toBe(false);
});
});
describe('3. RadarBotFilterService 综合过滤清洗', () => {
it('应标记机器人流量并决定是否计入 CRM 真实意向得分', async () => {
const botTraffic = await service.filterIncomingTraffic({
ip: '66.249.66.1',
userAgent: 'Googlebot/2.1',
});
expect(botTraffic.shouldRecordLead).toBe(false);
expect(botTraffic.category).toBe('CRAWLER');
const humanTraffic = await service.filterIncomingTraffic({
ip: '94.200.12.34',
userAgent: 'Mozilla/5.0 (Linux; Android 14) Chrome/120.0 Mobile Safari/537.36',
});
expect(humanTraffic.shouldRecordLead).toBe(true);
expect(humanTraffic.category).toBe('HUMAN');
});
});
});

查看文件

@@ -0,0 +1,76 @@
/**
* OmniCard AI (CRMmp) - 移动端多媒体离线缓存持久化策略单元测试
* 严格遵循 TDD 规范
*/
describe('Point 64 (P2): 移动端名片离线多媒体缓存与本地持久化策略 (Media Cache)', () => {
let cacheMod: any;
let storageMap: Record<string, string>;
beforeEach(() => {
storageMap = {};
(global as any).uni = {
getStorageSync: jest.fn((key: string) => storageMap[key] || ''),
setStorageSync: jest.fn((key: string, val: string) => {
storageMap[key] = String(val);
}),
removeStorageSync: jest.fn((key: string) => {
delete storageMap[key];
}),
};
// @ts-ignore
cacheMod = require('../../../mpuniapp/common/media-cache.uts');
cacheMod.clearAllMediaCache();
});
afterEach(() => {
delete (global as any).uni;
});
describe('1. 缓存存储与命中获取 (saveMediaToCache & getCachedMedia)', () => {
const remoteUrl = 'https://cdn.omnicard.ai/avatars/alex.png';
const localPath = '_doc/cache/avatars/alex.png';
it('写入缓存后应能成功命中并返回本地文件路径', () => {
cacheMod.saveMediaToCache(remoteUrl, localPath, 24); // 24小时有效
const hit = cacheMod.getCachedMedia(remoteUrl);
expect(hit).toBeDefined();
expect(hit.localPath).toBe(localPath);
expect(hit.isExpired).toBe(false);
});
it('未缓存的 URL 查询应返回 null', () => {
const miss = cacheMod.getCachedMedia('https://cdn.omnicard.ai/not-cached.png');
expect(miss).toBeNull();
});
});
describe('2. 缓存过期淘汰机制 (pruneExpiredMediaCache)', () => {
it('已过期的缓存条目应被自动清理淘汰', () => {
const oldUrl = 'https://cdn.omnicard.ai/old.png';
cacheMod.saveMediaToCache(oldUrl, '_doc/old.png', -1); // 传入负数表示已过期
const beforePrune = cacheMod.getCachedMedia(oldUrl);
expect(beforePrune.isExpired).toBe(true);
const prunedCount = cacheMod.pruneExpiredMediaCache();
expect(prunedCount).toBe(1);
// 清理后再次获取应为 null
expect(cacheMod.getCachedMedia(oldUrl)).toBeNull();
});
});
describe('3. 缓存容量上限保护', () => {
it('应提供当前缓存条目数量统计', () => {
cacheMod.saveMediaToCache('https://cdn.omnicard.ai/1.png', '_doc/1.png');
cacheMod.saveMediaToCache('https://cdn.omnicard.ai/2.png', '_doc/2.png');
const stats = cacheMod.getMediaCacheStats();
expect(stats.totalEntries).toBe(2);
});
});
});