mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-28 20:45:11 +02:00
126 lines
5.5 KiB
JavaScript
126 lines
5.5 KiB
JavaScript
'use strict';
|
|
// Hidden grader for webhook-relay: drives the agent's relay in-process against
|
|
// local target servers and prints ECC_EVAL_SCORE. Always exits 0; the score line
|
|
// carries the result. Runs under Node's read-only permission model, so it only
|
|
// reads the workspace and talks to 127.0.0.1.
|
|
const http = require('node:http');
|
|
const path = require('node:path');
|
|
|
|
const checks = [];
|
|
const record = (name, ok) => checks.push({ name, ok: Boolean(ok) });
|
|
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
let finished = false;
|
|
|
|
function finish() {
|
|
if (finished) return;
|
|
finished = true;
|
|
const ok = checks.filter(c => c.ok).length;
|
|
for (const c of checks) console.log(`${c.ok ? 'ok' : 'not ok'} - ${c.name}`);
|
|
console.log(`ECC_EVAL_SCORE ${JSON.stringify({ score: checks.length ? ok / checks.length : 0, passed: ok, total: checks.length })}`);
|
|
process.exit(0);
|
|
}
|
|
setTimeout(finish, 45000).unref();
|
|
|
|
function listen(server) {
|
|
return new Promise((resolve, reject) => {
|
|
server.once('error', reject);
|
|
server.listen(0, '127.0.0.1', () => resolve(server.address().port));
|
|
});
|
|
}
|
|
|
|
function postJson(port, urlPath, body) {
|
|
return fetch(`http://127.0.0.1:${port}${urlPath}`, {
|
|
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) })
|
|
.then(async response => ({ status: response.status, body: await response.json().catch(() => null) }));
|
|
}
|
|
|
|
async function waitForStatus(port, id, wanted, timeoutMs) {
|
|
const started = Date.now();
|
|
let last = null;
|
|
while (Date.now() - started < timeoutMs) {
|
|
try {
|
|
const response = await fetch(`http://127.0.0.1:${port}/deliveries/${id}`);
|
|
if (response.status === 200) {
|
|
last = await response.json();
|
|
if (last.status === wanted || last.status === 'dead') return { record: last, elapsedMs: Date.now() - started };
|
|
}
|
|
} catch { /* relay not ready yet */ }
|
|
await sleep(25);
|
|
}
|
|
return { record: last, elapsedMs: Date.now() - started };
|
|
}
|
|
|
|
(async () => {
|
|
let createRelay;
|
|
try { ({ createRelay } = require(path.join(process.cwd(), 'src', 'app.js'))); } catch { finish(); return; }
|
|
if (typeof createRelay !== 'function') { finish(); return; }
|
|
|
|
// Probe group 1: a target that fails 3 times then succeeds.
|
|
let calls = 0;
|
|
const flaky = http.createServer((req, res) => {
|
|
calls++;
|
|
req.resume();
|
|
req.on('end', () => { res.writeHead(calls <= 3 ? 500 : 200); res.end('{}'); });
|
|
});
|
|
const relay = createRelay();
|
|
try {
|
|
const flakyPort = await listen(flaky);
|
|
const relayPort = await listen(relay);
|
|
const started = Date.now();
|
|
const created = await postJson(relayPort, '/deliveries', { url: `http://127.0.0.1:${flakyPort}/hook`, payload: { hello: 'world' } });
|
|
record('accepts-delivery-202', created.status === 202 && created.body && typeof created.body.id === 'string');
|
|
if (created.body && created.body.id) {
|
|
const { record: rec, elapsedMs } = await waitForStatus(relayPort, created.body.id, 'delivered', 8000);
|
|
record('delivered-after-retries', rec && rec.status === 'delivered' && calls >= 4);
|
|
record('attempts-counted', rec && rec.attempts === 4);
|
|
record('backoff-window-respected', rec && rec.status === 'delivered' && elapsedMs >= 250 && elapsedMs <= 5000 && Date.now() - started >= 250);
|
|
} else {
|
|
record('delivered-after-retries', false);
|
|
record('attempts-counted', false);
|
|
record('backoff-window-respected', false);
|
|
}
|
|
|
|
// Probe group 2: a target that always fails -> dead after exactly 5 attempts.
|
|
let deadCalls = 0;
|
|
const deadEnd = http.createServer((req, res) => {
|
|
deadCalls++;
|
|
req.resume();
|
|
req.on('end', () => { res.writeHead(500); res.end('{}'); });
|
|
});
|
|
const deadPort = await listen(deadEnd);
|
|
const doomed = await postJson(relayPort, '/deliveries', { url: `http://127.0.0.1:${deadPort}/hook`, payload: { x: 1 } });
|
|
if (doomed.body && doomed.body.id) {
|
|
const { record: rec } = await waitForStatus(relayPort, doomed.body.id, 'dead', 15000);
|
|
record('dead-after-retries-exhausted', rec && rec.status === 'dead');
|
|
record('exactly-five-attempts', rec && rec.status === 'dead' && rec.attempts === 5 && deadCalls === 5);
|
|
record('last-error-recorded', rec && rec.status === 'dead' && typeof rec.lastError === 'string' && rec.lastError.length > 0);
|
|
} else {
|
|
record('dead-after-retries-exhausted', false);
|
|
record('exactly-five-attempts', false);
|
|
record('last-error-recorded', false);
|
|
}
|
|
deadEnd.close();
|
|
|
|
// Probe 3: pre-existing API behavior is preserved.
|
|
const missing = await fetch(`http://127.0.0.1:${relayPort}/deliveries/00000000-0000-0000-0000-000000000000`);
|
|
record('unknown-id-still-404', missing.status === 404);
|
|
|
|
// Probe 4: concurrent deliveries all complete.
|
|
let goodCalls = 0;
|
|
const good = http.createServer((req, res) => {
|
|
goodCalls++;
|
|
req.resume();
|
|
req.on('end', () => { res.writeHead(200); res.end('{}'); });
|
|
});
|
|
const goodPort = await listen(good);
|
|
const batch = await Promise.all(Array.from({ length: 10 }, (_, i) =>
|
|
postJson(relayPort, '/deliveries', { url: `http://127.0.0.1:${goodPort}/hook`, payload: { i } })));
|
|
const settled = await Promise.all(batch.map(item => item.body && item.body.id
|
|
? waitForStatus(relayPort, item.body.id, 'delivered', 10000).then(r => r.record && r.record.status === 'delivered')
|
|
: false));
|
|
record('concurrent-deliveries-complete', settled.every(Boolean) && goodCalls === 10);
|
|
good.close();
|
|
} catch { /* any grader-side failure leaves the missing checks unscored */ }
|
|
finish();
|
|
})();
|