feat: add points risk report

This commit is contained in:
Codex
2026-06-30 15:12:32 +08:00
parent a5a2caa2e9
commit 87f4cf9c4e
15 changed files with 522 additions and 25 deletions

View File

@@ -8601,6 +8601,54 @@ async function testPointActivitiesAndExchange() {
});
assert.ok(exchangeOrders.items?.some(item => item.userId === USER_ID), 'tenant admin should list exchange orders');
const riskReport = await request('/api/tenant-admin/points-risk-report', {
userId: TENANT_ADMIN_USER_ID,
query: {
timeRange: '30d',
highEarnedThreshold: 10,
highClaimThreshold: 1,
highEventPointsThreshold: 10,
highRedeemThreshold: 10,
limit: 20,
},
});
assert.equal(riskReport.item?.range?.timeRange, '30d', 'points risk report should echo range');
assert.ok(riskReport.item?.summary?.eventCount >= 2, 'points risk report should include point ledger summary');
assert.ok(
riskReport.item?.eventBreakdown?.some(item => item.eventType === 'activity_reward'),
'points risk report should include activity reward breakdown',
);
assert.ok(
riskReport.item?.suspiciousUsers?.some(item => item.userId === USER_ID && item.riskFlags?.includes('high_earned_points')),
'points risk report should flag high earned point users',
);
assert.ok(
riskReport.item?.suspiciousEvents?.some(item => item.sourceId === ids.pointActivityTask),
'points risk report should flag large activity reward events',
);
assert.ok(
riskReport.item?.suspiciousTasks?.some(item => item.taskId === ids.pointActivityTask),
'points risk report should flag high-frequency point tasks',
);
assert.ok(
riskReport.item?.suspiciousExchanges?.some(item => item.itemId === ids.pointExchangeItem),
'points risk report should flag high-cost point exchanges',
);
const invalidRiskRange = await request('/api/tenant-admin/points-risk-report', {
userId: TENANT_ADMIN_USER_ID,
query: { timeRange: '365d' },
expectStatus: 400,
});
assert.equal(invalidRiskRange.code, 'INVALID_POINTS_RISK_RANGE', 'points risk report should reject unsupported ranges');
const partnerRiskDenied = await request('/api/tenant-admin/points-risk-report', {
tenantId: PARTNER_TENANT_ID,
userId: TENANT_ADMIN_USER_ID,
expectStatus: 403,
});
assert.equal(partnerRiskDenied.code, 'TENANT_ADMIN_REQUIRED', 'points risk report must be tenant isolated');
const partnerExchangeDenied = await request('/api/tenant-admin/point-exchange-items', {
tenantId: PARTNER_TENANT_ID,
userId: TENANT_ADMIN_USER_ID,
@@ -8616,6 +8664,7 @@ async function testTenantMemberPermissionsAndAudit() {
assert.ok(permissionMatrix.permissions?.some(item => item.key === 'marketing:write'), 'permission matrix should expose marketing permission');
assert.ok(permissionMatrix.permissions?.some(item => item.key === 'marketing:points:read'), 'permission matrix should expose point marketing read permission');
assert.ok(permissionMatrix.permissions?.some(item => item.key === 'marketing:points:write'), 'permission matrix should expose point marketing write permission');
assert.ok(permissionMatrix.permissions?.some(item => item.key === 'marketing:points:risk:read'), 'permission matrix should expose point risk report read permission');
assert.ok(permissionMatrix.permissions?.some(item => item.key === 'notifications:read'), 'permission matrix should expose user notification read permission');
assert.ok(permissionMatrix.permissions?.some(item => item.key === 'dashboard:read'), 'permission matrix should expose dashboard read permission');
assert.ok(permissionMatrix.permissions?.some(item => item.key === 'roles:write'), 'permission matrix should expose role template permission');