#!/usr/bin/env bash # End-to-end test of the /ws/edge/v1 contract against a real database and real # printers: seed a farm, claim its printers, mint an edge token, run the actual # continuum-proxy daemon, and assert that live telemetry reached Postgres and # that a command sent over HTTP reached a printer. # # This is the test that the unit tests cannot be: tests/edge_protocol.test.ts # only proves the two ends agree about the wire format. Nothing but this proves # the printer-id translation, persistTelemetry and sendPrinterCommand actually # work against a database. # # SAFETY: the only command it sends is a fan speed, and only to the printer # named by PRINTER_ID (default p1p), which it refuses to send unless that # printer reports IDLE. It never starts, pauses or cancels a print. Point it at # a printer you can see. # # Usage: # scripts/dev-env.sh up && scripts/dev-env.sh schema # scripts/e2e-edge.sh # # PRINTER_ID=p1p which printer to send the fan command to # PROXY_DIR=../continuum-proxy # KEEP_DATA=1 leave the seeded farm behind for inspection # # The HTTP routes now require a verified Supabase access token, so this starts # scripts/dev-auth.ts - a local RS256 issuer publishing a JWKS at the path the # backend fetches - and mints a token for a seeded org `operator`. That is the # real verification path, not a bypass: see the header of dev-auth.ts. set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$REPO_ROOT" PRINTER_ID="${PRINTER_ID:-p1p}" PROXY_DIR="${PROXY_DIR:-$REPO_ROOT/../continuum-proxy}" PORT="${PORT:-3010}" AUTH_PORT="${AUTH_PORT:-3011}" BASE="http://localhost:$PORT" DATABASE_URL="${DATABASE_URL:-postgres://continuum:continuum@localhost:5432/continuum}" VALKEY_URL="${VALKEY_URL:-redis://localhost:6379}" EDGE_JWT_SECRET="${EDGE_JWT_SECRET:-e2e-edge-secret}" # The backend refuses to boot without object-storage credentials, which is # deliberate - see src/s3/index.ts. Nothing in this test presigns or uploads # anything, so these only have to exist. R2_ACCESS_KEY_ID="${R2_ACCESS_KEY_ID:-e2e}" R2_SECRET_ACCESS_KEY="${R2_SECRET_ACCESS_KEY:-e2e}" R2_ENDPOINT="${R2_ENDPOINT:-http://localhost:9000}" export DATABASE_URL VALKEY_URL EDGE_JWT_SECRET PORT export R2_ACCESS_KEY_ID R2_SECRET_ACCESS_KEY R2_ENDPOINT # This script asserts TURN is unconfigured (below). Sourcing dev-secrets.sh # turns it on, which used to fail the assertion - and print the live # credentials in the failure message. Make the assumption true. unset CLOUDFLARE_TURN_KEY_ID CLOUDFLARE_TURN_API_TOKEN WORK="$(mktemp -d)" BACKEND_PID="" PROXY_PID="" AUTH_PID="" # `uuidgen` is not installed everywhere; the kernel's generator always is. new_uuid() { if [ -r /proc/sys/kernel/random/uuid ]; then cat /proc/sys/kernel/random/uuid else python3 -c 'import uuid; print(uuid.uuid4())' fi } # Uses DATABASE_URL rather than a hardcoded localhost. The backend under test # reads that variable, so a psql pointed anywhere else seeds fixtures into a # different database than the one being exercised - and the failure message # below names DATABASE_URL, which would then be naming a URL nothing tried. psql_q() { psql "$DATABASE_URL" -tAq -c "$1"; } # The farm's operator. A real Supabase user id would come from the project; # here it only has to be a uuid that the token's `sub` and the org_members row # agree on. USER_ID="$(new_uuid)" cleanup() { local rc=$? for pid in "$PROXY_PID" "$BACKEND_PID" "$AUTH_PID"; do [ -z "$pid" ] && continue kill "$pid" 2>/dev/null || true done # A gateway that outlives this script keeps reconnecting with a token for a # farm that is about to be deleted, and pollutes the next run's log. for _ in $(seq 1 10); do [ -n "$PROXY_PID" ] || break kill -0 "$PROXY_PID" 2>/dev/null || break sleep 0.3 done # Never `kill $PID` with an unset PID: `kill 0` signals the whole process # group, which includes this script and whatever launched it. for pid in "$PROXY_PID" "$BACKEND_PID" "$AUTH_PID"; do [ -n "$pid" ] && kill -9 "$pid" 2>/dev/null || true done if [ -z "${KEEP_DATA:-}" ] && [ -n "${ORG_ID:-}" ]; then # Farms cascade from the organisation, printers from the farm. psql_q "DELETE FROM organisations WHERE id = '$ORG_ID'" >/dev/null 2>&1 || true fi if [ $rc -ne 0 ]; then echo echo "--- backend log (tail) ---"; tail -30 "$WORK/backend.log" 2>/dev/null || true echo "--- dev-auth log (tail) ---"; tail -10 "$WORK/auth.log" 2>/dev/null || true echo "--- proxy log (tail) ---"; tail -30 "$WORK/proxy.log" 2>/dev/null || true fi rm -rf "$WORK" exit $rc } trap cleanup EXIT fail() { echo "FAIL: $*" >&2; exit 1; } ok() { echo " ok: $*"; } # --- 0. preconditions ------------------------------------------------------ echo "==> checking the database is up and migrated" psql_q "SELECT 1" >/dev/null 2>&1 || fail "no database at $DATABASE_URL - run scripts/dev-env.sh up" psql_q "SELECT gateway_printer_id FROM printers LIMIT 0" >/dev/null 2>&1 \ || fail "printers.gateway_printer_id is missing - run scripts/dev-env.sh schema" psql_q "SELECT org_id FROM farms LIMIT 0" >/dev/null 2>&1 \ || fail "farms.org_id is missing - run scripts/dev-env.sh schema" ok "schema has the edge columns and the org layer" [ -f "$PROXY_DIR/printers.toml" ] || fail "no $PROXY_DIR/printers.toml - the proxy needs real printers" # --- 1. seed --------------------------------------------------------------- echo "==> seeding an organisation, a farm and claiming its printers" EDGE_PROXY_TOKEN="e2e-$(date +%s)" ORG_ID="$(psql_q "INSERT INTO organisations (name) VALUES ('e2e org') RETURNING id")" [ -n "$ORG_ID" ] || fail "could not create an organisation" # `operator`, not `owner`: the command route requires operator, so seeding the # weakest role that should work proves the boundary is where it claims to be # rather than passing because the test user can do everything. psql_q "INSERT INTO org_members (org_id, user_id, role) VALUES ('$ORG_ID', '$USER_ID', 'operator')" >/dev/null FARM_ID="$(psql_q "INSERT INTO farms (name, org_id, edge_proxy_token) VALUES ('e2e farm', '$ORG_ID', '$EDGE_PROXY_TOKEN') RETURNING id")" [ -n "$FARM_ID" ] || fail "could not create a farm" ok "org $ORG_ID, farm $FARM_ID, operator $USER_ID" # gateway_printer_id is the whole point: it maps the gateway's own ids from # printers.toml onto these rows. Telemetry for an id nothing claims is skipped. while read -r pid pname; do [ -z "$pid" ] && continue psql_q "INSERT INTO printers (farm_id, name, vendor, gateway_printer_id) VALUES ('$FARM_ID', '$pname', 'bambu', '$pid')" >/dev/null ok "claimed '$pid' as '$pname'" done < <(awk -F'"' '/^id *=/ {id=$2} /^name *=/ {print id, $2}' "$PROXY_DIR/printers.toml") # --- 2. backend ------------------------------------------------------------ echo "==> starting the dev-auth JWKS issuer on :$AUTH_PORT" bun scripts/dev-auth.ts --port "$AUTH_PORT" > "$WORK/auth.log" 2>&1 & AUTH_PID=$! AUTH_URL="http://localhost:$AUTH_PORT" for _ in $(seq 1 40); do curl -sf "$AUTH_URL/auth/v1/.well-known/jwks.json" >/dev/null 2>&1 && break sleep 0.25 done curl -sf "$AUTH_URL/auth/v1/.well-known/jwks.json" >/dev/null 2>&1 \ || fail "the dev-auth issuer did not come up: $(cat "$WORK/auth.log")" export SUPABASE_URL="$AUTH_URL" ok "issuer up, SUPABASE_URL=$SUPABASE_URL" USER_TOKEN="$(curl -sf "$AUTH_URL/token?sub=$USER_ID" \ | python3 -c 'import json,sys; print(json.load(sys.stdin)["access_token"])')" [ -n "$USER_TOKEN" ] || fail "could not mint a user token" AUTH_HEADER="authorization: Bearer $USER_TOKEN" ok "minted an access token for the operator" echo "==> starting continuum-backend on :$PORT" bun run src/index.ts > "$WORK/backend.log" 2>&1 & BACKEND_PID=$! for _ in $(seq 1 60); do curl -sf "$BASE/health" >/dev/null 2>&1 && break sleep 0.5 done HEALTH="$(curl -sf "$BASE/health")" || fail "backend did not become healthy" echo "$HEALTH" | grep -q '"db":true' || fail "backend reports the database is down: $HEALTH" echo "$HEALTH" | grep -q '"valkey":true' || fail "backend reports valkey is down: $HEALTH" ok "health: $HEALTH" # --- 3. edge token --------------------------------------------------------- echo "==> minting an edge token" TOKEN="$(curl -sf -X POST "$BASE/auth/edge-token" \ -H 'content-type: application/json' \ -d "{\"farmId\":\"$FARM_ID\",\"edgeProxyToken\":\"$EDGE_PROXY_TOKEN\"}" \ | python3 -c 'import json,sys; print(json.load(sys.stdin).get("token",""))')" [ -n "$TOKEN" ] || fail "no token returned from POST /auth/edge-token" ok "token minted (${#TOKEN} chars)" # --- 4. the real gateway --------------------------------------------------- echo "==> starting the real continuum-proxy against it" ( cd "$PROXY_DIR" && cargo build --quiet 2>&1 | tail -3 ) || fail "proxy did not build" # env -C rather than a `( cd ... ) &` subshell: $! has to be the daemon itself, # or cleanup kills only the wrapper and leaves an orphan reconnecting with a # stale token. An earlier version did exactly that, and the orphan then # reported a deleted farm's printers into the next run. # STREAM_BASE_URL is deliberately set: it is what the gateway advertises in # its hello and the only way GET /printers/:id/stream can answer 200, so # leaving it unset would make the camera assertions below untestable rather # than passing. It does not have to be reachable - nothing here plays video. STREAM_BASE_URL="http://e2e-gateway.invalid:1984" env -C "$PROXY_DIR" \ CONTINUUM_GATEWAY_ID=e2e-gw \ CONTINUUM_FARM_ID="$FARM_ID" \ CONTINUUM_GATEWAY_TOKEN="$EDGE_PROXY_TOKEN" \ CONTINUUM_UPLINK_URL="ws://localhost:$PORT/ws/edge/v1" \ `# Deliberately absent, not an oversight: what is under test is the config` \ `# the gateway writes and the camera_stream it advertises, both of which` \ `# happen without go2rtc. Spawning the real one would bind :1984 and dial` \ `# the printers on the LAN, so the test would depend on hardware it does` \ `# not need. The supervisor logs a spawn failure every 5s; that is expected.` \ CONTINUUM_GO2RTC_BIN=/nonexistent-go2rtc \ CONTINUUM_GO2RTC_CONFIG="$WORK/go2rtc.yaml" \ CONTINUUM_STREAM_BASE_URL="$STREAM_BASE_URL" \ CONTINUUM_PRINTERS_PATH=printers.toml \ RUST_LOG=info \ "$PROXY_DIR/target/debug/continuum-proxy" > "$WORK/proxy.log" 2>&1 & PROXY_PID=$! # Wait for *every* claimed printer, not just the first. Telemetry is on a 5s # timer and a Bambu on protocol v1 reports one tick behind the rest (it only # sends a full status when asked, so each tick reads the previous tick's # answer) - stopping at the first row that appears is how an earlier version of # this script silently tested only the v2 printer. CLAIMED="$(psql_q "SELECT count(*) FROM printers WHERE farm_id='$FARM_ID' AND gateway_printer_id IS NOT NULL")" echo "==> waiting for live telemetry from all $CLAIMED claimed printer(s)" for _ in $(seq 1 45); do COUNT="$(psql_q "SELECT count(*) FROM printers WHERE farm_id='$FARM_ID' AND gateway_printer_id IS NOT NULL AND last_seen_at IS NOT NULL AND vendor_state IS NOT NULL")" [ "${COUNT:-0}" -ge "$CLAIMED" ] && break sleep 1 done [ "${COUNT:-0}" -ge 1 ] || fail "no telemetry was persisted at all (see the proxy log below)" if [ "${COUNT:-0}" -lt "$CLAIMED" ]; then # Not fatal: a printer that is genuinely switched off cannot report, and that # should not fail a run against the ones that can. Name them so the # difference between "off" and "broken" is visible. echo " WARNING: only $COUNT of $CLAIMED printers reported. Silent:" psql_q "SELECT ' - ' || gateway_printer_id FROM printers WHERE farm_id='$FARM_ID' AND gateway_printer_id IS NOT NULL AND (last_seen_at IS NULL OR vendor_state IS NULL)" else ok "all $COUNT claimed printer(s) reported live telemetry" fi if grep "reported unknown printer" "$WORK/backend.log" | grep -q "$FARM_ID"; then fail "the backend could not map a gateway printer id for this farm - check gateway_printer_id" fi ok "every printer id this gateway reported mapped to a row" echo "==> what landed in the database:" psql "$DATABASE_URL" -c \ "SELECT gateway_printer_id AS gw, status, vendor_state, progress_percent AS pct, current_layer || '/' || total_layers AS layers, remaining_minutes AS mins, nozzle_temp_c AS nozzle, bed_temp_c AS bed, active_alerts AS alerts, coalesce(current_job_name, '-') AS job FROM printers WHERE farm_id = '$FARM_ID' ORDER BY gateway_printer_id" # Proves the telemetry is real rather than a row touched with nulls. psql_q "SELECT 1 FROM printers WHERE farm_id='$FARM_ID' AND vendor_state IS NOT NULL AND nozzle_temp_c IS NOT NULL LIMIT 1" | grep -q 1 \ || fail "rows were touched but carry no actual telemetry" ok "rows carry real vendor state and temperatures" # --- 5. a command, the whole way down -------------------------------------- TARGET_UUID="$(psql_q "SELECT id FROM printers WHERE farm_id='$FARM_ID' AND gateway_printer_id='$PRINTER_ID'")" [ -n "$TARGET_UUID" ] || fail "no printer claimed as '$PRINTER_ID'" GW="$(curl -sf -H "$AUTH_HEADER" "$BASE/printers/$TARGET_UUID/gateway")" echo "$GW" | grep -q '"gatewayOnline":true' || fail "the gateway is not registered as online: $GW" ok "gateway reports online for $PRINTER_ID" COMMAND_TESTED=no STATE="$(psql_q "SELECT vendor_state FROM printers WHERE id='$TARGET_UUID'")" if [ "$STATE" != "IDLE" ] && [ "$STATE" != "FINISH" ]; then echo " SKIPPED the command test: $PRINTER_ID reports '${STATE:-no telemetry yet}', not IDLE/FINISH." echo " Refusing to touch a printer that may be mid-print." else echo "==> sending set_fan_speed to $PRINTER_ID ($STATE) over HTTP" RESP="$(curl -s -o "$WORK/cmd.json" -w '%{http_code}' -X POST \ "$BASE/printers/$TARGET_UUID/commands" -H 'content-type: application/json' \ -H "$AUTH_HEADER" -d '{"action":"set_fan_speed","percent":40}')" [ "$RESP" = "200" ] || fail "command returned HTTP $RESP: $(cat "$WORK/cmd.json")" grep -q '"ok":true' "$WORK/cmd.json" || fail "command was not acknowledged: $(cat "$WORK/cmd.json")" ok "command acknowledged by the printer: $(cat "$WORK/cmd.json")" # Put it back, so this leaves the printer as it found it. curl -s -X POST "$BASE/printers/$TARGET_UUID/commands" \ -H 'content-type: application/json' -H "$AUTH_HEADER" \ -d '{"action":"set_fan_speed","percent":0}' > /dev/null ok "fan returned to 0" COMMAND_TESTED=yes fi # --- 5b. the camera path --------------------------------------------------- # Only the X1/H2 class publishes an rtsp_url - confirmed at the network layer: # the H2C accepts TCP on 322, a P1P refuses it and listens on 6000 instead. So # "some printers have a stream and most do not" is the expected state, and both # halves are asserted. A run where no printer has a camera still passes, but # says so rather than claiming the path works. echo "==> the camera path: what the gateway reported, and what the API answers" WITH_CAMERA="$(psql_q "SELECT gateway_printer_id FROM printers WHERE farm_id='$FARM_ID' AND camera_stream IS NOT NULL ORDER BY gateway_printer_id LIMIT 1")" if [ -z "$WITH_CAMERA" ]; then echo " SKIPPED: no printer reported a camera stream." echo " Expected if only P1/A1-class printers are on: they publish no rtsp_url." else # The gateway sets camera_stream to its own printer id, so this also checks # the two ends agree about the name a stream is served under. STREAM_NAME="$(psql_q "SELECT camera_stream FROM printers WHERE farm_id='$FARM_ID' AND gateway_printer_id='$WITH_CAMERA'")" [ "$STREAM_NAME" = "$WITH_CAMERA" ] \ || fail "camera_stream for '$WITH_CAMERA' is '$STREAM_NAME', expected its own gateway id" ok "'$WITH_CAMERA' reported camera stream '$STREAM_NAME'" CAM_UUID="$(psql_q "SELECT id FROM printers WHERE farm_id='$FARM_ID' AND gateway_printer_id='$WITH_CAMERA'")" STREAM="$(curl -sf -H "$AUTH_HEADER" "$BASE/printers/$CAM_UUID/stream")" \ || fail "GET /printers/$CAM_UUID/stream failed for a printer that has a camera" echo "$STREAM" | grep -q "\"cameraId\":\"$WITH_CAMERA\"" \ || fail "wrong cameraId: $STREAM" # Deliberately NOT a URL any more. A browser cannot play from a gateway's # own address - go2rtc sends no CORS headers and 401s an unauthenticated # preflight - so serving one offered a player that could never load. If # this reappears, something has regressed to the pre-relay design. echo "$STREAM" | grep -q '"streamUrl"' \ && fail "the stream endpoint is serving a gateway URL again: $STREAM" # No Cloudflare credentials in this test, so TURN is off and iceServers is # empty. Both are asserted, because an empty list with turnConfigured true # would mean minting silently failed. echo "$STREAM" | grep -q '"turnConfigured":false' \ || fail "expected TURN to be unconfigured in the test env: $STREAM" echo "$STREAM" | grep -q '"iceServers":\[\]' \ || fail "expected no ICE servers when TURN is unconfigured: $STREAM" ok "GET .../stream: $STREAM" # The signalling relay, end to end through the real gateway: an offer goes # cloud -> uplink -> gateway -> its own go2rtc. go2rtc is not running in # this test (see CONTINUUM_GO2RTC_BIN above), so the honest expectation is # a 503 whose reason came back *from the gateway* rather than from a # timeout - that is what proves the round trip happened at all. OFFER_CODE="$(curl -s -o "$WORK/offer.json" -w '%{http_code}' -X POST \ -H "$AUTH_HEADER" -H 'Content-Type: application/json' \ -d '{"sdp":"v=0\r\no=- 0 0 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\n"}' \ "$BASE/printers/$CAM_UUID/webrtc")" # go2rtc is deliberately not running here, so the expected answer is the # gateway reporting that it could not reach it - which still proves the # offer travelled cloud -> uplink -> gateway. A timeout would mean it did # not. Verified once by hand with the real go2rtc and a valid offer, which # returns a 200 and an answer SDP carrying go2rtc's own ICE candidates; # that is not automated here because it needs a live camera and go2rtc # installed, which would make this script depend on both. if [ "$OFFER_CODE" = "503" ] && grep -q 'go2rtc' "$WORK/offer.json"; then ok "the WebRTC offer round-tripped to the gateway: $(head -c 170 "$WORK/offer.json")" elif [ "$OFFER_CODE" = "503" ] && grep -q 'did not answer within' "$WORK/offer.json"; then fail "the gateway never answered the offer - the relay did not round-trip: $(cat "$WORK/offer.json")" else fail "unexpected answer to a WebRTC offer (HTTP $OFFER_CODE): $(cat "$WORK/offer.json")" fi fi # A printer with no camera must 404, and say which half is missing. This is # the case a frontend hits for most of a mixed fleet, so it matters that it is # a clean refusal rather than a 200 carrying nulls. NO_CAMERA="$(psql_q "SELECT id FROM printers WHERE farm_id='$FARM_ID' AND camera_stream IS NULL AND gateway_printer_id IS NOT NULL LIMIT 1")" if [ -n "$NO_CAMERA" ]; then CODE="$(curl -s -o "$WORK/nocam.json" -w '%{http_code}' -H "$AUTH_HEADER" \ "$BASE/printers/$NO_CAMERA/stream")" [ "$CODE" = "404" ] || fail "expected 404 for a printer with no camera, got $CODE" grep -q 'no camera' "$WORK/nocam.json" \ || fail "the 404 should blame the printer's camera, not the gateway: $(cat "$WORK/nocam.json")" ok "a printer with no camera is refused with 404, blaming the right half" fi # --- 5c. the operator console's live feed ---------------------------------- # The user-facing socket, driven for real. The unit tests can only mirror its # composition, because the route imports ../../db; this is the only thing that # proves it is wired into the app at all, that the Supabase token verifies on # the upgrade, and that a gateway's telemetry actually reaches a console. echo "==> /ws/telemetry: connecting as the operator and waiting for a push" WS_OUT="$WORK/telemetry.json" if bun scripts/watch-telemetry.ts "ws://localhost:$PORT" "$USER_TOKEN" 12 > "$WS_OUT" 2>"$WORK/ws.err"; then python3 - "$WS_OUT" <<'PYCHK' || fail "the telemetry socket did not behave: $(cat "$WS_OUT" "$WORK/ws.err")" import json, sys d = json.load(open(sys.argv[1])) # "bearer" and not the token: the runtime echoes the first offered subprotocol # back in the response header, so the credential must not be in that position. assert d["negotiated"] == "bearer", f'echoed subprotocol was {d["negotiated"]!r}, expected "bearer"' kinds = [f.get("type") for f in d["frames"]] assert "ready" in kinds, f"no ready frame; got {kinds}" ready = next(f for f in d["frames"] if f.get("type") == "ready") assert ready["visiblePrinters"] >= 1, f"ready says {ready['visiblePrinters']} visible printers" pushes = [f for f in d["frames"] if f.get("type") == "printer.telemetry"] assert pushes, f"no printer.telemetry push arrived; got {kinds}" # A push must be the printer resource, not a telemetry-shaped subset, and must # carry real readings rather than a touched row of nulls. p = pushes[-1]["printer"] for key in ("id", "farmId", "name", "vendor"): assert key in p, f"push is missing {key}: {p}" assert p.get("vendorState"), f"push carries no vendor state: {p}" print(f' ok: {len(pushes)} push(es); last was {p["name"]} ' f'state={p["vendorState"]} nozzle={p.get("nozzleTempC")} camera={p.get("cameraStream")}') PYCHK else fail "could not connect to /ws/telemetry: $(cat "$WORK/ws.err")" fi ok "the console socket authenticated and received live telemetry" # A socket with no valid credential must not open at all - the whole point of # checking at the upgrade rather than in a first message. echo "==> an unauthenticated console socket must be refused" if bun scripts/watch-telemetry.ts "ws://localhost:$PORT" "not-a-real-token" 5 \ > "$WORK/ws-bad.json" 2>&1; then fail "a socket opened with a bogus token: $(cat "$WORK/ws-bad.json")" fi ok "a bogus token is refused before any socket exists" # --- 6. the negative case -------------------------------------------------- echo "==> an unclaimed printer must be refused, not guessed at" ORPHAN="$(psql_q "INSERT INTO printers (farm_id, name, vendor) VALUES ('$FARM_ID', 'unclaimed', 'bambu') RETURNING id")" CODE="$(curl -s -o "$WORK/orphan.json" -w '%{http_code}' -X POST \ "$BASE/printers/$ORPHAN/commands" -H 'content-type: application/json' \ -H "$AUTH_HEADER" -d '{"action":"set_fan_speed","percent":0}')" [ "$CODE" = "409" ] || fail "expected HTTP 409 for an unclaimed printer, got $CODE" grep -q 'PRINTER_UNREACHABLE' "$WORK/orphan.json" || fail "wrong error: $(cat "$WORK/orphan.json")" ok "unclaimed printer refused with 409 PRINTER_UNREACHABLE" echo # Say what was actually exercised. Claiming commands work when the command test # was skipped is worse than not running it. if [ -n "${WITH_CAMERA:-}" ]; then echo " Camera path verified: '$WITH_CAMERA' reported a stream and the API served it." else echo " Camera path NOT verified: no printer on this LAN publishes an rtsp_url." fi if [ "$COMMAND_TESTED" = yes ]; then echo "PASS - telemetry and commands both verified against a real database and real printers." else echo "PARTIAL PASS - telemetry verified against a real database and real printers." echo " The command path was NOT exercised: no printer was idle enough to send to." echo " Rerun with PRINTER_ID= once one is free." fi