feat: aggregate cmpp template mismatch reviews

This commit is contained in:
hectorzhao
2026-07-12 13:25:19 +08:00
parent c8b6d0aa7c
commit 8b6ec92f4f
17 changed files with 523 additions and 38 deletions
@@ -33,16 +33,65 @@ function createPrismaMock(overrides: Record<string, unknown> = {}) {
findUnique: jest.fn().mockResolvedValue({ id: 'risk-task-1', riskHits: [] }),
update: jest.fn(),
findMany: jest.fn(),
upsert: jest.fn().mockImplementation(({ create }: { create: Record<string, unknown> }) => Promise.resolve({ id: 'review-task-1', ...create })),
},
smsMessageRecord: {
update: jest.fn().mockResolvedValue({ id: 'message-1' }),
},
riskHitRecord: {
createMany: jest.fn(),
findMany: jest.fn(),
},
$transaction: jest.fn(async (callback) => callback({
smsSendTask: {
upsert: jest.fn().mockImplementation(({ create }: { create: Record<string, unknown> }) => Promise.resolve({ id: 'review-task-1', ...create })),
},
smsMessageRecord: {
update: jest.fn().mockResolvedValue({ id: 'message-1' }),
},
})),
...overrides,
};
}
describe('RiskReviewService', () => {
it('groups identical CMPP template mismatches into a deterministic short review window', async () => {
const prisma = createPrismaMock();
prisma.smsSendTask.findUnique.mockResolvedValue({
id: 'review-task-1',
sourceType: 'cmpp_template_mismatch',
phoneTotal: 2,
_count: { messageRecords: 2 },
riskHits: [],
});
const service = new RiskReviewService(prisma as never);
const result = await service.aggregateTemplateMismatch({
tenantId: 'tenant-1',
applicationId: 'app-1',
account: '100001',
messageRecordId: 'message-1',
signatureId: 'sig-1',
content: '【签名】同一审核内容',
});
expect(prisma.$transaction).toHaveBeenCalledTimes(1);
expect(result).toEqual(expect.objectContaining({ sourceType: 'cmpp_template_mismatch', phoneTotal: 2 }));
});
it('does not allow an aggregated review task to be decided before its window closes', async () => {
const prisma = createPrismaMock();
prisma.smsSendTask.findUnique.mockResolvedValue({
id: 'review-task-1',
sourceType: 'cmpp_template_mismatch',
windowEndsAt: new Date(Date.now() + 10_000),
});
const service = new RiskReviewService(prisma as never);
await expect(service.approveTask('review-task-1', { reason: '通过' })).rejects.toThrow('聚合窗口尚未关闭');
expect(prisma.smsSendTask.update).not.toHaveBeenCalled();
});
it('rejects tasks over the application max phone threshold', async () => {
const prisma = createPrismaMock();
prisma.smsApplication.findUnique.mockResolvedValue({ id: 'app-1', maxPhonesPerTask: 2 });