forked from wangziqi/gongxue-base
feat: alert usage overage worker failures
This commit is contained in:
@@ -116,8 +116,7 @@ async function main() {
|
||||
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');
|
||||
await runWorkerOnce();
|
||||
|
||||
const count = await pool.query(
|
||||
`
|
||||
|
||||
@@ -46,15 +46,83 @@ function runWorkerOnce() {
|
||||
});
|
||||
}
|
||||
|
||||
function runWorkerFailureOnce() {
|
||||
const child = spawn(process.execPath, ['apps/worker/dist/apps/worker/src/index.js', '--once', '--job', 'platform-usage-overage'], {
|
||||
cwd: process.cwd(),
|
||||
env: {
|
||||
...process.env,
|
||||
DATABASE_URL: databaseUrl,
|
||||
WORKER_PLATFORM_USAGE_OVERAGE_BATCH_SIZE: '20',
|
||||
WORKER_PLATFORM_USAGE_OVERAGE_MONTH: '2026-99',
|
||||
WORKER_PLATFORM_USAGE_OVERAGE_ID: 'platform-usage-overage-failure-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.notEqual(code, 0, `invalid usage overage month should fail the worker\n${output}`);
|
||||
assert.match(output, /Invalid platform usage overage month/, 'worker failure output should explain invalid month');
|
||||
resolve(output);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function runAuditAlertWorkerOnce() {
|
||||
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-usage-overage-alert-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, `audit alert worker should exit 0\n${output}`);
|
||||
assert.match(output, /platform-audit-alerts batch processed=\d+/, 'audit alert worker output should include summary');
|
||||
resolve(output);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function dateOnly(value) {
|
||||
if (value instanceof Date) return value.toISOString().slice(0, 10);
|
||||
return String(value || '').slice(0, 10);
|
||||
}
|
||||
|
||||
async function cleanup(pool) {
|
||||
await pool.query('delete from public.audit_logs where tenant_id = any($1::uuid[]) or action = $2', [
|
||||
await pool.query('delete from public.audit_logs where tenant_id = any($1::uuid[]) or action = any($2::text[])', [
|
||||
[ids.tenant, ids.noOverageTenant],
|
||||
'platform.invoice.usage_overage_batch_created',
|
||||
['platform.invoice.usage_overage_batch_created', 'platform.invoice.usage_overage_worker_failed'],
|
||||
]);
|
||||
await pool.query('delete from public.tenant_invoice_payments where tenant_id = any($1::uuid[])', [[ids.tenant, ids.noOverageTenant]]);
|
||||
await pool.query('delete from public.tenant_invoice_items where tenant_id = any($1::uuid[])', [[ids.tenant, ids.noOverageTenant]]);
|
||||
@@ -204,6 +272,42 @@ async function main() {
|
||||
);
|
||||
assert.equal(invoiceCount.rows[0]?.count, 1, 'worker should be idempotent for usage overage invoices');
|
||||
|
||||
await runWorkerFailureOnce();
|
||||
const failureAudit = await pool.query(
|
||||
`
|
||||
select id, action, target_type, target_id, details
|
||||
from public.audit_logs
|
||||
where action = 'platform.invoice.usage_overage_worker_failed'
|
||||
order by created_at desc
|
||||
limit 1
|
||||
`,
|
||||
);
|
||||
assert.equal(failureAudit.rowCount, 1, 'failed usage overage worker should create a platform audit log');
|
||||
assert.equal(failureAudit.rows[0]?.target_type, 'tenant_invoice_batch', 'failure audit should target the invoice batch');
|
||||
assert.equal(failureAudit.rows[0]?.target_id, '2026-99', 'failure audit should retain the failed month as target');
|
||||
assert.equal(failureAudit.rows[0]?.details?.workerId, 'platform-usage-overage-failure-test', 'failure audit should record worker id');
|
||||
assert.equal(failureAudit.rows[0]?.details?.error?.code, 'PLATFORM_USAGE_OVERAGE_WORKER_FAILED', 'failure audit should expose a stable error code');
|
||||
assert.ok(!JSON.stringify(failureAudit.rows[0]?.details).match(/postgres:\/\/|password|secret/i), 'failure audit details must not leak secrets');
|
||||
|
||||
const alertOutput = await runAuditAlertWorkerOnce();
|
||||
assert.match(alertOutput, /created=\d+/, 'audit alert worker should process the usage overage failure');
|
||||
const failureAlert = await pool.query(
|
||||
`
|
||||
select 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.audit_log_id = $1
|
||||
`,
|
||||
[failureAudit.rows[0].id],
|
||||
);
|
||||
assert.equal(failureAlert.rowCount, 1, 'usage overage worker failure should create one platform alert');
|
||||
assert.equal(failureAlert.rows[0]?.status, 'open', 'failure alert should start open');
|
||||
assert.equal(failureAlert.rows[0]?.severity, 'high', 'usage overage worker failure should be high severity');
|
||||
assert.equal(failureAlert.rows[0]?.rule_code, 'platform_usage_overage_worker_failed', 'failure alert should use the usage overage failure rule');
|
||||
assert.equal(failureAlert.rows[0]?.details?.auditDetails?.workerId, 'platform-usage-overage-failure-test', 'failure alert should preserve worker id');
|
||||
assert.ok(!JSON.stringify(failureAlert.rows[0]?.details).match(/postgres:\/\/|password|secret/i), 'failure alert details must not leak secrets');
|
||||
|
||||
console.log('Platform usage overage worker integration test complete.');
|
||||
} finally {
|
||||
await cleanup(pool).catch(() => {});
|
||||
|
||||
Reference in New Issue
Block a user