// The frozen manifest's derivation, checked. // // `scripts/frozenManifest.js` runs in one place — a CI job with a whole core // checked out beside it — so it is the least-exercised piece of machinery in this // repo, and it is the piece that decides whether the URLs this module claims are // the URLs it serves (MODULE_API.md §5.3). Its three answers are pure functions of // two manifests and a fragment, so all three are asked here, with fixtures rather // than a clone. // // What is deliberately NOT asserted here: the numbers. `routes.manifest.json`'s // routes are proved by the job that generates them from a real core, and a copy of // that count in this file would only ever be a second thing to update. const test = require('node:test') const assert = require('node:assert') const fs = require('node:fs') const path = require('node:path') const { diffManifests, coverage, MANIFEST, FRAGMENT } = require('../scripts/frozenManifest') const manifest = (public_ = [], internal = []) => ({ public: public_, internal }) const get = (p) => ({ method: 'GET', path: p }) test("the module's routes are the ones a core gains by loading it", () => { const before = manifest([get('/api/v1/public/settings')]) const after = manifest([get('/api/v1/public/settings'), get('/api/v1/public/rust/servers')]) const { added, removed } = diffManifests(before, after) assert.deepStrictEqual(removed, []) assert.deepStrictEqual(added, [{ method: 'GET', path: '/api/v1/public/rust/servers', tier: 'public' }]) }) test('a route core loses to the module is reported, not quietly absorbed', () => { // The failure this exists for, and the one phase 1 could only check by reading: // core mounts several routes at the TIER ROOT (/status, /version) that the // loader's collision probe cannot see, so a module whose mount displaced one // would not show up as an addition — the URL is unchanged — and a check that // only looked at what appeared would call it clean. const before = manifest([get('/api/v1/public/settings'), get('/api/v1/public/status')]) const after = manifest([get('/api/v1/public/settings')]) const { removed } = diffManifests(before, after) assert.deepStrictEqual(removed, ['public GET /api/v1/public/status']) }) test('a route whose METHOD changed counts as removed and added', () => { const { added, removed } = diffManifests( manifest([{ method: 'POST', path: '/api/v1/admin/thing' }]), manifest([{ method: 'PUT', path: '/api/v1/admin/thing' }]), ) assert.deepStrictEqual(removed, ['public POST /api/v1/admin/thing']) assert.strictEqual(added.length, 1) }) test('the internal app is diffed too, and keeps its own tier', () => { const { added } = diffManifests( manifest([], [get('/internal/health')]), manifest([], [get('/internal/health'), get('/internal/rust/thing')]), ) assert.deepStrictEqual(added, [{ method: 'GET', path: '/internal/rust/thing', tier: 'internal' }]) }) test('added routes are sorted, so the committed file does not churn on traversal order', () => { const { added } = diffManifests( manifest([]), manifest([get('/b'), get('/a'), { method: 'POST', path: '/a' }]), ) assert.deepStrictEqual( added.map((r) => `${r.method} ${r.path}`), ['GET /a', 'GET /b', 'POST /a'], ) }) // ── coverage: the route ⇄ fragment agreement ──────────────────────────────── const fragment = (paths) => ({ paths }) test('a served route with no documented operation is named', () => { const { undocumented, unserved } = coverage([get('/api/v1/public/rust/servers')], fragment({})) assert.deepStrictEqual(undocumented, ['GET /api/v1/public/rust/servers']) assert.deepStrictEqual(unserved, []) }) test('a documented operation nobody serves is named too', () => { // The direction core's own spec has no check for, which is how it accumulated // orphan tags and schemas describing routes that had moved out of it. A // documented URL nobody serves is a client following the docs into a 404. const { undocumented, unserved } = coverage([], fragment({ '/api/v1/public/rust/gone': { get: {} } })) assert.deepStrictEqual(undocumented, []) assert.deepStrictEqual(unserved, ['GET /api/v1/public/rust/gone']) }) test('express :params and OpenAPI {params} are the same route', () => { const { undocumented, unserved } = coverage( [{ method: 'DELETE', path: '/api/v1/admin/rust/servers/:id' }], fragment({ '/api/v1/admin/rust/servers/{id}': { delete: {} } }), ) assert.deepStrictEqual(undocumented, []) assert.deepStrictEqual(unserved, []) }) test('methods are matched, not just paths', () => { const { undocumented, unserved } = coverage( [{ method: 'POST', path: '/api/v1/admin/rust/servers/:id/test' }], fragment({ '/api/v1/admin/rust/servers/{id}/test': { get: {} } }), ) assert.deepStrictEqual(undocumented, ['POST /api/v1/admin/rust/servers/{id}/test']) assert.deepStrictEqual(unserved, ['GET /api/v1/admin/rust/servers/{id}/test']) }) // ── the committed artifacts, against each other ───────────────────────────── // // These two files are generated together by a job that has a real core; here // there is no core, so what can still be asked is whether they agree with each // other. If they do not, one of them was committed without the other. test('every route in the committed manifest has a committed operation', () => { const { routes } = JSON.parse(fs.readFileSync(MANIFEST, 'utf8')) const spec = JSON.parse(fs.readFileSync(FRAGMENT, 'utf8')) const { undocumented, unserved } = coverage(routes, spec) assert.deepStrictEqual(undocumented, [], 'routes.manifest.json lists routes swagger-fragment.json does not document') assert.deepStrictEqual(unserved, [], 'swagger-fragment.json documents operations routes.manifest.json does not list') }) test('the fragment carries only the three sections §6.1a allows', () => { const spec = JSON.parse(fs.readFileSync(FRAGMENT, 'utf8')) assert.deepStrictEqual(Object.keys(spec).sort(), ['components', 'paths', 'tags']) assert.deepStrictEqual(Object.keys(spec.components), ['schemas']) }) test("the fragment defines only namespaced schemas, and redefines none of core's", () => { const spec = JSON.parse(fs.readFileSync(FRAGMENT, 'utf8')) for (const name of Object.keys(spec.components.schemas)) { assert.match(name, /^Rust[A-Z]/, `${name} is not namespaced — core wins the collision and drops it (§6.1a)`) } // Anything this fragment REFERENCES and does not define has to be one of // core's shared schemas, which resolve in the merged document — that is the // whole point of a fragment. A typo'd $ref is otherwise invisible until a // reader opens /api/docs.json and finds a dangling pointer. const refs = JSON.stringify(spec.paths).match(/#[/]components[/]schemas[/]([A-Za-z0-9_]+)/g) || [] const shared = ['Error', 'ValidationError'] for (const name of new Set(refs.map((r) => r.split('/').pop()))) { const resolvable = Object.hasOwn(spec.components.schemas, name) || shared.includes(name) assert.ok(resolvable, `$ref to ${name} resolves to nothing — not defined here, not one of core's shared schemas`) } }) test('every path in the fragment is fully qualified', () => { const spec = JSON.parse(fs.readFileSync(FRAGMENT, 'utf8')) for (const p of Object.keys(spec.paths)) { // §6.1a: core merges the fragment verbatim and never re-derives a prefix, so // a router-relative path here is a path nothing serves. assert.match(p, /^\/api\/v1\/(public|admin|player)\//, `${p} is not a fully-qualified URL`) assert.doesNotMatch(p, /\/$/, `${p} has a trailing slash — no client calls that URL`) } }) test("the manifest and the module's declared mounts agree", () => { const { routes } = JSON.parse(fs.readFileSync(MANIFEST, 'utf8')) const { mounts } = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'module.json'), 'utf8')) const declared = [] for (const [tier, prefixes] of Object.entries(mounts)) { for (const prefix of prefixes) declared.push(`/api/v1/${tier}${prefix}/`) } // Every route this module serves is under a prefix it declared. There is no // exception here yet, and that is the point of asserting it now: phase 6 adds // the `admin.users.detail` extension slot, whose routes live under core's // `/api/v1/admin/users/` rather than under any mount of ours (§2.4). When that // arrives this test must grow the exception deliberately, rather than a route // outside every declared mount arriving unnoticed. for (const route of routes) { const under = declared.some((d) => route.path.startsWith(d)) assert.ok(under, `${route.method} ${route.path} is served from outside every mount module.json declares`) } })