Files
ECC/docker/context-profiles/complex-eval/cases3/idempotent-webhooks/check.cjs
T

120 lines
6.4 KiB
JavaScript

'use strict';
// Hidden grader for idempotent-webhooks: exactly-once under sequential,
// concurrent, and mixed-concurrent duplicates, plus the documented API,
// regression coverage, and hygiene. Prints ECC_EVAL_SCORE and always exits 0.
const fs = require('node:fs');
const path = require('node:path');
const checks = [];
const record = (name, ok) => checks.push({ name, ok: Boolean(ok) });
let finished = false;
function finish() {
if (finished) return;
finished = true;
for (let i = checks.length; i < 12; i++) record(`unreached-${i + 1}`, false);
const ok = checks.filter(c => c.ok).length;
for (const c of checks) process.stdout.write(`${c.ok ? 'ok' : 'not ok'} - ${c.name}\n`);
process.stdout.write(`ECC_EVAL_SCORE ${JSON.stringify({ score: ok / 12, passed: ok, total: 12 })}\n`);
process.exit(0);
}
// A crashing agent server must not kill the grader: score what completed.
process.on('uncaughtException', finish);
process.on('unhandledRejection', finish);
const root = process.cwd();
const hasEnvelope = body => body && body.error && typeof body.error.code === 'string'
&& /^[A-Z][A-Z0-9_]+$/.test(body.error.code) && typeof body.error.message === 'string';
(async () => {
let createApp;
let store;
try {
({ createApp } = require(path.join(root, 'src', 'app.js')));
({ store } = require(path.join(root, 'src', 'store.js')));
} catch { /* scored below */ }
if (typeof createApp === 'function' && store && Array.isArray(store.paymentLog)) {
try {
const app = createApp();
await new Promise(resolve => app.listen(0, '127.0.0.1', resolve));
const port = app.address().port;
const send = (eventId, orderId, amountCents) => fetch(`http://127.0.0.1:${port}/webhooks/payments`, {
method: 'POST', headers: { 'content-type': 'application/json' },
body: JSON.stringify({ eventId, orderId, amountCents, type: 'payment.succeeded' }) });
const logsFor = orderId => store.paymentLog.filter(p => p.orderId === orderId).length;
// 1: single delivery applies once.
const single = await send('ev-1', 'o1', 5000);
const singleBody = await single.json().catch(() => null);
record('single-delivery-processed', single.status === 200 && singleBody
&& singleBody.status === 'processed' && singleBody.orderId === 'o1' && logsFor('o1') === 1);
// 2: sequential retry replays without re-applying.
const retry = await send('ev-1', 'o1', 5000);
const retryBody = await retry.json().catch(() => null);
record('sequential-duplicate-inert', retry.status === 200 && retryBody
&& retryBody.status === 'duplicate' && logsFor('o1') === 1);
// 3: fifty concurrent identical deliveries apply exactly once.
const storm = await Promise.all(Array.from({ length: 50 }, () => send('ev-2', 'o2', 12500)));
const stormBodies = [];
for (const r of storm) stormBodies.push(await r.json().catch(() => null));
const processedCount = stormBodies.filter(b => b && b.status === 'processed').length;
const duplicateCount = stormBodies.filter(b => b && b.status === 'duplicate').length;
record('concurrent-storm-exactly-once', storm.every(r => r.status === 200)
&& processedCount === 1 && duplicateCount === 49 && logsFor('o2') === 1
&& store.orders.get('o2').paymentsApplied === 1);
// 4: a different event for an already-paid order is already_paid and inert.
const second = await send('ev-3', 'o2', 12500);
const secondBody = await second.json().catch(() => null);
record('already-paid-order-inert', second.status === 200 && secondBody
&& secondBody.status === 'already_paid' && logsFor('o2') === 1);
// 5-7: contract errors with envelopes.
const unknown = await send('ev-4', 'nope', 100);
record('unknown-order-404-envelope', unknown.status === 404 && hasEnvelope(await unknown.json().catch(() => null)));
const malformed = await fetch(`http://127.0.0.1:${port}/webhooks/payments`, {
method: 'POST', headers: { 'content-type': 'application/json' }, body: '{bad json' });
record('malformed-body-400-envelope', malformed.status === 400 && hasEnvelope(await malformed.json().catch(() => null)));
const mismatch = await send('ev-5', 'o3', 999999);
record('amount-mismatch-422-envelope', mismatch.status === 422
&& hasEnvelope(await mismatch.json().catch(() => null)) && logsFor('o3') === 0);
// 8: mixed storm — three orders, three eventIds, ten duplicates each, all concurrent.
const mixed = await Promise.all(['o4', 'o5', 'o6'].flatMap(orderId =>
Array.from({ length: 10 }, () => send(`ev-${orderId}`, orderId, store.orders.get(orderId).amountCents))));
for (const r of mixed) await r.json().catch(() => null);
record('mixed-storm-each-order-once', ['o4', 'o5', 'o6'].every(orderId =>
logsFor(orderId) === 1 && store.orders.get(orderId).paymentsApplied === 1));
// 9: order inspection endpoint reflects reality.
const orderView = await fetch(`http://127.0.0.1:${port}/orders/o2`);
const orderBody = await orderView.json().catch(() => null);
record('order-endpoint-accurate', orderView.status === 200 && orderBody
&& orderBody.status === 'paid' && orderBody.paymentsApplied === 1 && Boolean(orderBody.paidAt));
app.close();
} catch { /* remaining checks unscored */ }
} else {
for (const name of ['single-delivery-processed', 'sequential-duplicate-inert', 'concurrent-storm-exactly-once',
'already-paid-order-inert', 'unknown-order-404-envelope', 'malformed-body-400-envelope',
'amount-mismatch-422-envelope', 'mixed-storm-each-order-once', 'order-endpoint-accurate']) record(name, false);
}
// Conventions.
let tests = '';
try {
for (const f of fs.readdirSync(path.join(root, 'test'))) tests += fs.readFileSync(path.join(root, 'test', f), 'utf8');
} catch { /* missing */ }
record('concurrency-regression-tests', (tests.match(/\btest\(/g) || []).length >= 4
&& /Promise\.all|concurrent|duplicate|retry/i.test(tests));
let changelog = '';
try { changelog = fs.readFileSync(path.join(root, 'CHANGELOG.md'), 'utf8'); } catch { /* missing */ }
record('changelog-entry', /idem|duplicat|retry|inc-104|race/i.test(changelog));
try {
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
record('no-external-dependencies', !pkg.dependencies && !pkg.devDependencies);
} catch { record('no-external-dependencies', false); }
finish();
})();