Files
octoturge 6a2457315e Add a switch to turn job dispatch off, and use it in the dev stack
The scheduler sends a queued job to any idle claimed printer in any farm.
The dashboard dev stack is wired to the real printers, so testing the job
upload UI there would have started a real print on the first idle one.

DISPATCH_ENABLED=false (exactly that string) disables the timer and the
nudge from POST /jobs; any other value that looks like an attempt to
disable it is warned about, because a typo that silently leaves it on is
the dangerous direction. scripts/e2e-camera.sh now always runs the backend
with it off.
2026-10-29 14:35:40 +00:00

92 lines
4.0 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { nextJobStatus } from "../src/dispatch/job_status";
/**
* The job-status mapping, tested exhaustively.
*
* This is the logic that decides whether a print happened. It exists because
* a gateway accepting `start_print` says nothing about the outcome: a real
* end-to-end run on a P1P (2026-10-15) was accepted, uploaded byte-perfect,
* reached RUNNING and then the printer paused itself on a physical
* broken-filament fault. Job state is therefore derived from the telemetry
* that follows, and getting this table wrong means the API reports prints
* that never ran - the one failure nobody would catch by looking.
*/
describe("nextJobStatus", () => {
const currents = ["uploading", "printing", "paused"] as const;
const printerStates = ["idle", "printing", "paused", "error"] as const;
// Written out rather than computed: a table derived the same way the
// implementation derives it agrees with a wrong implementation.
const expected: Record<string, string | undefined> = {
"uploading/idle": undefined,
"uploading/printing": "printing",
"uploading/paused": "paused",
"uploading/error": "failed",
"printing/idle": "completed",
"printing/printing": "printing",
"printing/paused": "paused",
"printing/error": "failed",
"paused/idle": "completed",
"paused/printing": "printing",
"paused/paused": "paused",
"paused/error": "failed",
};
for (const current of currents) {
for (const printerState of printerStates) {
const key = `${current}/${printerState}`;
test(`${key} -> ${expected[key] ?? "unchanged"}`, () => {
expect(nextJobStatus(current, printerState)).toBe(expected[key] as never);
});
}
}
test("an idle printer does NOT complete a job that was still uploading", () => {
// The case worth its own test. A printer is idle for the entire transfer
// - minutes of it, at the 0.2 MiB/s measured over FTPS - so "idle" there
// means "has not started", not "finished". Reading it as completion
// would mark every job successful seconds after dispatch, before the
// file had even reached the machine.
expect(nextJobStatus("uploading", "idle")).toBeUndefined();
});
test("a printer error fails the job from any state, including mid-upload", () => {
// The P1P's self-pause arrived while the job was still `uploading` by
// the cloud's reckoning, because the printer faulted during its
// pre-print stages. If `uploading` were excluded here, that job would
// have sat claiming to upload until the reaper got it twenty minutes
// later.
for (const current of currents) {
expect(nextJobStatus(current, "error")).toBe("failed");
}
});
test("completion is never reported from a state the print had not begun in", () => {
// Guards the invariant rather than the table: the only route to
// "completed" is through a state that means the printer had actually
// been printing.
const completable = currents.filter((c) => nextJobStatus(c, "idle") === "completed");
expect(completable).toEqual(["printing", "paused"]);
});
});
describe("the dispatch switch", () => {
test("is on by default and off only for the exact string false", async () => {
const { dispatchEnabled } = await import("../src/dispatch/enabled");
expect(dispatchEnabled({})).toBe(true);
expect(dispatchEnabled({ DISPATCH_ENABLED: "true" })).toBe(true);
expect(dispatchEnabled({ DISPATCH_ENABLED: "false" })).toBe(false);
});
test("a value that looks like an attempt to disable it but does not is reported", async () => {
const { dispatchEnabled, dispatchSwitchWarning } = await import("../src/dispatch/enabled");
for (const typo of ["0", "off", "False", "no"]) {
expect(dispatchEnabled({ DISPATCH_ENABLED: typo })).toBe(true);
expect(dispatchSwitchWarning({ DISPATCH_ENABLED: typo })).toContain("WILL be dispatched");
}
expect(dispatchSwitchWarning({})).toBeUndefined();
expect(dispatchSwitchWarning({ DISPATCH_ENABLED: "false" })).toBeUndefined();
});
});