test(lifecycle): add UI surface coverage — HTTPS proxy + iframe URLs
Closes the coverage gap where existing bats suites would report green on a node whose dashboard tiles 502 because the proxy upstream is dead. First pass against .198 caught real prod issues immediately: /app/lnd/ → 502 (lnd container exited) /app/mempool/ → 502 (mempool container exited) /app/fedimint/ → 502 (fedimint container exited) while existing tests reported only "container is up: false" with no 404/502 distinction. * lib/ui-probes.bash — sourced helper. probe_https_200, probe_app_url (skip-if-container-down else assert-200), probe_dashboard_shell (asserts the Vue SPA HTML, not nginx default — catches the layout regression from feedback_release_tarball_layout.md), probe_dashboard_catalog (asserts /catalog.json non-empty). * bats/ui-coverage.bats — 9 @test cases covering the dashboard + bitcoin-ui :8334 + the seven HTTPS_PROXY_PATHS most users hit (lnd, electrumx, mempool, fedimint, btcpay, filebrowser). URL list mirrors HTTPS_PROXY_PATHS in neode-ui/src/views/appSession/appSessionConfig.ts. Divergence between the two is the exact bug class we're guarding against. Loops clean under run-20x.sh. Container-state oracle is via local podman inspect, so the suite must run on the archy host (same as companion-survives-archipelago-restart.bats). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
93
tests/lifecycle/bats/ui-coverage.bats
Normal file
93
tests/lifecycle/bats/ui-coverage.bats
Normal file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env bats
|
||||
# tests/lifecycle/bats/ui-coverage.bats
|
||||
#
|
||||
# UI surface tests — exercises the URLs a real user actually clicks
|
||||
# through, not just the JSON-RPC API. Fills the coverage gap where the
|
||||
# previous bats suites would report "container is up" while the iframe
|
||||
# behind /app/<id>/ was returning 502 because nginx had a stale upstream
|
||||
# or the proxy port was wrong.
|
||||
#
|
||||
# URL map sourced from neode-ui/src/views/appSession/appSessionConfig.ts
|
||||
# (the frontend's own resolveAppUrl). Tests here MUST stay in sync with
|
||||
# that file — divergence is the whole bug class we're guarding against.
|
||||
#
|
||||
# Each app probe is gated on its container being running:
|
||||
# - container down → skip (clean dependency report, no false-fail)
|
||||
# - container up → URL MUST return 200 with non-empty body
|
||||
#
|
||||
# Looped 20× via tests/lifecycle/run-20x.sh.
|
||||
|
||||
load '../lib/rpc.bash'
|
||||
load '../lib/ui-probes.bash'
|
||||
|
||||
setup_file() {
|
||||
: "${ARCHY_PASSWORD:?Set ARCHY_PASSWORD env var to the UI password}"
|
||||
export ARCHY_FORCE_LOGIN=1
|
||||
rpc_login
|
||||
unset ARCHY_FORCE_LOGIN
|
||||
HOST="${ARCHY_HOST:-127.0.0.1}"
|
||||
export HOST
|
||||
}
|
||||
|
||||
teardown_file() {
|
||||
rpc_logout_local
|
||||
}
|
||||
|
||||
# ────────────────────────────────────────────────────────────────────
|
||||
# Dashboard shell + catalog (always required)
|
||||
# ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@test "dashboard https://host/ returns the Vue SPA shell" {
|
||||
run probe_dashboard_shell
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "dashboard catalog endpoint responds with apps" {
|
||||
run probe_dashboard_catalog
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
# ────────────────────────────────────────────────────────────────────
|
||||
# Bitcoin UI — direct host port (8334), companion container
|
||||
# ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@test "bitcoin-ui is reachable on :8334 when archy-bitcoin-ui is running" {
|
||||
probe_app_url archy-bitcoin-ui "http://$HOST:8334/" "bitcoin-ui (direct port 8334)"
|
||||
}
|
||||
|
||||
# ────────────────────────────────────────────────────────────────────
|
||||
# HTTPS proxy paths — match HTTPS_PROXY_PATHS in appSessionConfig.ts
|
||||
# ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@test "lnd proxy https://host/app/lnd/ responds when lnd is running" {
|
||||
probe_app_url lnd "https://$HOST/app/lnd/" "lnd (proxy /app/lnd/)"
|
||||
}
|
||||
|
||||
@test "electrumx proxy https://host/app/electrumx/ responds when electrumx is running" {
|
||||
# electrumx companion (archy-electrs-ui) is what serves the iframe HTML;
|
||||
# the electrumx daemon is just the TCP backend.
|
||||
probe_app_url archy-electrs-ui "https://$HOST/app/electrumx/" "electrumx (proxy /app/electrumx/)"
|
||||
}
|
||||
|
||||
@test "mempool proxy https://host/app/mempool/ responds when mempool is running" {
|
||||
probe_app_url mempool "https://$HOST/app/mempool/" "mempool (proxy /app/mempool/)"
|
||||
}
|
||||
|
||||
@test "fedimint proxy https://host/app/fedimint/ responds when fedimint is running" {
|
||||
probe_app_url fedimint "https://$HOST/app/fedimint/" "fedimint (proxy /app/fedimint/)"
|
||||
}
|
||||
|
||||
@test "btcpay proxy https://host/app/btcpay/ responds when btcpay-server is running" {
|
||||
probe_app_url btcpay-server "https://$HOST/app/btcpay/" "btcpay (proxy /app/btcpay/)"
|
||||
}
|
||||
|
||||
@test "filebrowser proxy https://host/app/filebrowser/ responds when filebrowser is running" {
|
||||
probe_app_url filebrowser "https://$HOST/app/filebrowser/" "filebrowser (proxy /app/filebrowser/)"
|
||||
}
|
||||
|
||||
# ────────────────────────────────────────────────────────────────────
|
||||
# Companion-served URLs that aren't in HTTPS_PROXY_PATHS but show up
|
||||
# in the dashboard. archy-lnd-ui shares lnd's iframe path; archy-electrs-ui
|
||||
# shares electrumx's. The earlier test already covers those — leaving
|
||||
# this section for future companion-direct probes (none today).
|
||||
# ────────────────────────────────────────────────────────────────────
|
||||
108
tests/lifecycle/lib/ui-probes.bash
Normal file
108
tests/lifecycle/lib/ui-probes.bash
Normal file
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env bash
|
||||
# tests/lifecycle/lib/ui-probes.bash
|
||||
#
|
||||
# HTTPS proxy + iframe URL probes. Sourced from bats files. Pairs with
|
||||
# lib/rpc.bash but tests the URL surface a real user actually clicks
|
||||
# (dashboard, /app/<id>/ proxy paths, direct-port iframes), not just the
|
||||
# JSON-RPC API.
|
||||
#
|
||||
# Pattern: every probe is a skip-or-assert pair:
|
||||
# - if the container that backs the URL is not running → skip
|
||||
# (cleanly reports the dependency, doesn't false-fail)
|
||||
# - if it IS running → the URL MUST return 200
|
||||
# That catches the "container up but UI broken" failure mode that the
|
||||
# RPC-only tests miss (.198 today: archy-bitcoin-ui Up 12 minutes,
|
||||
# but is the iframe actually serving usable HTML? this layer answers).
|
||||
|
||||
# Curl options for a probe: short timeout, follow redirects, ignore self-
|
||||
# signed cert (the alpha fleet uses one), no proxy environment leak.
|
||||
PROBE_CURL_OPTS=(-skfL -m 8 --noproxy "*")
|
||||
|
||||
# ────────────────────────────────────────────────────────────────────
|
||||
# Container-state oracle
|
||||
# ────────────────────────────────────────────────────────────────────
|
||||
|
||||
# True iff `name` is currently in the running state per podman.
|
||||
probe_container_running() {
|
||||
local name="$1"
|
||||
[[ "$(podman inspect --format '{{.State.Running}}' "$name" 2>/dev/null)" == "true" ]]
|
||||
}
|
||||
|
||||
# ────────────────────────────────────────────────────────────────────
|
||||
# URL probes
|
||||
# ────────────────────────────────────────────────────────────────────
|
||||
|
||||
# Probe an HTTPS URL — assert 200 and non-empty body.
|
||||
# Usage: probe_https_200 URL "human description"
|
||||
probe_https_200() {
|
||||
local url="$1"
|
||||
local label="${2:-$url}"
|
||||
local body status
|
||||
body=$(curl "${PROBE_CURL_OPTS[@]}" -w '%{http_code}' "$url" 2>/dev/null) || {
|
||||
echo "probe_https_200: $label ($url) — curl failed (network/timeout)" >&2
|
||||
return 1
|
||||
}
|
||||
status="${body: -3}"
|
||||
body="${body:0:-3}"
|
||||
if [[ "$status" != "200" ]]; then
|
||||
echo "probe_https_200: $label ($url) returned $status (want 200)" >&2
|
||||
return 1
|
||||
fi
|
||||
if [[ -z "$body" ]]; then
|
||||
echo "probe_https_200: $label ($url) returned empty body" >&2
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Probe a URL backed by a container — skip if container is not running,
|
||||
# assert 200 if it is. This is the standard shape for app UI tests.
|
||||
# Usage: probe_app_url CONTAINER URL "human description"
|
||||
probe_app_url() {
|
||||
local container="$1"
|
||||
local url="$2"
|
||||
local label="${3:-$url}"
|
||||
if ! probe_container_running "$container"; then
|
||||
skip "$label: backing container '$container' is not running"
|
||||
fi
|
||||
run probe_https_200 "$url" "$label"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
# Probe the archipelago dashboard itself (the SPA shell at https://node/).
|
||||
# Asserts 200 and that the body looks like the Vue index, not an nginx
|
||||
# default page. Catches "frontend tarball was extracted with the wrong
|
||||
# layout" — see feedback_release_tarball_layout.md.
|
||||
probe_dashboard_shell() {
|
||||
local host="${ARCHY_HOST:-127.0.0.1}"
|
||||
local url="https://$host/"
|
||||
local body
|
||||
body=$(curl "${PROBE_CURL_OPTS[@]}" "$url" 2>/dev/null) || {
|
||||
echo "probe_dashboard_shell: $url — curl failed" >&2
|
||||
return 1
|
||||
}
|
||||
# Vue shell carries one of these markers: <div id="app">, the SPA bundle
|
||||
# tag, or the manifest link. Nginx default does not.
|
||||
if echo "$body" | grep -qE 'id="app"|<script.*\.js"|manifest\.webmanifest'; then
|
||||
return 0
|
||||
fi
|
||||
echo "probe_dashboard_shell: $url returned 200 but body doesn't look like the Vue shell" >&2
|
||||
echo "first 200 bytes: ${body:0:200}" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
# Probe the catalog endpoint that the dashboard uses to populate tiles.
|
||||
# Returns 0 if catalog is reachable AND has at least one entry.
|
||||
probe_dashboard_catalog() {
|
||||
local host="${ARCHY_HOST:-127.0.0.1}"
|
||||
local body
|
||||
body=$(curl "${PROBE_CURL_OPTS[@]}" "https://$host/catalog.json" 2>/dev/null) || {
|
||||
echo "probe_dashboard_catalog: /catalog.json fetch failed" >&2
|
||||
return 1
|
||||
}
|
||||
if ! echo "$body" | jq -e 'length > 0' >/dev/null 2>&1; then
|
||||
echo "probe_dashboard_catalog: /catalog.json is not a non-empty array/object" >&2
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
Reference in New Issue
Block a user