|
| 1 | +#!/usr/bin/env bash |
| 2 | +# End-to-end smoke test for the headless server. |
| 3 | +# |
| 4 | +# Launches dist-headless/main/index.js on an ephemeral port + localhost, |
| 5 | +# parses the [web-client] URL out of stdout, then delegates to |
| 6 | +# scripts/web-smoke.mjs and scripts/ws-smoke.mjs for HTTP + WS |
| 7 | +# validation. Finally SIGTERMs the server and confirms it exits within |
| 8 | +# 5s (no zombies). |
| 9 | +# |
| 10 | +# Run locally: npm run build:headless && bash scripts/smoke-headless.sh |
| 11 | +# Run in CI: same — invoked from .github/workflows/ci.yml. |
| 12 | +# |
| 13 | +# Exit codes: 0 = all checks passed; non-zero = a check failed (the |
| 14 | +# server log is dumped to stderr on URL-parse failure). |
| 15 | + |
| 16 | +set -euo pipefail |
| 17 | + |
| 18 | +# Isolated data dir so we don't touch ~/.harness on a dev box or the |
| 19 | +# runner's $HOME in CI. |
| 20 | +LOG="${HARNESS_SMOKE_LOG:-/tmp/harness-server.log}" |
| 21 | +HARNESS_DATA_DIR="$(mktemp -d)" |
| 22 | +export HARNESS_DATA_DIR |
| 23 | + |
| 24 | +node dist-headless/main/index.js --port 0 --host 127.0.0.1 > "$LOG" 2>&1 & |
| 25 | +SERVER_PID=$! |
| 26 | +trap 'kill "$SERVER_PID" 2>/dev/null || true; wait "$SERVER_PID" 2>/dev/null || true' EXIT |
| 27 | + |
| 28 | +# Wait up to 15s for the URL line. Format is |
| 29 | +# "[web-client] open http://127.0.0.1:<port>/?token=<token>" |
| 30 | +# emitted from src/main/index.ts in the webHttpServer.listen callback. |
| 31 | +# If the log shape changes, this fails loudly (which is what we want, |
| 32 | +# since the Settings UI and other tooling read the same line). |
| 33 | +URL="" |
| 34 | +for _ in $(seq 1 75); do |
| 35 | + URL="$(grep -oE 'http://127\.0\.0\.1:[0-9]+/\?token=[a-f0-9]+' "$LOG" | head -1 || true)" |
| 36 | + if [ -n "$URL" ]; then break; fi |
| 37 | + sleep 0.2 |
| 38 | +done |
| 39 | +if [ -z "$URL" ]; then |
| 40 | + echo "::error::headless server did not advertise a URL within 15s" >&2 |
| 41 | + echo "--- server log ---" >&2 |
| 42 | + cat "$LOG" >&2 |
| 43 | + exit 1 |
| 44 | +fi |
| 45 | +echo "server up at $URL" |
| 46 | + |
| 47 | +# Split URL into host:port + token for the existing smoke scripts. |
| 48 | +# URL shape is fixed (http://127.0.0.1:<port>/?token=<hex>), no need |
| 49 | +# for a real URL parser. |
| 50 | +HOST_PORT="${URL#http://}" |
| 51 | +HOST_PORT="${HOST_PORT%%/*}" |
| 52 | +TOKEN="${URL##*token=}" |
| 53 | +PORT="${HOST_PORT##*:}" |
| 54 | + |
| 55 | +# 1+2. web-client HTTP: auth gate + HTML + asset reachability. |
| 56 | +node scripts/web-smoke.mjs "$HOST_PORT" "$TOKEN" |
| 57 | + |
| 58 | +# 3. WS upgrade + snapshot round-trip. |
| 59 | +node scripts/ws-smoke.mjs "$TOKEN" "$PORT" |
| 60 | + |
| 61 | +# 4. Clean shutdown — SIGTERM should exit within 5s. Catches "server |
| 62 | +# hangs on SIGTERM" bugs that would leave zombies in CI. |
| 63 | +kill -TERM "$SERVER_PID" |
| 64 | +for _ in $(seq 1 25); do |
| 65 | + if ! kill -0 "$SERVER_PID" 2>/dev/null; then break; fi |
| 66 | + sleep 0.2 |
| 67 | +done |
| 68 | +if kill -0 "$SERVER_PID" 2>/dev/null; then |
| 69 | + echo "::error::server did not exit on SIGTERM within 5s" >&2 |
| 70 | + kill -9 "$SERVER_PID" || true |
| 71 | + exit 1 |
| 72 | +fi |
| 73 | +echo "clean shutdown OK" |
| 74 | +trap - EXIT |
0 commit comments