// ── The announcement pipeline, once legs became registrations ────────────── // // Phase 2 PR 4 (docs/website/MODULE_SYSTEM.md §1.8): `announce_jobs`' two // hardcoded leg column groups became `announce_job_legs` rows, and which legs // exist is what modules/registries.js answers. These tests are about that // property specifically — that nothing in the worker or the model knows the word // "towncrier", and a leg nobody registered is handled rather than assumed away. // // Point the DB at a closed port BEFORE requiring anything; every DB call the // model makes is stubbed. process.env.DB_HOST = '127.0.0.1' process.env.DB_PORT = '59999' const { test, beforeEach, afterEach, after } = require('node:test') const assert = require('node:assert/strict') const registries = require('../src/modules/registries') const announceJobs = require('../src/model/announceJobs/announceJobs.model') const announceDb = require('../src/model/announceJobs/announceJobs.db') const worker = require('../src/utils/announceWorker') const posts = require('../src/model/posts/posts.model') const db = require('../src/utils/db') after(() => db.close()) const originals = { create: announceDb.create, findById: announceDb.findById, findByPostId: announceDb.findByPostId, findDue: announceDb.findDue, updateLeg: announceDb.updateLeg, ensureLegs: announceDb.ensureLegs, setStatus: announceDb.setStatus, linkAnnounceJob: posts.linkAnnounceJob, markAnnounced: posts.markAnnounced, getById: posts.getById, } afterEach(() => Object.assign(announceDb, { create: originals.create, findById: originals.findById, findByPostId: originals.findByPostId, findDue: originals.findDue, updateLeg: originals.updateLeg, ensureLegs: originals.ensureLegs, setStatus: originals.setStatus, }) && Object.assign(posts, { linkAnnounceJob: originals.linkAnnounceJob, markAnnounced: originals.markAnnounced, getById: originals.getById, })) // Two legs that record what they were asked to do, standing in for core's // discord and a module's own. function fakeLegs() { const calls = [] return { calls, a: { leg: 'discord', label: 'Discord #news', dispatch: async (p) => { calls.push(['discord', p.id]); return { ok: true } }, classify: (r) => (r.ok ? { outcome: 'done' } : { outcome: 'retry', error: 'x' }) }, b: { leg: 'rust.motd', label: 'Server MOTD', dispatch: async (p) => { calls.push(['rust.motd', p.id]); return { ok: false, status: 503 } }, classify: (r) => (r.ok ? { outcome: 'done' } : { outcome: 'retry', error: 'down' }) }, } } beforeEach(() => registries._reset()) function register(...legs) { const api = registries.stage('core') for (const l of legs) api.registerAnnounceLeg(l) registries.apply(api.staged) } // ── Enqueue ──────────────────────────────────────────────────────────────── test('enqueue creates one leg row per REGISTERED leg, whatever they are', () => { const legs = fakeLegs() register(legs.a, legs.b) let created = null announceDb.create = async (postId, ids) => { created = { postId, ids }; return 42 } posts.linkAnnounceJob = async () => {} return announceJobs.enqueue(9).then((jobId) => { assert.equal(jobId, 42) assert.deepEqual(created, { postId: 9, ids: ['discord', 'rust.motd'] }) }) }) test('with no legs registered, a job is created with none — and rolls up done', async () => { let created = null announceDb.create = async (postId, ids) => { created = { postId, ids }; return 1 } posts.linkAnnounceJob = async () => {} await announceJobs.enqueue(9) assert.deepEqual(created.ids, []) announceDb.findById = async () => ({ id: 1, post_id: 9, status: 'pending', legs: [] }) const statuses = [] announceDb.setStatus = async (_id, s) => statuses.push(s) posts.markAnnounced = async () => {} const job = await announceJobs.refreshStatus(1) assert.equal(job.status, 'done') assert.deepEqual(statuses, ['done']) }) // ── The worker dispatches through the registration ───────────────────────── test('the worker dispatches each due leg through whatever registered it', async () => { const legs = fakeLegs() register(legs.a, legs.b) posts.getById = async () => ({ id: 5, title: 't', excerpt: 'e', body: null }) const updates = [] announceDb.findDue = async () => [{ id: 1, post_id: 5, status: 'pending', legs: [ { leg: 'discord', status: 'pending', attempts: 0, next_attempt_at: null }, { leg: 'rust.motd', status: 'pending', attempts: 0, next_attempt_at: null }, ], }] announceDb.updateLeg = async (jobId, leg, fields) => updates.push([leg, fields.status]) announceDb.findById = async () => ({ id: 1, post_id: 5, status: 'pending', legs: [] }) announceDb.setStatus = async () => {} posts.markAnnounced = async () => {} await worker.tick(new Date()) assert.deepEqual(legs.calls, [['discord', 5], ['rust.motd', 5]]) // discord delivered; rust.motd got a 503, so it is rescheduled, not failed. assert.deepEqual(updates, [['discord', 'done'], ['rust.motd', 'pending']]) }) test('a leg row nobody registers any more is left alone, not failed', async () => { // Its module was uninstalled. Failing it would roll the job up terminal on the // strength of a leg that no longer exists, and reinstalling should resume it. register(fakeLegs().a) let touched = false announceDb.updateLeg = async () => { touched = true } posts.getById = async () => { throw new Error('must not even look up the post') } await worker.processLeg({ id: 1, post_id: 5, legs: [{ leg: 'gone.leg', status: 'pending', attempts: 0 }] }, 'gone.leg') assert.equal(touched, false) }) test('isLegDue reads the row, not a leg-prefixed column', () => { const past = new Date(Date.now() - 1000) const future = new Date(Date.now() + 60_000) assert.equal(worker.isLegDue({ status: 'pending', next_attempt_at: null }, new Date()), true) assert.equal(worker.isLegDue({ status: 'pending', next_attempt_at: past }, new Date()), true) assert.equal(worker.isLegDue({ status: 'pending', next_attempt_at: future }, new Date()), false) assert.equal(worker.isLegDue({ status: 'done', next_attempt_at: null }, new Date()), false) assert.equal(worker.isLegDue({ status: 'failed', next_attempt_at: null }, new Date()), false) }) // ── Retry, and the label the panel renders ───────────────────────────────── test('resetLeg refuses a leg nobody registered', async () => { register(fakeLegs().a) await assert.rejects(() => announceJobs.resetLeg(5, 'rust.motd'), /unknown announce leg/) }) test('resetLeg creates the row when a module was installed after the job', async () => { // Otherwise the retry button could never deliver a newly-installed module's // leg on an already-announced post: the worker only sees rows that exist. const legs = fakeLegs() register(legs.a, legs.b) const ensured = [] announceDb.findByPostId = async () => ({ id: 1, post_id: 5, status: 'partial', legs: [{ leg: 'discord', status: 'done' }] }) announceDb.ensureLegs = async (jobId, ids) => ensured.push([jobId, ids]) announceDb.updateLeg = async () => {} announceDb.findById = async () => ({ id: 1, post_id: 5, status: 'partial', legs: [{ leg: 'discord', status: 'done' }, { leg: 'rust.motd', status: 'pending' }] }) announceDb.setStatus = async () => {} const job = await announceJobs.resetLeg(5, 'rust.motd') assert.deepEqual(ensured, [[1, ['rust.motd']]]) assert.deepEqual(job.legs.map((l) => l.label), ['Discord #news', 'Server MOTD']) }) test('a leg’s label comes from its registration, and an orphan keeps its id', async () => { register(fakeLegs().a) announceDb.findByPostId = async () => ({ id: 1, post_id: 5, status: 'partial', legs: [{ leg: 'discord', status: 'done' }, { leg: 'gone.leg', status: 'pending' }], }) const job = await announceJobs.getByPostId(5) assert.deepEqual(job.legs.map((l) => [l.leg, l.label]), [ ['discord', 'Discord #news'], ['gone.leg', 'gone.leg'], ]) }) // ── recordOutcome reads attempts off the row ─────────────────────────────── test('recordOutcome takes the attempt count from the leg row', async () => { register(fakeLegs().a) const updates = [] announceDb.updateLeg = async (jobId, leg, fields) => updates.push(fields) announceDb.findById = async () => ({ id: 1, post_id: 5, status: 'pending', legs: [{ leg: 'discord', status: 'pending' }] }) announceDb.setStatus = async () => {} const job = { id: 1, post_id: 5, legs: [{ leg: 'discord', status: 'pending', attempts: 2 }] } await announceJobs.recordOutcome(job, 'discord', { outcome: 'retry', error: 'nope' }) assert.equal(updates[0].attempts, 3) assert.ok(updates[0].nextAttemptAt instanceof Date) })