← back to Costa Rica
test/async-harden.test.js
101 lines
'use strict';
// harden()/asyncWrap() — Express 4 async-error safety (Cody gate, cycle 11, TK-10346).
// Proves an uncaught throw/rejection in a hardened route reaches error middleware
// (clean 500, no hang, no crash), a normal route is unaffected, and a route that
// catches its own error keeps its own response (try/catch still wins).
const { test, before, after } = require('node:test');
const assert = require('node:assert');
const http = require('node:http');
const express = require('express');
const { asyncWrap, harden } = require('../lib/async-harden');
// --- unit: asyncWrap forwards both sync throws and async rejections ---
test('asyncWrap: a synchronous throw is forwarded to next(err)', () => {
let forwarded = null;
const wrapped = asyncWrap(() => { throw new Error('sync-boom'); });
wrapped({}, {}, (e) => { forwarded = e; });
assert.ok(forwarded && /sync-boom/.test(forwarded.message));
});
test('asyncWrap: an async rejection is forwarded to next(err)', async () => {
let forwarded = null;
const wrapped = asyncWrap(async () => { throw new Error('async-boom'); });
wrapped({}, {}, (e) => { forwarded = e; });
await new Promise(r => setImmediate(r)); // let the rejection settle
assert.ok(forwarded && /async-boom/.test(forwarded.message));
});
test('asyncWrap: a handler that resolves normally does NOT call next with an error', async () => {
let nextErr = 'unset';
const wrapped = asyncWrap(async (req, res) => { res.ok = true; });
const res = {};
wrapped({}, res, (e) => { nextErr = e; });
await new Promise(r => setImmediate(r));
assert.equal(res.ok, true);
assert.equal(nextErr, 'unset', 'next(err) not called on success');
});
test('harden: is idempotent — a second call does not double-wrap', () => {
const router = express.Router();
router.get('/x', async (_req, res) => res.end());
harden(router);
const h1 = router.stack.find(l => l.route && l.route.path === '/x').route.stack[0].handle;
harden(router);
const h2 = router.stack.find(l => l.route && l.route.path === '/x').route.stack[0].handle;
assert.strictEqual(h1, h2, 'handler is wrapped once, not re-wrapped');
assert.equal(h1.__hardened, true);
});
// --- E2E: a hardened sub-router behind a global error handler ---
let server, base;
before(async () => {
const router = express.Router();
router.get('/boom', async () => { throw new Error('kaboom — uncaught in an async route'); });
router.get('/boom-sync', (_req, _res) => { throw new Error('sync kaboom'); });
router.get('/ok', async (_req, res) => res.json({ ok: true, hi: 'there' }));
router.get('/caught', async (_req, res) => {
try { throw new Error('handled internally'); }
catch { res.status(400).json({ ok: false, caught: true }); }
});
harden(router);
const app = express();
app.use('/api', router);
// Global error handler mounted last, mirroring server.js.
app.use((err, req, res, _next) => { res.status(500).json({ ok: false, error: 'internal error', path: req.path }); });
await new Promise(r => { server = app.listen(0, r); });
base = `http://127.0.0.1:${server.address().port}`;
});
after(() => server && server.close());
function get(path) {
return new Promise((resolve, reject) => {
http.get(base + path, res => { let b = ''; res.on('data', c => b += c); res.on('end', () => resolve({ status: res.statusCode, json: JSON.parse(b || '{}') })); }).on('error', reject);
});
}
test('E2E: an uncaught ASYNC throw -> clean 500 (not a hang, not a crash)', async () => {
const r = await get('/api/boom');
assert.equal(r.status, 500);
assert.equal(r.json.ok, false);
assert.equal(r.json.error, 'internal error', 'generic message, no internal detail leaked');
});
test('E2E: an uncaught SYNC throw -> clean 500 too', async () => {
const r = await get('/api/boom-sync');
assert.equal(r.status, 500);
});
test('E2E: a normal route is unaffected by harden', async () => {
const r = await get('/api/ok');
assert.equal(r.status, 200);
assert.deepEqual(r.json, { ok: true, hi: 'there' });
});
test('E2E: a route that catches its own error keeps its own response (try/catch still wins, not a 500)', async () => {
const r = await get('/api/caught');
assert.equal(r.status, 400, 'the handler self-handled -> its 400, not the error handler 500');
assert.equal(r.json.caught, true);
});