← back to Costa Rica

test/async-harden-real-router.test.js

65 lines

'use strict';
// Cody gate follow-up (cycle 11, TK-10346): async-harden.test.js proved the
// MECHANISM against a throwaway router built inline in the test. Cody's one
// required gap: nothing proved the WIRING against the REAL routes/app.js router —
// so a future regression (e.g. server.js losing its harden(...) call, or a route
// losing an inner try/catch it used to have) would go uncaught by CI. This test
// mounts the ACTUAL router from routes/app.js, wraps it with the real harden()
// exactly as server.js does, and drives a genuinely-uncaught route (verified by
// reading routes/app.js: `GET /listings/:slug` at line 137 has no try/catch around
// its pool.query) through a rejecting pool.query to prove the full real chain:
// real route -> real harden() -> real error middleware -> clean 500, not a hang.
//
// No live DB: db.pool.query is swapped to reject on demand. No live creds needed
// for anything else the router touches on this path.

const { test, before, after } = require('node:test');
const assert = require('node:assert');
const http = require('node:http');
const express = require('express');

const db = require('../lib/db');
const { harden } = require('../lib/async-harden');
const { router } = require('../routes/app'); // the REAL router, unmodified

const origQuery = db.pool.query;
let server, base;

before(async () => {
  const app = express();
  app.use(express.json());
  // Mirror server.js's exact wiring: app.use('/api/app', harden(require('./routes/app').router))
  // followed by the global error handler mounted last.
  app.use('/api/app', harden(router));
  app.use((err, req, res, _next) => {
    if (res.headersSent) return;
    console.error(`[500] ${req.method} ${req.path}`, err && err.message);
    res.status(500).json({ ok: false, error: 'internal error' });
  });
  await new Promise(r => { server = app.listen(0, r); });
  base = `http://127.0.0.1:${server.address().port}`;
});
after(() => { db.pool.query = origQuery; 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('REAL ROUTER: GET /listings/:slug (genuinely uncaught in routes/app.js) -> a rejecting pool.query returns a clean 500, not a hang', async () => {
  db.pool.query = async () => { throw new Error('simulated DB outage'); };
  const started = Date.now();
  const r = await get('/api/app/listings/some-place');
  const elapsed = Date.now() - started;
  assert.equal(r.status, 500, 'the REAL route, hardened via the REAL harden(), reaches the REAL error middleware');
  assert.equal(r.json.error, 'internal error', 'generic message, no DB detail leaked');
  assert.ok(elapsed < 5000, `resolved in ${elapsed}ms — proves this is not a hang (the pre-fix behavior)`);
});

test('REAL ROUTER: a normal (non-throwing) call to the same real route still works through harden()', async () => {
  db.pool.query = async () => ({ rows: [] }); // no listing found — the route's own 404, not the error path
  const r = await get('/api/app/listings/nonexistent-place');
  assert.equal(r.status, 404, 'hardening a route that behaves normally does not change its normal responses');
});