补齐平台订阅、外部登录、CRM 同步与用户端页面;Nginx 转发用户端接口,登录页使用 Google 与 Apple 标志。 Co-authored-by: Cursor <cursoragent@cursor.com>
127 行
4.6 KiB
TypeScript
127 行
4.6 KiB
TypeScript
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<number, any>();
|
|
(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(
|
|
/未找到该名片互换邀约/
|
|
);
|
|
});
|
|
});
|
|
});
|