// ── The three de-entanglement registries ─────────────────────────────────── // // Phase 2 PR 4 of docs/website/MODULE_SYSTEM.md §2.7. The properties worth a test // are the ones nobody exercises by hand: what happens when two registrants want // the same name, and what is left behind when one of them fails halfway. // // Point the DB at a closed port BEFORE requiring anything — registerCore() pulls // in the announce legs, which pull in models that build a pool at require time. // No query is ever run. process.env.DB_HOST = '127.0.0.1' process.env.DB_PORT = '59999' const { test, beforeEach, after } = require('node:test') const assert = require('node:assert/strict') const registries = require('../src/modules/registries') const db = require('../src/utils/db') // Declares `admin.users.detail` the way production does — at require time, in the // router that owns the resource. require('../src/router/v1/admin') after(() => db.close()) beforeEach(() => registries._reset()) const stream = (id, over = {}) => ({ id, label: id, ...over }) const leg = (id, over = {}) => ({ leg: id, label: id, dispatch: async () => ({ ok: true }), classify: () => ({ outcome: 'done' }), ...over }) /** Register a batch as `owner` and return the error message, or null on success. */ function tryApply(owner, build) { const api = registries.stage(owner) try { build(api) registries.apply(api.staged) return null } catch (err) { return err.message } } // ── Core goes through the same door ──────────────────────────────────────── test('registerCore registers core AND the not-yet-extracted shard content', () => { registries.registerCore() const ids = registries.allStreams().map((s) => s.id) // Core's one stream first, then the seven that leave with module-uo. assert.equal(ids[0], 'news.post') assert.equal(ids.length, 8) assert.ok(ids.includes('vendor.sale')) assert.deepEqual(registries.announceLegIds(), ['discord', 'towncrier']) assert.equal(registries.slotFilledBy('admin.users.detail'), 'core') assert.equal(registries.isCoreRegistered(), true) }) test('registerCore is idempotent — a second call registers nothing twice', () => { registries.registerCore() const before = registries.allStreams().length registries.registerCore() assert.equal(registries.allStreams().length, before) }) test('the wire shape of a stream survives registration', () => { registries.registerCore() const personal = registries.allStreams().find((s) => s.id === 'vendor.sale') // Two booleans, not the contract's single `scope`: this object is the body of // GET /auth/me/notifications/streams and a shipped Android client reads both. assert.equal(personal.personal, true) assert.equal(personal.requiresLinkedAccount, true) assert.ok(personal.description.length > 0) assert.ok(registries.personalStreams().has('vendor.sale')) assert.equal(registries.isValidStream('vendor.sale'), true) assert.equal(registries.isValidStream('nope.nope'), false) }) // ── Namespacing ──────────────────────────────────────────────────────────── test('a module’s stream ids must carry its module id', () => { assert.equal(tryApply('rust', (api) => api.registerNotificationStreams([stream('rust.raid')])), null) assert.match( tryApply('rust', (api) => api.registerNotificationStreams([stream('raid.started')])), /not namespaced "rust\."/, ) }) test('the seven pre-module-system stream ids are grandfathered to uo alone', () => { // Renaming them would be a data migration (notification_subs rows) and a break // for a shipped Android client — the same reasoning as the loader's legacy // table prefixes. assert.equal(tryApply('uo', (api) => api.registerNotificationStreams([stream('vendor.sale')])), null) registries._reset() assert.match( tryApply('rust', (api) => api.registerNotificationStreams([stream('vendor.sale')])), /not namespaced "rust\."/, ) }) test('an announce leg must be namespaced too, with towncrier grandfathered to uo', () => { assert.equal(tryApply('uo', (api) => api.registerAnnounceLeg(leg('towncrier'))), null) registries._reset() assert.equal(tryApply('rust', (api) => api.registerAnnounceLeg(leg('rust.motd'))), null) registries._reset() assert.match( tryApply('rust', (api) => api.registerAnnounceLeg(leg('towncrier'))), /not namespaced "rust\."/, ) }) // ── Collisions name the holder ───────────────────────────────────────────── test('a stream core already registered is refused, naming core', () => { registries.registerCore() assert.match( tryApply('uo', (api) => api.registerNotificationStreams([stream('news.post')])), /already registered by "core"/, ) }) test('two modules cannot register the same stream or leg', () => { assert.equal(tryApply('aaa', (api) => api.registerNotificationStreams([stream('aaa.thing')])), null) // A second module can only reach it via its own namespace, so collide on a // grandfathered id, which is the realistic case. assert.match( tryApply('aaa', (api) => api.registerNotificationStreams([stream('aaa.thing')])), /already registered by "aaa"/, ) assert.equal(tryApply('bbb', (api) => api.registerAnnounceLeg(leg('bbb.x'))), null) assert.match(tryApply('bbb', (api) => api.registerAnnounceLeg(leg('bbb.x'))), /already registered by "bbb"/) }) test('a batch cannot claim the same name twice', () => { assert.match( tryApply('aaa', (api) => api.registerNotificationStreams([stream('aaa.x'), stream('aaa.x')])), /registered twice/, ) }) // ── Validate-then-commit ─────────────────────────────────────────────────── test('a batch whose LAST claim collides commits none of the earlier ones', () => { // The property the whole staging design exists for. A half-registered catalog // is worse than a missing one: a subscribable stream nothing will publish to. registries.registerCore() const before = registries.allStreams().length const err = tryApply('uo', (api) => { api.registerNotificationStreams([stream('uo.first'), stream('uo.second')]) api.registerAnnounceLeg(leg('uo.leg')) api.registerNotificationStreams([stream('news.post')]) // collides with core }) assert.match(err, /already registered by "core"/) assert.equal(registries.allStreams().length, before, 'uo.first / uo.second must not be registered') assert.equal(registries.isValidStream('uo.first'), false) assert.equal(registries.announceLeg('uo.leg'), null) }) test('staging alone changes nothing — only apply() commits', () => { const api = registries.stage('uo') api.registerNotificationStreams([stream('uo.staged')]) assert.equal(registries.isValidStream('uo.staged'), false) registries.apply(api.staged) assert.equal(registries.isValidStream('uo.staged'), true) }) // ── Shape checks fire at the call ────────────────────────────────────────── test('a malformed claim throws where the registrant made it, not at apply()', () => { const api = registries.stage('uo') assert.throws(() => api.registerNotificationStreams([stream('nodots')]), /bad stream id/) assert.throws(() => api.registerNotificationStreams([{ id: 'uo.x' }]), /has no label/) assert.throws(() => api.registerNotificationStreams('not an array'), /expected an array/) assert.throws(() => api.registerAnnounceLeg(leg('uo.x', { dispatch: null })), /has no dispatch/) assert.throws(() => api.registerAnnounceLeg(leg('uo.x', { classify: null })), /has no classify/) assert.throws(() => api.registerAnnounceLeg({ leg: 'NOPE' }), /bad leg id/) }) // ── Extension slots ──────────────────────────────────────────────────────── test('only a declared slot can be filled, and only once', () => { const router = () => {} const api = registries.stage('uo') assert.throws(() => api.registerExtension('admin.invented', router), /unknown extension slot/) assert.throws(() => api.registerExtension('admin.users.detail', 'not a router'), /is not a router/) registries.registerCore() // core fills it assert.match( tryApply('uo', (a) => a.registerExtension('admin.users.detail', router)), /already filled by "core"/, ) }) test('a slot cannot be declared twice', () => { assert.throws(() => registries.declareSlot('admin.users.detail'), /already declared/) }) // ── The slot's spec, which static analysis cannot see ────────────────────── test('the filled slot’s router is findable in the live app, at the resource path', () => { // Guards swagger/slotSpecs.js: it recovers each slot's mount prefix from the // live stack rather than a hardcoded table. If this stops working, the six // slot routes vanish from swagger-output.json with `Success` printed — the // exact silent failure the spike hit (MODULE_API.md §7.4). /* eslint-disable global-require */ const app = require('../src/app') const { findMountPrefix } = require('../swagger/slotSpecs') /* eslint-enable global-require */ const [slot] = registries.filledSlots() assert.ok(slot, 'app.js must have registered core, filling the slot') assert.equal(slot.slot, 'admin.users.detail') assert.ok(slot.specFile, 'core names the file its slot router is generated from') assert.equal(findMountPrefix(app._router.stack, slot.router), '/api/v1/admin/users/:id') })