THROWAWAY BRANCH — evidence for the Phase 1 contract, never merged. See
modules/uo/SPIKE.md and docs/website/MODULE_API.md Part 7.
The six public spawn-atlas routes now live in modules/uo/, reached only through
the ctx/register surface, with the client half loading as a prebuilt ESM chunk.
All three exit criteria met:
• zero internal-file imports from the module into core; the built chunk has
zero bare import specifiers and bundles no React
• routes.manifest.json AND routes.guards.json are byte-identical
• /uo/atlas renders from /modules/uo/entry.js under script-src 'self' with
zero CSP violation reports
729 core tests and 81 module tests pass. Verified end to end against the real
database: the schema fragment replays after core's, onBoot runs the atlas
refresh, and the six API URLs answer unchanged.
Two things the spike changed in the contract:
• ctx.express / ctx.validator. A module lives outside server/, so Node never
reaches server/node_modules and require('express') fails outright — the
server-side twin of the one-React rule, which §2.6 had only for the client.
• window.__rg.jsxRuntime, so a module can build with the automatic JSX
runtime its tooling already assumes rather than being forced to classic.
And it confirmed §6.1 empirically: regenerating the OpenAPI spec silently
deleted all 361 lines of the atlas paths with "Swagger-autogen: Success", while
the route manifest kept all six in the same run. That is exactly the
static-analysis-vs-runtime split the fragment merge exists to prevent.
Co-Authored-By: Claude <noreply@anthropic.com>
61 lines
2.4 KiB
JavaScript
61 lines
2.4 KiB
JavaScript
// ── Test harness: a fake ctx ───────────────────────────────────────────────
|
|
//
|
|
// A module's tests cannot require core — that is the whole zero-internal-imports
|
|
// rule (docs/website/MODULE_API.md §5.1), and it applies to test files too. So
|
|
// instead of stubbing core's modules the way core's own tests do, a module test
|
|
// hands `core.init()` a ctx it fabricated.
|
|
//
|
|
// That turns out to be the nicer story: the seam that exists so a module can be
|
|
// swapped onto a different core is the same seam that lets its tests run with no
|
|
// database, no express app and no settings table. Core's tests reach the same
|
|
// place by pointing the mariadb pool at a dead port; a module does not have to.
|
|
|
|
const core = require('../core')
|
|
|
|
/**
|
|
* Build and install a fake ctx. Every member is a stub the test can reassign.
|
|
* @param {object} [over] members to override, deep-merged one level
|
|
*/
|
|
function installFakeCtx(over = {}) {
|
|
const settings = new Map()
|
|
|
|
const ctx = {
|
|
moduleId: 'uo',
|
|
paths: { moduleRoot: require('path').join(__dirname, '..', '..') },
|
|
// Null, not the real packages: a module cannot resolve express from outside
|
|
// server/ (that is why ctx carries them at all), and these tests construct no
|
|
// router. A test that needs one passes the real ones in `over`.
|
|
express: null,
|
|
validator: null,
|
|
db: {
|
|
// Every test that needs a query result reassigns this.
|
|
query: async () => [],
|
|
pool: { getConnection: async () => { throw new Error('no pool in tests') } },
|
|
},
|
|
log: () => ({ error() {}, warn() {}, info() {}, debug() {} }),
|
|
settings: {
|
|
get: async (key) => (settings.has(key) ? settings.get(key) : null),
|
|
set: async (key, value) => { settings.set(key, value) },
|
|
getInstanceName: async () => 'Test Shard',
|
|
},
|
|
auth: { getUserFromRequest: () => null },
|
|
push: { publish: async () => {} },
|
|
secretBox: { encrypt: (s) => s, decrypt: (s) => s },
|
|
middleware: {
|
|
requireAuth: (req, res, next) => next(),
|
|
requireRole: () => (req, res, next) => next(),
|
|
siteMode: (req, res, next) => next(),
|
|
validate: (req, res, next) => next(),
|
|
noindex: (req, res, next) => next(),
|
|
},
|
|
uploads: {},
|
|
posts: {},
|
|
...over,
|
|
}
|
|
|
|
core.init(ctx)
|
|
return ctx
|
|
}
|
|
|
|
module.exports = { installFakeCtx }
|