Add Klipper, OctoPrint and PrusaLink printers from the API

POST /farms/:id/printers now takes any of the four vendors the gateway can
drive. Credentials depend on the vendor: Bambu needs an access code and
serial, OctoPrint and PrusaLink an API key, Klipper nothing. A missing one
is a 422 naming what is needed. The API key is relayed to the gateway and
kept out of logs and errors like the access code.

The printer_vendor enum gains octoprint (migration 0006); PrusaLink is
stored as prusa, the name the enum already had.

scripts/e2e-vendors.sh adds each kind through the API against a fake REST
printer (scripts/fake-http-printer.ts) and runs a job through the real
scheduler to completion. The fake shares its author's reading of the docs
with the driver, so this proves the plumbing, not the protocols.
This commit is contained in:
2026-10-30 07:49:31 +00:00
parent 69b6efea9b
commit d53c22b641
9 changed files with 1171 additions and 12 deletions
+19 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { gatewayIdFor, safeGatewayError } from "../src/provision/plan";
import { dbVendorFor, gatewayIdFor, missingCredential, safeGatewayError } from "../src/provision/plan";
describe("gatewayIdFor", () => {
const accepted = /^[A-Za-z0-9_-]{1,64}$/;
@@ -38,3 +38,21 @@ describe("safeGatewayError", () => {
expect(safeGatewayError(undefined, [])).toBe("the gateway did not say why");
});
});
describe("missingCredential", () => {
test("a Bambu needs its access code and serial number", () => {
expect(missingCredential("bambu", {})).toBe("an access code");
expect(missingCredential("bambu", { accessCode: "12345678" })).toBe("a serial number");
expect(missingCredential("bambu", { accessCode: "12345678", serialNumber: "01P00A123" })).toBeUndefined();
});
test("OctoPrint and PrusaLink need an API key; Klipper needs nothing", () => {
expect(missingCredential("octoprint", { accessCode: "x" })).toBe("an API key");
expect(missingCredential("prusa_link", { apiKey: " " })).toBe("an API key");
expect(missingCredential("prusa_link", { apiKey: "k" })).toBeUndefined();
expect(missingCredential("klipper", {})).toBeUndefined();
});
test("the gateway's PrusaLink is stored under the database's older name", () => {
expect(dbVendorFor("prusa_link")).toBe("prusa");
expect(dbVendorFor("octoprint")).toBe("octoprint");
});
});