const { test } = require('node:test') const assert = require('node:assert/strict') const logic = require('../src/model/announceJobs/announceJobs.logic') // Each leg owns its own text-building and result classification since PR 4 — a // leg is a registration now, not a branch in the worker (MODULE_SYSTEM.md §1.8). const townCrier = require('../src/utils/shardAnnounce') const discord = require('../src/utils/discordAnnounce') // ── buildTownCrierText ─────────────────────────────────────────────────────── test('buildTownCrierText produces title, excerpt, and URL lines', () => { const lines = townCrier.buildTownCrierText( { id: 7, title: 'Server Update', excerpt: 'Big things afoot.', body: null }, { baseUrl: 'https://uom.example' }, ) assert.deepEqual(lines, ['Server Update', 'Big things afoot.', 'https://uom.example/site/news']) }) test('buildTownCrierText falls back to a stripped body when excerpt is empty', () => { const lines = townCrier.buildTownCrierText( { id: 1, title: 'T', excerpt: '', body: '
Hello world
' }, { baseUrl: 'https://uom.example' }, ) assert.equal(lines[1], 'Hello world') }) test('buildTownCrierText clamps each line to the sidecar per-line cap', () => { const longTitle = 'x'.repeat(500) const lines = townCrier.buildTownCrierText( { id: 1, title: longTitle, excerpt: 'y'.repeat(500), body: null }, { baseUrl: 'https://uom.example' }, ) for (const line of lines) assert.ok(line.length <= townCrier.MAX_LINE_LEN, `line too long: ${line.length}`) assert.ok(lines[0].endsWith('…')) assert.ok(lines.length <= townCrier.MAX_LINES) }) test('buildTownCrierText omits the excerpt line when there is no excerpt or body', () => { const lines = townCrier.buildTownCrierText( { id: 1, title: 'Only a title', excerpt: null, body: null }, { baseUrl: 'https://uom.example' }, ) assert.deepEqual(lines, ['Only a title', 'https://uom.example/site/news']) }) // ── classifyTownCrier ──────────────────────────────────────────────────────── test('town crier classify: 2xx is done', () => { assert.equal(townCrier.classify({ ok: true, status: 200 }).outcome, 'done') }) test('town crier classify: over-cap / auth / protocol errors are terminal (no retry)', () => { for (const status of [400, 401, 409]) { assert.equal(townCrier.classify({ ok: false, status }).outcome, 'terminal', `status ${status}`) } }) test('town crier classify: shard-transient and network errors retry', () => { for (const status of [503, 504, 500, 0]) { assert.equal(townCrier.classify({ ok: false, status }).outcome, 'retry', `status ${status}`) } }) // ── classifyDiscord ────────────────────────────────────────────────────────── test('discord classify: ok is done, every failure retries', () => { assert.equal(discord.classify({ ok: true, status: 200 }).outcome, 'done') for (const status of [400, 503, 0]) { assert.equal(discord.classify({ ok: false, status }).outcome, 'retry', `status ${status}`) } }) // ── scheduleAfter (backoff) ────────────────────────────────────────────────── test('scheduleAfter returns increasing delays then null at the attempt cap', () => { const d1 = logic.scheduleAfter(1) const d2 = logic.scheduleAfter(2) assert.ok(d1 > 0 && d2 > d1, 'delays should grow') // Exhausted once attempts reach MAX_ATTEMPTS. assert.equal(logic.scheduleAfter(logic.MAX_ATTEMPTS), null) assert.equal(logic.scheduleAfter(logic.MAX_ATTEMPTS + 3), null) }) // ── rollupStatus ───────────────────────────────────────────────────────────── // Takes the LIST of leg statuses, not two named legs: which legs exist is what a // module decides (MODULE_SYSTEM.md §1.8). test('rollupStatus derives the parent status from its legs', () => { assert.equal(logic.rollupStatus(['done', 'done']), 'done') assert.equal(logic.rollupStatus(['failed', 'failed']), 'failed') assert.equal(logic.rollupStatus(['pending', 'pending']), 'pending') // One terminal, the others not matching → partial. assert.equal(logic.rollupStatus(['done', 'pending']), 'partial') assert.equal(logic.rollupStatus(['pending', 'failed']), 'partial') assert.equal(logic.rollupStatus(['done', 'failed']), 'partial') }) test('rollupStatus generalises past two legs', () => { assert.equal(logic.rollupStatus(['done', 'done', 'done']), 'done') assert.equal(logic.rollupStatus(['done', 'done', 'pending']), 'partial') assert.equal(logic.rollupStatus(['pending', 'pending', 'pending']), 'pending') assert.equal(logic.rollupStatus(['failed', 'failed', 'failed']), 'failed') // A single leg is not a special case. assert.equal(logic.rollupStatus(['pending']), 'pending') assert.equal(logic.rollupStatus(['done']), 'done') // No legs registered at all: nothing is left to deliver, so the job is done // rather than pending forever on a leg that does not exist. assert.equal(logic.rollupStatus([]), 'done') })