import { test } from "node:test"; import assert from "node:assert/strict"; import { getRegistry, newJobId, type JobRecord } from "./registry"; // Run with: pnpm --filter yt-dlp-transcript-common exec tsx --test common/jobs/registry.test.ts // // Covers the per-queue priority insertion added so a manually clicked Sync // (foreground) preempts the auto-download runner's queued units (background) // without interrupting one already running. Each test uses a UNIQUE queueKey so // the shared global registry singleton doesn't leak state between tests. function rec(queueKey: string, background?: boolean): JobRecord { return { id: newJobId(), kind: "test", queueKey, background, status: "queued", queuedAt: 0, logPath: "", }; } // enqueue() needs start/onCancel callbacks; track which fired. In production // makeJob() registers the record before enqueue (so cancel/finalize/position // can look it up by id), so mirror that here. function enqueue(record: JobRecord) { const calls = { started: false, cancelled: false }; getRegistry().register(record); const result = getRegistry().enqueue(record, { start: () => { calls.started = true; }, onCancel: () => { calls.cancelled = true; }, }); return { result, calls }; } test("first job on a queue runs immediately", () => { const q = `test:first:${newJobId()}`; const a = rec(q); const { result, calls } = enqueue(a); assert.equal(result.willRunNow, true); assert.equal(result.position, 0); assert.equal(a.status, "running"); assert.equal(calls.started, true); }); test("foreground inserts AHEAD of a queued background job", () => { const q = `test:preempt:${newJobId()}`; const head = rec(q); // runs immediately, never moves enqueue(head); const bg = rec(q, true); // queued background const bgRes = enqueue(bg); assert.equal(bgRes.result.position, 1); const fg = rec(q); // foreground arrives after bg but should jump ahead of it const fgRes = enqueue(fg); assert.equal(fgRes.result.position, 1, "foreground slots in at index 1"); assert.equal( getRegistry().positionInQueue(bg.id), 2, "background pushed back to index 2", ); assert.equal(getRegistry().positionInQueue(head.id), 0, "running head fixed"); }); test("foreground jobs keep FIFO among themselves, before background", () => { const q = `test:fifo:${newJobId()}`; const head = rec(q); enqueue(head); const bg = rec(q, true); enqueue(bg); const fg1 = rec(q); enqueue(fg1); const fg2 = rec(q); enqueue(fg2); // Order: head(0), fg1(1), fg2(2), bg(3) assert.equal(getRegistry().positionInQueue(fg1.id), 1); assert.equal(getRegistry().positionInQueue(fg2.id), 2); assert.equal(getRegistry().positionInQueue(bg.id), 3); }); test("background jobs always append to the back", () => { const q = `test:bgback:${newJobId()}`; const head = rec(q); enqueue(head); const bg1 = rec(q, true); enqueue(bg1); const bg2 = rec(q, true); const bg2Res = enqueue(bg2); assert.equal(bg2Res.result.position, 2, "second background lands at the tail"); }); test("finalize promotes the foreground job before the background one", () => { const q = `test:promote:${newJobId()}`; const head = rec(q); enqueue(head); const bg = rec(q, true); const bgStart = enqueue(bg); const fg = rec(q); const fgStart = enqueue(fg); assert.equal(bgStart.calls.started, false); assert.equal(fgStart.calls.started, false); // Head finishes → q[0] (the foreground job) is promoted and started. getRegistry().finalize(head.id, "done"); assert.equal(fg.status, "running", "foreground promoted first"); assert.equal(fgStart.calls.started, true); assert.equal(bg.status, "queued", "background still waiting"); }); test("cancelling a queued background job fires onCancel and removes it", () => { const q = `test:cancelbg:${newJobId()}`; const head = rec(q); enqueue(head); const bg = rec(q, true); const bgStart = enqueue(bg); const ok = getRegistry().cancel(bg.id); assert.equal(ok, true); assert.equal(bgStart.calls.cancelled, true); assert.equal(bg.status, "cancelled"); assert.equal( getRegistry().positionInQueue(bg.id), -1, "removed from its queue", ); }); // Release 13 slice W1: a managed job persists its sidecar in onCancel, so a // queued job must already be terminal when the scheduler fires it. It used to // be marked after, and the sidecar kept "queued". test("a queued job is already cancelled when its onCancel runs", () => { const q = `test:cancel-order:${newJobId()}`; enqueue(rec(q)); // the head, running const queued = rec(q); let seen: { status: string; endedAt?: number } | null = null; getRegistry().register(queued); getRegistry().enqueue(queued, { start: () => {}, onCancel: () => { seen = { status: queued.status, endedAt: queued.endedAt }; }, }); assert.equal(getRegistry().cancel(queued.id), true); assert.equal(seen!.status, "cancelled"); assert.equal(typeof seen!.endedAt, "number"); assert.equal(getRegistry().positionInQueue(queued.id), -1); });