import { describe, expect, it } from 'vitest' import { mergeParts, queueKey } from '../src/utils/systemParts.js' // The System tab's one table (operator 2026-09-23: "combine the two sections // into a single table"). What is pinned here is the JOIN, because its failure // is silent and is precisely the thing the merge exists to fix: a lane that // stops matching its roster part does not throw — the table grows a SECOND row // for the same worker, one with controls and one without. const PART = { key: 'celery:default,download,import,thumbnail', kind: 'celery', name: 'Worker', state: 'ok', detail: 'Worker is running', // The roster SORTS a celery part's queues into its key. queues: ['default', 'download', 'import', 'thumbnail'], } const LANE = { name: 'worker', display_name: 'Worker', // The lane table lists them in the order the role reads them, which is NOT // sorted. If the join ever compares these two lists directly rather than as // sets, this fixture is what catches it. queues: ['default', 'import', 'thumbnail', 'download'], slots: 2, slots_cap: 4, autoscale: false, live: { present: true, replicas: 1, pool: 2, active: 0 }, pending: 0, } const POSTGRES = { key: 'postgres', kind: 'datastore', name: 'PostgreSQL', state: 'ok', detail: 'answering', latency_ms: 2.5, } describe('queueKey', () => { it('does not care what order either side lists its queues in', () => { expect(queueKey(LANE.queues)).toBe(queueKey(PART.queues)) }) it('survives a part that has no queues at all', () => { // A datastore, and also the stale `Worker ()` row a previous deployment // left in the roster with an empty queue set. Neither must match a lane. expect(queueKey(undefined)).toBe('') expect(queueKey([])).toBe('') }) }) describe('mergeParts', () => { it('gives a worker ONE row, carrying its controls', () => { const rows = mergeParts([PART, POSTGRES], [LANE]) expect(rows).toHaveLength(2) const worker = rows.find((r) => r.name === 'Worker') expect(worker.lane).toBe(LANE) expect(rows.filter((r) => r.name === 'Worker')).toHaveLength(1) }) it('leaves a datastore without a lane rather than guessing one', () => { const pg = mergeParts([PART, POSTGRES], [LANE]).find((r) => r.key === 'postgres') expect(pg.lane).toBeUndefined() }) it('still lists a lane the roster has never seen', () => { // Parts are learned as they appear; the lane table is known up front. The // lane an operator most needs to find — an optional one, never started — // is exactly the one with no roster entry. const ml = { ...LANE, name: 'ml', display_name: 'ML tagging', queues: ['ml'], slots: 0, optional: true, live: { present: false, replicas: 0, pool: null, active: 0 }, } const rows = mergeParts([POSTGRES], [ml]) const row = rows.find((r) => r.name === 'ML tagging') expect(row.lane).toBe(ml) expect(row.kindLabel).toBe('optional lane') }) it('does not call a lane at zero slots broken', () => { // The roster only knows a heartbeat age, so it goes on saying "is running" // for a lane the operator deliberately dialled to nothing. The lane knows // the difference; reporting the operator's own choice as a fault is how an // indicator stops being read. const off = { ...LANE, slots: 0 } const row = mergeParts([PART], [off])[0] expect(row.detail).toBe('off — no slots') }) it('puts the broken thing first, whatever it is', () => { const down = { ...POSTGRES, state: 'down', detail: 'not answering' } const rows = mergeParts([PART, down], [LANE]) expect(rows[0].name).toBe('PostgreSQL') }) it('otherwise puts the rows you can act on first', () => { const rows = mergeParts([POSTGRES, PART], [LANE]) expect(rows.map((r) => r.name)).toEqual(['Worker', 'PostgreSQL']) }) it('reports a wedged lane, and only through the reporter it was given', () => { // `laneStuckFor` is passed in rather than imported, so this file does not // re-test the store's rule — it tests that the merge asks. const asked = [] const rows = mergeParts([PART], [LANE], (lane) => { asked.push(lane.name) return '40 minutes' }) expect(asked).toEqual(['worker']) expect(rows[0].stuckFor).toBe('40 minutes') // The roster still owns a matched row's state — `stuckFor` is a note // beside it, not a verdict that overrides the heartbeat. expect(rows[0].state).toBe('ok') }) it('handles an empty everything without inventing rows', () => { expect(mergeParts([], [])).toEqual([]) expect(mergeParts(undefined, undefined)).toEqual([]) }) })