Files
octoturge d53c22b641 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.
2026-10-30 07:49:31 +00:00

59 lines
2.6 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { dbVendorFor, gatewayIdFor, missingCredential, safeGatewayError } from "../src/provision/plan";
describe("gatewayIdFor", () => {
const accepted = /^[A-Za-z0-9_-]{1,64}$/;
test("makes a readable id the gateway will accept", () => {
expect(gatewayIdFor("Bench P1P", () => 0)).toBe("bench-p1p-0000");
expect(gatewayIdFor("Bench P1P", () => 0.999999)).toMatch(accepted);
});
test("survives names with nothing usable in them", () => {
expect(gatewayIdFor("***", () => 0.5)).toMatch(/^printer-[0-9a-f]{4}$/);
expect(gatewayIdFor("Принтер", () => 0.5)).toMatch(accepted);
});
test("never exceeds what the gateway accepts, whatever the name", () => {
expect(gatewayIdFor("x".repeat(500))).toMatch(accepted);
expect(gatewayIdFor("../../etc/passwd")).toMatch(accepted);
});
test("two printers with the same name get different ids", () => {
const ids = new Set(Array.from({ length: 50 }, () => gatewayIdFor("Same name")));
expect(ids.size).toBeGreaterThan(30);
});
});
describe("safeGatewayError", () => {
test("passes an ordinary message through", () => {
expect(safeGatewayError("could not connect to the printer", ["12345678"])).toBe(
"could not connect to the printer",
);
});
test("removes a credential that somehow got into the message", () => {
expect(safeGatewayError("bad login 12345678 for host", ["12345678"])).toBe("bad login <hidden> for host");
});
test("has an answer when the gateway gave none", () => {
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");
});
});