test: add first-version coverage

This commit is contained in:
hectorzhao
2026-07-01 14:46:35 +08:00
parent 924457a48e
commit 91d1e38a09
13 changed files with 5484 additions and 1 deletions
@@ -0,0 +1,213 @@
import { RiskReviewService } from './risk-review.service';
function createPrismaMock(overrides: Record<string, unknown> = {}) {
return {
riskRule: {
findFirst: jest.fn().mockResolvedValue({ id: 'default-rule' }),
create: jest.fn(),
findMany: jest.fn().mockResolvedValue([]),
},
globalBlacklist: {
findMany: jest.fn().mockResolvedValue([]),
},
enterpriseBlacklist: {
findMany: jest.fn().mockResolvedValue([]),
},
smsApplication: {
findUnique: jest.fn().mockResolvedValue(null),
},
smsTemplate: {
findUnique: jest.fn().mockResolvedValue(null),
},
smsSendTask: {
count: jest.fn().mockResolvedValue(0),
create: jest.fn().mockImplementation(({ data }: { data: Record<string, unknown> }) =>
Promise.resolve({ id: 'risk-task-1', ...data }),
),
findUnique: jest.fn().mockResolvedValue({ id: 'risk-task-1', riskHits: [] }),
update: jest.fn(),
findMany: jest.fn(),
},
riskHitRecord: {
createMany: jest.fn(),
findMany: jest.fn(),
},
...overrides,
};
}
describe('RiskReviewService', () => {
it('rejects tasks over the application max phone threshold', async () => {
const prisma = createPrismaMock();
prisma.smsApplication.findUnique.mockResolvedValue({ id: 'app-1', maxPhonesPerTask: 2 });
prisma.riskRule.findMany.mockResolvedValue([
{
id: 'rule-max',
code: 'MAX_PHONES_PER_TASK',
name: '单任务最大号码数',
metric: 'phoneTotal',
thresholdValue: 100000,
action: 'block',
priority: 10,
},
]);
prisma.smsSendTask.findUnique.mockResolvedValue({
id: 'risk-task-1',
status: 'rejected',
riskHits: [{ ruleCode: 'MAX_PHONES_PER_TASK' }],
});
const service = new RiskReviewService(prisma as never);
const result = await service.evaluateTask({
tenantId: 'tenant-1',
applicationId: 'app-1',
content: 'hello',
phones: ['13800000001', '13800000002', '13800000003'],
});
expect(result.canSubmit).toBe(false);
expect(result.status).toBe('rejected');
expect(prisma.riskHitRecord.createMany).toHaveBeenCalledWith({
data: [expect.objectContaining({ ruleCode: 'MAX_PHONES_PER_TASK', actualValue: 3, action: 'block' })],
});
});
it('routes duplicate and blacklist ratio hits to manual review', async () => {
const prisma = createPrismaMock();
prisma.globalBlacklist.findMany.mockResolvedValue([{ phoneNumber: '13800000001' }]);
prisma.riskRule.findMany.mockResolvedValue([
{
id: 'rule-dup',
code: 'DUPLICATE_PHONE_RATIO',
name: '重复号码比例',
metric: 'duplicateRatio',
thresholdValue: 0.2,
action: 'manual_review',
priority: 20,
},
{
id: 'rule-black',
code: 'BLACKLIST_HIT_RATIO',
name: '黑名单命中比例',
metric: 'blacklistHitRatio',
thresholdValue: 0.2,
action: 'manual_review',
priority: 40,
},
]);
const service = new RiskReviewService(prisma as never);
const result = await service.evaluateTask({
tenantId: 'tenant-1',
content: 'hello',
phones: ['13800000001', '13800000001', '13800000002'],
});
expect(result.status).toBe('pending_review');
expect(prisma.smsSendTask.create).toHaveBeenCalledWith({
data: expect.objectContaining({
duplicateRatio: 0.3333,
blacklistHitRatio: 0.3333,
status: 'pending_review',
riskDecision: 'manual_review',
}),
});
expect(prisma.riskHitRecord.createMany).toHaveBeenCalledWith({
data: expect.arrayContaining([
expect.objectContaining({ ruleCode: 'DUPLICATE_PHONE_RATIO' }),
expect.objectContaining({ ruleCode: 'BLACKLIST_HIT_RATIO' }),
]),
});
});
it('rejects illegal phone and template variable anomalies', async () => {
const prisma = createPrismaMock();
prisma.smsTemplate.findUnique.mockResolvedValue({
id: 'tpl-1',
category: 'notice',
variables: [{ name: 'code', required: true }],
});
prisma.riskRule.findMany.mockResolvedValue([
{
id: 'rule-illegal',
code: 'ILLEGAL_PHONE_RATIO',
name: '非法号码比例',
metric: 'illegalRatio',
thresholdValue: 0.1,
action: 'block',
priority: 30,
},
{
id: 'rule-var',
code: 'TEMPLATE_VARIABLE_ANOMALY',
name: '模板变量异常',
metric: 'variableIssueCount',
thresholdValue: 0,
action: 'block',
priority: 70,
},
]);
const service = new RiskReviewService(prisma as never);
const result = await service.evaluateTask({
tenantId: 'tenant-1',
templateId: 'tpl-1',
content: '验证码 ${code}',
phones: ['13800000001', 'not-a-phone'],
variables: { extra: 'value' },
});
expect(result.status).toBe('rejected');
expect(prisma.smsSendTask.create).toHaveBeenCalledWith({
data: expect.objectContaining({
illegalRatio: 0.5,
variableIssues: expect.arrayContaining([
{ type: 'missing_required_variable', name: 'code' },
{ type: 'unexpected_variable', name: 'extra' },
]),
}),
});
});
it('marks non-working marketing bulk and frequent task creation for manual review', async () => {
const prisma = createPrismaMock();
prisma.smsSendTask.count.mockResolvedValue(11);
prisma.riskRule.findMany.mockResolvedValue([
{
id: 'rule-night',
code: 'NON_WORKING_MARKETING_BULK',
name: '非工作时间大批量营销发送',
metric: 'nonWorkingMarketingPhones',
thresholdValue: 2,
action: 'manual_review',
priority: 50,
},
{
id: 'rule-frequency',
code: 'TASK_CREATE_FREQUENCY',
name: '短时间任务创建频控',
metric: 'recentTaskCount',
thresholdValue: 10,
action: 'manual_review',
priority: 60,
},
]);
const service = new RiskReviewService(prisma as never);
const result = await service.evaluateTask({
tenantId: 'tenant-1',
category: 'marketing',
content: 'promo',
phones: ['13800000001', '13800000002', '13800000003'],
requestedAt: '2026-07-01T22:00:00+08:00',
});
expect(result.status).toBe('pending_review');
expect(prisma.riskHitRecord.createMany).toHaveBeenCalledWith({
data: expect.arrayContaining([
expect.objectContaining({ ruleCode: 'NON_WORKING_MARKETING_BULK', actualValue: 3 }),
expect.objectContaining({ ruleCode: 'TASK_CREATE_FREQUENCY', actualValue: 11 }),
]),
});
});
});