// docs/api-reference.md was hand-written, and by the time anyone checked it // documented twenty-three endpoints that answer 404. Reference material written // by hand is correct on the day it is written and silently wrong afterwards. // // So the document is generated from the Express router stack. These tests pin // the two things that can break that: the shape of the stack (undocumented, and // an Express upgrade could change it) and the rule that every route says what // it is for. const test = require('node:test'); const assert = require('node:assert/strict'); const express = require('express'); const openapi = require('../src/utils/openapi'); // A stand-in for the real app: mounted routers, a route straight on the app, // plain middleware that is not a router, and a duplicate path across two // routers mounted on the same prefix. function syntheticApp() { const app = express(); const users = express.Router(); users.get('/me', (req, res) => res.end()); users.post('/login', (req, res) => res.end()); users.get('/thing/:id', (req, res) => res.end()); const also = express.Router(); also.get('/me', (req, res) => res.end()); // duplicate, loses to the first also.delete('/thing/:id', (req, res) => res.end()); app.use((req, res, next) => next()); // not a router app.use('/api/auth', users); app.use('/api/auth', also); app.get('/api/health', (req, res) => res.end()); // straight on the app return app; } test('every mounted route is found, at its full path', () => { const found = openapi.routes(syntheticApp()); const seen = found.map(r => r.method + ' ' + r.path).sort(); assert.deepEqual(seen, [ 'DELETE /api/auth/thing/:id', 'GET /api/auth/me', 'GET /api/auth/thing/:id', 'GET /api/health', 'POST /api/auth/login' ]); }); test('a duplicate path is documented once — Express answers with the first', () => { const found = openapi.routes(syntheticApp()); assert.equal(found.filter(r => r.method === 'GET' && r.path === '/api/auth/me').length, 1); }); test('plain middleware is not mistaken for a router', () => { // app.use(fn) has a regexp matching everything; treating it as a mount would // invent a path for every route under it. assert.equal(openapi.mountPath({ regexp: /^\/?(?=\/|$)/i }), ''); }); test('a mount regexp reads back as the path it was mounted at', () => { const app = syntheticApp(); const mounts = app._router.stack.filter(l => l.handle && l.handle.stack).map(openapi.mountPath); assert.ok(mounts.includes('/api/auth'), 'got: ' + JSON.stringify(mounts)); }); test('an app with no router yields nothing rather than throwing', () => { // The endpoint must fail soft: a broken document is better than a 500 on a // page that was only ever informational. assert.deepEqual(openapi.routes(null), []); assert.deepEqual(openapi.routes({}), []); }); test('path params become OpenAPI templates and are declared', () => { assert.equal(openapi.toTemplate('/api/my-resources/:id/export'), '/api/my-resources/{id}/export'); const doc = openapi.document(syntheticApp()); const op = doc.paths['/api/auth/thing/{id}'].get; assert.equal(op.parameters.length, 1); assert.equal(op.parameters[0].name, 'id'); assert.equal(op.parameters[0].in, 'path'); assert.equal(op.parameters[0].required, true); }); test('the document is valid OpenAPI 3.1 with both auth schemes declared', () => { const doc = openapi.document(syntheticApp(), { version: '1.2.3' }); assert.equal(doc.openapi, '3.1.0'); assert.equal(doc.info.version, '1.2.3'); assert.ok(doc.paths['/api/health']); assert.deepEqual(Object.keys(doc.components.securitySchemes).sort(), ['bearer', 'cookie']); // Cookie and bearer are alternatives, not both required. assert.equal(doc.security.length, 2); }); test('routes are grouped so a reader can find them', () => { const doc = openapi.document(syntheticApp()); assert.deepEqual(doc.paths['/api/auth/me'].get.tags, ['auth']); assert.deepEqual(doc.paths['/api/health'].get.tags, ['health']); }); // ---- the metadata half ----------------------------------------------------- test('metadata is keyed exactly as routes are mounted', () => { const meta = require('../src/utils/openapiRoutes'); for (const key of Object.keys(meta.operations)) { assert.match(key, /^(GET|POST|PUT|DELETE|PATCH) \/api\//, 'malformed key: ' + key); } }); test('a public operation says so, and is the only kind that does', () => { // Guessing this from middleware gets it wrong in the direction that hides a // hole, so it is stated per route. Registration status must be public — the // sign-in screen reads it before anyone has a session. const meta = require('../src/utils/openapiRoutes'); assert.equal(meta.operations['GET /api/auth/registration-status'].public, true); assert.equal(meta.operations['GET /api/health'].public, true); assert.equal(meta.operations['POST /api/auth/logout'].public, undefined); }); test('the endpoint is mounted after every router, and behind auth', () => { const fs = require('fs'); const server = fs.readFileSync(require('path').join(__dirname, '..', 'server.js'), 'utf8'); const at = server.indexOf("app.get('/api/openapi.json'"); assert.ok(at > -1, 'the endpoint is not mounted'); assert.match(server.slice(at, at + 200), /authMiddleware/); // Anything mounted below it would be missing from the document. const lastMount = server.lastIndexOf("app.use('/api"); assert.ok(lastMount < at, 'a router is mounted after the openapi endpoint'); });