// ── /api/docs.json, with a module installed ──────────────────────────────── // // The request-time half of docs/website/MODULE_API.md §6.1's settled decision. // `swagger-output.json` is core's own routes and cannot be anything else: it is // generated on a developer's machine and committed, so it has to come out the // same regardless of what they had checked out, and a module arrives on the // volume long after the image was built. The module's routes therefore reach the // document only here, from the fragment it ships (§2.8, §6.1a). // // This boots the REAL app against a throwaway module directory, because the two // things worth locking are properties of the served document rather than of the // merge helper: that a started module's paths are IN it, and that core wins. // // The modules directory is written and MODULES_DIR set BEFORE app.js is required // — the scan is synchronous and happens during that require. process.env.DB_HOST = '127.0.0.1' process.env.DB_PORT = '59999' const fs = require('fs') const os = require('os') const path = require('path') const { test, before, after } = require('node:test') const assert = require('node:assert/strict') const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'rg-module-docs-')) const moduleDir = path.join(tmpRoot, 'atlas') fs.mkdirSync(path.join(moduleDir, 'client', 'dist'), { recursive: true }) fs.writeFileSync(path.join(moduleDir, 'client', 'dist', 'entry.js'), 'export const hello = 1\n') fs.writeFileSync( path.join(moduleDir, 'module.json'), JSON.stringify({ id: 'atlas', name: 'Atlas', version: '1.0.0', coreApi: '^1.0.0', client: { entry: 'client/dist/entry.js' }, }), ) fs.writeFileSync( path.join(moduleDir, 'swagger-fragment.json'), JSON.stringify({ paths: { '/api/v1/public/atlas/creatures': { get: { tags: ['Public · Atlas'], summary: 'List creatures' } }, // The collision case, and the one that matters: a module trying to // redefine a path core already declares. Core wins and the module's // version is dropped (§6.1a) — a module cannot rewrite core's docs. '/api/v1/public/settings': { get: { summary: 'MODULE OVERRIDE' } }, }, tags: [{ name: 'Public · Atlas', description: 'from the module' }], components: { schemas: { AtlasCreature: { type: 'object' }, // Same shape of collision, one section down. Error: { type: 'string', description: 'MODULE OVERRIDE' }, }, }, }), ) process.env.MODULES_DIR = tmpRoot /* eslint-disable global-require */ const app = require('../src/app') const loader = require('../src/modules/loader') const db = require('../src/utils/db') const coreSpec = require('../swagger/swagger-output.json') const { docsSpec, reset } = require('../swagger/docsSpec') /* eslint-enable global-require */ let server let base before(async () => { server = await new Promise((resolve) => { const s = app.listen(0, '127.0.0.1', () => resolve(s)) }) base = `http://127.0.0.1:${server.address().port}` }) after(async () => { server.closeAllConnections() await new Promise((resolve) => server.close(resolve)) await db.close() fs.rmSync(tmpRoot, { recursive: true, force: true }) }) const fetchSpec = async () => { const res = await fetch(`${base}/api/docs.json`) assert.equal(res.status, 200) return res.json() } test('a module that is not started contributes nothing', async () => { // It is `registered` here: loaded cleanly, onBoot not yet dispatched. Its // routes answer 503 in that state, so documenting them would send a client // somewhere it cannot go — the same reason the client chunk's