Files
continuum-backend/scripts/e2e-edge.sh
T
octoturge dddfff6800 Say what the e2e's WebRTC assertion actually proves
go2rtc is not running in this test, so the gateway cannot reach it and
the message "its go2rtc answered (as a refusal)" was wrong. What the
assertion really establishes is that the offer travelled
cloud -> uplink -> gateway at all, which a timeout would not.

Records that the happy path was verified by hand - the real go2rtc and a
valid offer return 200 with an answer SDP carrying go2rtc's own host and
server-reflexive candidates - and why that is not automated here: it
needs a live camera and go2rtc installed, and this script should not
depend on either.
2026-10-19 00:37:27 +00:00

454 lines
23 KiB
Bash
Executable File

#!/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
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
}
psql_q() { PGPASSWORD=continuum psql -h localhost -U continuum -d continuum -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="$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:"
PGPASSWORD=continuum psql -h localhost -U continuum -d continuum -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=<an idle printer> once one is free."
fi