import { describe, expect, it } from "bun:test"; import { Value } from "@sinclair/typebox/value"; import { EdgeClientMessageSchema, EdgeServerMessageSchema } from "@continuum/ts-types"; import currentCapture from "./fixtures/gateway_frames.json"; import legacyCapture from "./fixtures/gateway_frames_legacy_nulls.json"; /** * A contract test, not a unit test: it checks that what continuum-proxy * actually puts on the wire is what this backend's validator accepts. * * The fixtures are real captures from a live gateway talking to a real P1P and * H2C, not hand-written examples - which is the whole point. The two ends of * /ws/edge/v1 had silently drifted into completely different protocols * (different handshake, different telemetry shape, printer ids the gateway * could not know), and every hand-written example agreed with whichever side * wrote it. Re-capture with scripts/capture-gateway-frames.js when the protocol * changes, rather than editing these by hand. */ function check(schema: Parameters[0], value: unknown) { const ok = Value.Check(schema, value); if (!ok) { // Surface *why*, or a failure here is just "false !== true". const errors = [...Value.Errors(schema, value)].map( (e) => `${e.path || "/"}: ${e.message} (got ${JSON.stringify(e.value)})`, ); throw new Error(`validation failed:\n ${errors.join("\n ")}`); } } describe("gateway -> backend frames", () => { for (const frame of currentCapture.frames) { it(`accepts a real '${frame.type}' frame`, () => { check(EdgeClientMessageSchema, frame); }); } it("accepts the pre-fix capture, where empty fields were explicit nulls", () => { // Liberal reader: the gateway now omits these, but a serde-derived // producer emits null by default and one such field must not cost us a // whole farm's telemetry batch. for (const frame of legacyCapture.frames) { check(EdgeClientMessageSchema, frame); } }); it("carries the live telemetry this was captured from", () => { const telemetry = currentCapture.frames.find((f) => f.type === "telemetry"); expect(telemetry).toBeDefined(); const printers = (telemetry as { printers: Array<{ printer_id: string }> }).printers; expect(printers.length).toBeGreaterThan(0); // Guards against a fixture that validates because it is empty. expect(printers.every((p) => typeof p.printer_id === "string" && p.printer_id.length > 0)).toBe(true); }); it("rejects a telemetry batch whose printer has no id", () => { // printer_id and name are the only required fields; everything else is // genuinely unknowable for a printer that has only just connected. check(EdgeClientMessageSchema, { type: "telemetry", printers: [{ printer_id: "p1p", name: "P1P" }] }); expect(Value.Check(EdgeClientMessageSchema, { type: "telemetry", printers: [{ name: "P1P" }] })).toBe(false); }); it("rejects an unknown message type", () => { // The old protocol's handshake, which no gateway ever sent. expect(Value.Check(EdgeClientMessageSchema, { type: "auth", token: "x" })).toBe(false); }); }); describe("backend -> gateway frames", () => { // Exactly what src/routes/ws/edge.ts sends. If one of these stops // validating, the gateway's serde enum will have stopped accepting it too - // see the mirror of this test in continuum-proxy/tests/uplink_protocol.rs. const outbound = [ { type: "hello_ack", session_id: "3f2b1c5e-0000-4000-8000-000000000000" }, { type: "hello_error", message: "invalid edge token" }, { type: "heartbeat_ack", seq: 7 }, { type: "command", command_id: "9a7d2f10-0000-4000-8000-000000000000", printer_id: "p1p", command: { action: "set_fan_speed", percent: 40 }, }, { type: "command", command_id: "9a7d2f11-0000-4000-8000-000000000000", printer_id: "p1p", command: { action: "send_gcode_line", line: "M106 S0" }, }, { type: "command", command_id: "9a7d2f12-0000-4000-8000-000000000000", printer_id: "p1p", command: { action: "control_print", control: "pause" }, }, { type: "command", command_id: "9a7d2f13-0000-4000-8000-000000000000", printer_id: "p1p", command: { action: "control_print", control: "resume" }, }, { type: "command", command_id: "9a7d2f14-0000-4000-8000-000000000000", printer_id: "p1p", command: { action: "control_print", control: "stop" }, }, ]; for (const frame of outbound) { it(`emits a valid '${frame.type}' (${(frame as { command?: { action: string } }).command?.action ?? "no command"})`, () => { check(EdgeServerMessageSchema, frame); }); } it("rejects a print control verb the gateway does not implement", () => { // "cancel" is the trap: it is what Moonraker calls this, and the Klipper // implementation behind `control_print` really does send a cancel - but // the wire word is "stop". A verb that only one vendor recognises must // not validate here, or it reaches a gateway that cannot map it. for (const control of ["cancel", "abort", "Pause", ""]) { expect( Value.Check(EdgeServerMessageSchema, { type: "command", command_id: "x", printer_id: "p1p", command: { action: "control_print", control }, }), ).toBe(false); } }); it("has no start-print command to send", () => { // Still deliberate, but no longer for the original reason: starting an // uploaded file *has* now been confirmed on a real P1P (2026-10-11). // What is missing is the path around it - nothing uploads a sliced file // to a gateway and nothing decides which printer runs which job. Delete // this test when there is something to start. expect( Value.Check(EdgeServerMessageSchema, { type: "command", command_id: "x", printer_id: "p1p", command: { action: "start_print", file_name: "plate_1.3mf" }, }), ).toBe(false); }); });