forked from wangziqi/gongxue-base
feat: add platform audit alerts
This commit is contained in:
142
scripts/platform-audit-alert-worker-integration-test.js
Normal file
142
scripts/platform-audit-alert-worker-integration-test.js
Normal file
@@ -0,0 +1,142 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import pg from 'pg';
|
||||
import { spawn } from 'node:child_process';
|
||||
|
||||
const databaseUrl = process.env.DATABASE_URL || 'postgresql://postgres:postgres@127.0.0.1:54322/postgres';
|
||||
|
||||
const ids = {
|
||||
tenant: '00000000-0000-0000-0000-00000000aa01',
|
||||
auditLog: '00000000-0000-0000-0000-00000000aa02',
|
||||
};
|
||||
|
||||
function runWorkerOnce() {
|
||||
const child = spawn(process.execPath, ['apps/worker/dist/apps/worker/src/index.js', '--once', '--job', 'platform-audit-alerts'], {
|
||||
cwd: process.cwd(),
|
||||
env: {
|
||||
...process.env,
|
||||
DATABASE_URL: databaseUrl,
|
||||
WORKER_PLATFORM_AUDIT_ALERT_BATCH_SIZE: '20',
|
||||
WORKER_PLATFORM_AUDIT_ALERT_LOOKBACK_DAYS: '30',
|
||||
WORKER_PLATFORM_AUDIT_ALERT_ID: 'platform-audit-alert-worker-test',
|
||||
},
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
windowsHide: true,
|
||||
});
|
||||
let output = '';
|
||||
child.stdout.on('data', chunk => {
|
||||
output += chunk.toString();
|
||||
});
|
||||
child.stderr.on('data', chunk => {
|
||||
output += chunk.toString();
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
child.on('error', reject);
|
||||
child.on('exit', code => {
|
||||
try {
|
||||
assert.equal(code, 0, `worker should exit 0\n${output}`);
|
||||
assert.match(output, /platform-audit-alerts batch processed=\d+/, 'worker output should include audit alert summary');
|
||||
resolve(output);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function cleanup(pool) {
|
||||
await pool.query('delete from public.platform_audit_alerts where tenant_id = $1 or audit_log_id = $2', [ids.tenant, ids.auditLog]);
|
||||
await pool.query('delete from public.audit_logs where tenant_id = $1 or id = $2', [ids.tenant, ids.auditLog]);
|
||||
await pool.query('delete from public.tenant_billing_profiles where tenant_id = $1', [ids.tenant]);
|
||||
await pool.query('delete from public.tenant_domains where tenant_id = $1', [ids.tenant]);
|
||||
await pool.query('delete from public.tenants where id = $1', [ids.tenant]);
|
||||
}
|
||||
|
||||
async function createAuditLog(pool) {
|
||||
await pool.query(
|
||||
`
|
||||
insert into public.tenants (id, slug, name, legal_name, status, mode, billing_status, metadata)
|
||||
values ($1, 'platform-audit-alert-worker', '平台审计告警租户', '平台审计告警有限公司', 'active', 'saas', 'active', '{"source":"platform-audit-alert-worker-test"}'::jsonb)
|
||||
`,
|
||||
[ids.tenant],
|
||||
);
|
||||
await pool.query(
|
||||
`
|
||||
insert into public.audit_logs (
|
||||
id, tenant_id, actor_user_id, action, target_type, target_id,
|
||||
details, ip_address, user_agent, created_at
|
||||
)
|
||||
values (
|
||||
$1, $2::uuid, null, 'platform.tenant.status_updated', 'tenant', $3,
|
||||
'{"status":"suspended","reason":"integration alert","accessToken":"must-not-leak","nested":{"password":"must-not-leak"}}'::jsonb,
|
||||
'127.0.0.1', 'platform-audit-alert-worker-test', now()
|
||||
)
|
||||
`,
|
||||
[ids.auditLog, ids.tenant, ids.tenant],
|
||||
);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const pool = new pg.Pool({ connectionString: databaseUrl });
|
||||
try {
|
||||
await cleanup(pool);
|
||||
await createAuditLog(pool);
|
||||
|
||||
const firstOutput = await runWorkerOnce();
|
||||
assert.match(firstOutput, /created=\d+/, 'worker should create alert candidates');
|
||||
|
||||
const alerts = await pool.query(
|
||||
`
|
||||
select a.id, a.status, a.severity, a.action, a.target_type, a.target_id,
|
||||
a.details, r.code as rule_code
|
||||
from public.platform_audit_alerts a
|
||||
join public.platform_audit_alert_rules r on r.id = a.rule_id
|
||||
where a.tenant_id = $1 and a.audit_log_id = $2
|
||||
`,
|
||||
[ids.tenant, ids.auditLog],
|
||||
);
|
||||
assert.equal(alerts.rowCount, 1, 'worker should create one alert for the audit log');
|
||||
assert.equal(alerts.rows[0].status, 'open', 'worker alert should start open');
|
||||
assert.equal(alerts.rows[0].severity, 'high', 'tenant status changes should be high severity');
|
||||
assert.equal(alerts.rows[0].rule_code, 'platform_tenant_status_changed', 'worker should match default tenant status rule');
|
||||
assert.equal(alerts.rows[0].details?.workerId, 'platform-audit-alert-worker-test', 'alert should record worker id');
|
||||
assert.equal(alerts.rows[0].details?.auditDetails?.accessToken, '[REDACTED]', 'alert should redact token-like audit details');
|
||||
assert.equal(alerts.rows[0].details?.auditDetails?.nested?.password, '[REDACTED]', 'alert should redact nested password-like audit details');
|
||||
assert.ok(!JSON.stringify(alerts.rows[0].details).includes('must-not-leak'), 'alert details must not leak sensitive audit values');
|
||||
|
||||
const audit = await pool.query(
|
||||
`
|
||||
select action, target_type, target_id, details
|
||||
from public.audit_logs
|
||||
where tenant_id = $1 and action = 'platform.audit.alert_created'
|
||||
order by created_at desc
|
||||
limit 1
|
||||
`,
|
||||
[ids.tenant],
|
||||
);
|
||||
assert.equal(audit.rows[0]?.target_type, 'platform_audit_alert', 'worker should audit alert creation');
|
||||
assert.equal(audit.rows[0]?.details?.ruleCode, 'platform_tenant_status_changed', 'worker audit should include rule code');
|
||||
|
||||
const secondOutput = await runWorkerOnce();
|
||||
assert.match(secondOutput, /created=0/, 'second worker run should not create duplicate alerts');
|
||||
|
||||
const count = await pool.query(
|
||||
`
|
||||
select count(*)::integer as count
|
||||
from public.platform_audit_alerts
|
||||
where tenant_id = $1 and audit_log_id = $2
|
||||
`,
|
||||
[ids.tenant, ids.auditLog],
|
||||
);
|
||||
assert.equal(Number(count.rows[0]?.count), 1, 'worker should be idempotent');
|
||||
|
||||
console.log('Platform audit alert worker integration test complete.');
|
||||
} finally {
|
||||
await cleanup(pool).catch(() => {});
|
||||
await pool.end();
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(error => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user