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); expect(invite.persisted).toBe(false); expect(outcome.persisted).toBe(false); }); it('仅内存 Map 保存时,创建与确认结果均应带 persisted:false', async () => { // 无仓库注入:不得假装已入库 expect((service as any).exchangeEntity).toBeUndefined(); const invite = await service.initiateExchange({ senderCardId: 501, recipientCardId: 602, }); expect(invite.persisted).toBe(false); const outcome = await service.confirmExchange(invite.id, 602); expect(outcome.status).toBe('ACCEPTED'); expect(outcome.persisted).toBe(false); }); it('有仓库且写入成功时,创建与确认结果均应带 persisted:true', async () => { let nextId = 1001; const store = new Map(); (service as any).exchangeEntity = { save: async (row: any) => { if (!row.id) { row.id = nextId++; } store.set(Number(row.id), { ...row }); return row; }, findOne: async ({ where }: { where: { id?: number } }) => { if (where?.id == null) return null; const hit = store.get(Number(where.id)); return hit ? { ...hit } : null; }, }; const invite = await service.initiateExchange({ senderCardId: 701, recipientCardId: 802, }); expect(invite.persisted).toBe(true); expect(invite.id).toBe('1001'); const outcome = await service.confirmExchange(invite.id, 802); expect(outcome.status).toBe('ACCEPTED'); expect(outcome.persisted).toBe(true); }); it('找不到邀约时应报错,不得伪造成功互换', async () => { await expect( service.confirmExchange('xchg_missing_nowhere', 402) ).rejects.toThrow(/未找到该名片互换邀约/); }); it('有仓库但库中与内存均无此邀约时,确认应失败而非造一条成功互换', async () => { (service as any).exchangeEntity = { findOne: async () => null, save: async (row: any) => row, }; await expect(service.confirmExchange('99999', 402)).rejects.toThrow( /未找到该名片互换邀约/ ); }); }); });