The camera work had unit tests on both ends and nothing exercising it
against a real printer. This adds that, and it immediately earned its
keep: it is the only test that covers `stream_base_url` surviving the
hello, living on an in-memory connection, and coming back out of
`GET /printers/:id/stream` - a path with no unit test behind it, because
the connection map is not reachable without a socket.
Verified against a live H2C: it reports a camera stream named after its
own gateway id, and the endpoint answers
`{"streamUrl":"http://e2e-gateway.invalid:1984","cameraId":"h2c"}`.
Both halves are asserted, because "some printers have a stream and most
do not" is the expected state rather than a defect. Only the X1/H2 class
publishes an `rtsp_url` - confirmed at the network layer, where the H2C
accepts TCP on 322 and a P1P refuses it and listens on 6000 - so the
test also asserts that a printer without one is refused with a 404 that
blames the printer's camera and not the gateway. A run where no printer
has a camera still passes, but says so rather than claiming the path
works.
`CONTINUUM_STREAM_BASE_URL` is set to an unreachable name on purpose:
nothing here plays video, and it only has to prove the value made the
round trip. The gateway's go2rtc config is also pointed into the
scratch directory now, so a run no longer writes `go2rtc.yaml` into the
proxy checkout.
365 lines
17 KiB
Bash
Executable File
365 lines
17 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}"
|
|
export DATABASE_URL VALKEY_URL EDGE_JWT_SECRET PORT
|
|
|
|
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" \
|
|
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"
|
|
# The farm's stream base URL has to survive the hello and come back out of
|
|
# an in-memory connection, which is the part with no unit test behind it.
|
|
echo "$STREAM" | grep -q "\"streamUrl\":\"$STREAM_BASE_URL\"" \
|
|
|| fail "streamUrl did not come back from the gateway's hello: $STREAM"
|
|
ok "GET .../stream: $STREAM"
|
|
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
|
|
|
|
# --- 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
|