forked from wangziqi/gongxue-base
feat: add platform audit alerts
This commit is contained in:
@@ -1232,6 +1232,8 @@ async function testPlatformTenantOperationsAndAudit() {
|
||||
assert.ok(actions.has('platform.tenant.created'), 'platform audit should include tenant creation');
|
||||
assert.ok(actions.has('platform.tenant.billing_profile_upserted'), 'platform audit should include billing profile update');
|
||||
assert.ok(actions.has('platform.tenant.status_updated'), 'platform audit should include status update');
|
||||
const statusAuditLog = audit.items?.find(item => item.action === 'platform.tenant.status_updated');
|
||||
assert.ok(statusAuditLog?.id, 'platform tenant status audit should expose audit log id');
|
||||
|
||||
const auditExport = await request('/api/platform-admin/audit-logs/export', {
|
||||
tenantId: false,
|
||||
@@ -1253,6 +1255,92 @@ async function testPlatformTenantOperationsAndAudit() {
|
||||
});
|
||||
assert.equal(invalidAuditExportFormat.code, 'INVALID_EXPORT_FORMAT', 'platform audit export should reject invalid formats');
|
||||
|
||||
const alertRules = await request('/api/platform-admin/audit-alert-rules', {
|
||||
tenantId: false,
|
||||
userId: false,
|
||||
headers: adminHeaders,
|
||||
query: { enabled: true },
|
||||
});
|
||||
const statusAlertRule = alertRules.items?.find(item => item.code === 'platform_tenant_status_changed');
|
||||
assert.ok(statusAlertRule?.id, 'platform audit alert rules should include default tenant status rule');
|
||||
assert.ok(
|
||||
alertRules.items?.some(item => item.code === 'platform_invoice_payment_confirmed'),
|
||||
'platform audit alert rules should include manual payment confirmation rule',
|
||||
);
|
||||
|
||||
const alertPool = new pg.Pool({ connectionString: process.env.DATABASE_URL || DEFAULT_DATABASE_URL });
|
||||
let platformAlertId = '';
|
||||
try {
|
||||
const insertedAlert = await alertPool.query(
|
||||
`
|
||||
insert into public.platform_audit_alerts (
|
||||
rule_id, audit_log_id, tenant_id, severity, status,
|
||||
action, target_type, target_id, title, summary, details,
|
||||
first_seen_at, last_seen_at
|
||||
)
|
||||
values (
|
||||
$1, $2, $3::uuid, 'high', 'open',
|
||||
'platform.tenant.status_updated', 'tenant', $4,
|
||||
'租户状态变更告警', 'integration platform audit alert',
|
||||
'{"source":"api-integration-test","accessToken":"must-not-leak","nested":{"password":"must-not-leak"}}'::jsonb,
|
||||
now(), now()
|
||||
)
|
||||
on conflict (rule_id, audit_log_id) do update
|
||||
set status = 'open',
|
||||
updated_at = now()
|
||||
returning id
|
||||
`,
|
||||
[statusAlertRule.id, statusAuditLog.id, tenantId, tenantId],
|
||||
);
|
||||
platformAlertId = insertedAlert.rows[0].id;
|
||||
} finally {
|
||||
await alertPool.end();
|
||||
}
|
||||
|
||||
const auditAlerts = await request('/api/platform-admin/audit-alerts', {
|
||||
tenantId: false,
|
||||
userId: false,
|
||||
headers: adminHeaders,
|
||||
query: { tenantId, status: 'open', limit: 20 },
|
||||
});
|
||||
assert.ok(
|
||||
auditAlerts.items?.some(item => item.id === platformAlertId && item.ruleCode === 'platform_tenant_status_changed'),
|
||||
'platform admin should list platform audit alerts',
|
||||
);
|
||||
const listedAlert = auditAlerts.items?.find(item => item.id === platformAlertId);
|
||||
assert.equal(listedAlert?.details?.accessToken, '[REDACTED]', 'platform audit alert list should redact token-like details');
|
||||
assert.equal(listedAlert?.details?.nested?.password, '[REDACTED]', 'platform audit alert list should redact nested password-like details');
|
||||
assert.ok(!JSON.stringify(listedAlert).includes('must-not-leak'), 'platform audit alert list must not leak sensitive details');
|
||||
|
||||
const updatedAuditAlert = await request('/api/platform-admin/audit-alerts/status', {
|
||||
tenantId: false,
|
||||
userId: false,
|
||||
headers: adminHeaders,
|
||||
method: 'POST',
|
||||
body: {
|
||||
alertId: platformAlertId,
|
||||
status: 'resolved',
|
||||
resolutionNote: 'integration resolved',
|
||||
},
|
||||
});
|
||||
assert.equal(updatedAuditAlert.item?.status, 'resolved', 'platform admin should resolve audit alerts');
|
||||
assert.equal(updatedAuditAlert.item?.resolutionNote, 'integration resolved', 'audit alert resolution note should persist');
|
||||
assert.equal(updatedAuditAlert.item?.details?.accessToken, '[REDACTED]', 'audit alert status response should redact token-like details');
|
||||
assert.ok(!JSON.stringify(updatedAuditAlert).includes('must-not-leak'), 'audit alert status response must not leak sensitive details');
|
||||
|
||||
const invalidAuditAlertStatus = await request('/api/platform-admin/audit-alerts/status', {
|
||||
tenantId: false,
|
||||
userId: false,
|
||||
headers: adminHeaders,
|
||||
method: 'POST',
|
||||
body: {
|
||||
alertId: platformAlertId,
|
||||
status: 'open',
|
||||
},
|
||||
expectStatus: 400,
|
||||
});
|
||||
assert.equal(invalidAuditAlertStatus.code, 'INVALID_ALERT_STATUS', 'audit alerts should not be reopened through status endpoint');
|
||||
|
||||
const candidates = await request('/api/platform-admin/invoices/subscription-candidates', {
|
||||
tenantId: false,
|
||||
userId: false,
|
||||
@@ -1448,6 +1536,25 @@ async function testPlatformTenantOperationsAndAudit() {
|
||||
});
|
||||
assert.equal(studentAuditExportDenied.code, 'PLATFORM_ADMIN_REQUIRED', 'student must not export platform audit logs');
|
||||
|
||||
const studentAuditAlertDenied = await request('/api/platform-admin/audit-alerts', {
|
||||
tenantId: false,
|
||||
userId: USER_ID,
|
||||
expectStatus: 403,
|
||||
});
|
||||
assert.equal(studentAuditAlertDenied.code, 'PLATFORM_ADMIN_REQUIRED', 'student must not read platform audit alerts');
|
||||
|
||||
const studentAuditAlertStatusDenied = await request('/api/platform-admin/audit-alerts/status', {
|
||||
tenantId: false,
|
||||
userId: USER_ID,
|
||||
method: 'POST',
|
||||
body: {
|
||||
alertId: platformAlertId,
|
||||
status: 'acknowledged',
|
||||
},
|
||||
expectStatus: 403,
|
||||
});
|
||||
assert.equal(studentAuditAlertStatusDenied.code, 'PLATFORM_ADMIN_REQUIRED', 'student must not update platform audit alerts');
|
||||
|
||||
const studentReminderDenied = await request('/api/platform-admin/invoices/reminders', {
|
||||
tenantId: false,
|
||||
userId: USER_ID,
|
||||
|
||||
Reference in New Issue
Block a user