Files
continuum-backend/tests/stream.test.ts
T
octoturge a190c896da Relay P1/A1 camera frames to viewers
GET /printers/:id/snapshot asks the farm's gateway for one JPEG frame
(camera_snapshot_request) and returns it no-store. Concurrent viewers
share one read and a frame under 800 ms old is reused, because the
printer's camera serves one client. /stream answers kind "snapshot" for
these printers; printers.camera_snapshot is set from telemetry.

scripts/e2e-camera-snapshot.sh reads real frames from the P1P through
the real proxy and backend.
2026-10-29 16:11:10 +00:00

86 lines
4.0 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { Value } from "@sinclair/typebox/value";
import { EdgeClientMessageSchema } from "@continuum/ts-types";
import { NO_CAMERA_MESSAGE, NO_GATEWAY_MESSAGE, NO_SNAPSHOT_MESSAGE, resolveSnapshot, resolveStream } from "../src/stream";
const hello = { type: "hello", gateway_id: "gw-1", farm_id: "f1", token: "t" };
describe("hello stream_base_url", () => {
test("is accepted when present", () => {
const msg = { ...hello, stream_base_url: "http://gateway.local:1984" };
expect(Value.Check(EdgeClientMessageSchema, msg)).toBe(true);
});
test("is accepted when absent, the normal case", () => {
expect(Value.Check(EdgeClientMessageSchema, hello)).toBe(true);
});
test("tolerates an explicit null from a serde producer", () => {
expect(Value.Check(EdgeClientMessageSchema, { ...hello, stream_base_url: null })).toBe(true);
});
});
describe("telemetry camera_stream", () => {
const telemetry = (extra: object) => ({
type: "telemetry",
printers: [{ printer_id: "p1", name: "X1", ...extra }],
});
// The persistence step itself is `snapshot.camera_stream ?? null` inside
// persistTelemetry, which needs a database; what is checked here is that
// the value survives the validator that guards that path.
test("survives validation with its value intact", () => {
const msg = telemetry({ camera_stream: "p1" });
expect(Value.Check(EdgeClientMessageSchema, msg)).toBe(true);
expect((msg.printers[0] as { camera_stream?: string }).camera_stream).toBe("p1");
});
test("is optional", () => {
expect(Value.Check(EdgeClientMessageSchema, telemetry({}))).toBe(true);
});
});
describe("resolveStream", () => {
// No streamUrl any more, and its absence is the point: a browser cannot
// play from a gateway's own address (no CORS, and a workshop NAT besides),
// so handing one out only invited a player that could never load. What is
// left is the camera name to negotiate for.
test("returns the endpoint's success shape", () => {
expect(resolveStream("p1", true)).toEqual({ ok: true, stream: { cameraId: "p1" } });
});
test("the two failures are distinguishable", () => {
const noCamera = resolveStream(null, true);
const offline = resolveStream("p1", false);
if (noCamera.ok || offline.ok) throw new Error("expected both to fail");
expect(noCamera.reason).toBe("no_camera");
expect(offline.reason).toBe("gateway_offline");
expect(noCamera.message).toBe(NO_CAMERA_MESSAGE);
expect(offline.message).toBe(NO_GATEWAY_MESSAGE);
// Different problems for whoever is debugging: a P1/A1 that can never
// stream, versus a gateway that is merely unplugged.
expect(noCamera.message).not.toBe(offline.message);
});
test("a printer with no camera is blamed first, even if the gateway is also absent", () => {
const both = resolveStream(undefined, false);
expect(!both.ok && both.reason).toBe("no_camera");
});
});
describe("snapshot cameras (P1/A1)", () => {
test("telemetry and the gateway's answer survive the validators", () => {
expect(
Value.Check(EdgeClientMessageSchema, {
type: "telemetry",
printers: [{ printer_id: "p1", name: "P1P", camera_snapshot: true }],
}),
).toBe(true);
expect(Value.Check(EdgeClientMessageSchema, { type: "camera_snapshot", request_id: "r", jpeg_base64: "/9j/" })).toBe(true);
expect(Value.Check(EdgeClientMessageSchema, { type: "camera_snapshot", request_id: "r", error: "no frame" })).toBe(true);
expect(Value.Check(EdgeClientMessageSchema, { type: "camera_snapshot", jpeg_base64: "/9j/" })).toBe(false);
});
test("the printer is checked before the gateway", () => {
expect(resolveSnapshot(false, false)).toMatchObject({ ok: false, reason: "no_camera", message: NO_SNAPSHOT_MESSAGE });
expect(resolveSnapshot(true, false)).toMatchObject({ ok: false, reason: "gateway_offline", message: NO_GATEWAY_MESSAGE });
expect(resolveSnapshot(true, true)).toEqual({ ok: true });
});
});