From f80daff8ba87c229aa53bc0fcfdf84a7a16f8ae4 Mon Sep 17 00:00:00 2001 From: archipelago Date: Fri, 1 May 2026 16:11:02 -0400 Subject: [PATCH] test(lifecycle): add dedicated electrumx.bats suite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same shape as bitcoin-knots.bats and lnd.bats so the 20× release-gate exercises electrumx through the same state matrix it uses for the other two core apps. electrumx previously had a single TCP-port check inside required-stack.bats; this adds destructive + cascade-destructive tiers. 10 @test cases: * read-only: presence, valid state, TCP port (50001) reachable, no orphan containers beyond {electrumx, archy-electrs-ui} * destructive: stop, start, restart, TCP port recovers within 120s of cold restart (longer than bitcoind because electrumx replays its index against bitcoind on start) * cascade: uninstall, reinstall (240s timeout for index rebuild) With this suite, the three single-container core apps (bitcoin-knots, lnd, electrumx) now have parity coverage. Multi-container stacks (btcpay, mempool, fedimint) come next. Co-Authored-By: Claude Opus 4.7 (1M context) --- tests/lifecycle/bats/electrumx.bats | 146 ++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 tests/lifecycle/bats/electrumx.bats diff --git a/tests/lifecycle/bats/electrumx.bats b/tests/lifecycle/bats/electrumx.bats new file mode 100644 index 00000000..63f30390 --- /dev/null +++ b/tests/lifecycle/bats/electrumx.bats @@ -0,0 +1,146 @@ +#!/usr/bin/env bats +# tests/lifecycle/bats/electrumx.bats +# +# Lifecycle tests for the electrumx package (containers are named +# `electrumx` + `archy-electrs-ui`). Mirrors bitcoin-knots.bats / +# lnd.bats so the 20× release-gate run exercises electrumx through +# the same state matrix. +# +# Tiers: +# - Read-only (always runs): presence, valid state, TCP reachable +# - Destructive (ARCHY_ALLOW_DESTRUCTIVE=1): stop → start → restart +# - Cascade-destructive (ARCHY_ALLOW_CASCADE_DESTRUCTIVE=1): uninstall → reinstall +# +# Pre-req: electrumx is installed and bitcoin-knots is running (electrumx +# depends on bitcoind RPC for headers). + +load '../lib/rpc.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 +} + +teardown_file() { + rpc_logout_local +} + +# ──────────────────────────────────────────────────────────────────── +# Read-only tier +# ──────────────────────────────────────────────────────────────────── + +@test "container-list includes electrumx" { + run rpc_result container-list + [ "$status" -eq 0 ] + echo "$output" | jq -e '.[] | select(.name == "electrumx")' >/dev/null +} + +@test "container-list reports a valid state for electrumx" { + run rpc_result container-list + [ "$status" -eq 0 ] + local state + state=$(echo "$output" | jq -r '.[] | select(.name == "electrumx") | .state') + [[ "$state" =~ ^(running|stopped|exited|created|paused)$ ]] +} + +@test "electrumx TCP port accepts connections when running" { + local state + state=$(rpc_result container-list | jq -r '.[] | select(.name == "electrumx") | .state') + if [[ "$state" != "running" ]]; then + skip "electrumx not running (state=$state)" + fi + + # Same probe required-stack.bats uses — divergence flags a real regression. + run python3 - <<'PY' +import socket +s = socket.create_connection(("127.0.0.1", 50001), 3) +s.close() +print("ok") +PY + [ "$status" -eq 0 ] +} + +@test "no orphan electrumx-related containers beyond the known set" { + # FM4 guard: known-good electrumx-package set is {electrumx, archy-electrs-ui}. + local total known + total=$(podman ps -a --format '{{.Names}}' \ + | grep -Ec '^(electrumx|electrs|archy-electrs(-[a-z]+)?)$' || true) + known=$(podman ps -a --format '{{.Names}}' \ + | grep -Ec '^(electrumx|archy-electrs-ui)$' || true) + [ "$total" -eq "$known" ] +} + +# ──────────────────────────────────────────────────────────────────── +# Destructive tier (stop → start → restart on the same container) +# ──────────────────────────────────────────────────────────────────── + +@test "package.stop transitions electrumx to stopped" { + [[ "${ARCHY_ALLOW_DESTRUCTIVE:-0}" == "1" ]] || skip "ARCHY_ALLOW_DESTRUCTIVE not set" + + run rpc_result package.stop '{"id":"electrumx"}' + [ "$status" -eq 0 ] + + run wait_for_container_status electrumx stopped 60 + [ "$status" -eq 0 ] +} + +@test "package.start brings electrumx back to running" { + [[ "${ARCHY_ALLOW_DESTRUCTIVE:-0}" == "1" ]] || skip "ARCHY_ALLOW_DESTRUCTIVE not set" + + run rpc_result package.start '{"id":"electrumx"}' + [ "$status" -eq 0 ] + + run wait_for_container_status electrumx running 120 + [ "$status" -eq 0 ] +} + +@test "package.restart leaves electrumx in running state" { + [[ "${ARCHY_ALLOW_DESTRUCTIVE:-0}" == "1" ]] || skip "ARCHY_ALLOW_DESTRUCTIVE not set" + + run rpc_result package.restart '{"id":"electrumx"}' + [ "$status" -eq 0 ] + + run wait_for_container_status electrumx running 120 + [ "$status" -eq 0 ] +} + +@test "electrumx TCP port recovers after restart" { + [[ "${ARCHY_ALLOW_DESTRUCTIVE:-0}" == "1" ]] || skip "ARCHY_ALLOW_DESTRUCTIVE not set" + + # electrumx replays its index against bitcoind on cold start; allow 120s. + local deadline=$(( $(date +%s) + 120 )) + while (( $(date +%s) < deadline )); do + if python3 -c 'import socket; socket.create_connection(("127.0.0.1", 50001), 3).close()' \ + >/dev/null 2>&1; then + return 0 + fi + sleep 3 + done + fail "electrumx TCP port never reopened after restart" +} + +# ──────────────────────────────────────────────────────────────────── +# Cascade-destructive tier (uninstall + reinstall) +# ──────────────────────────────────────────────────────────────────── + +@test "package.uninstall removes electrumx" { + [[ "${ARCHY_ALLOW_CASCADE_DESTRUCTIVE:-0}" == "1" ]] || skip "ARCHY_ALLOW_CASCADE_DESTRUCTIVE not set" + + run rpc_result package.uninstall '{"id":"electrumx","preserve_data":true}' + [ "$status" -eq 0 ] + + run wait_for_container_status electrumx absent 120 + [ "$status" -eq 0 ] +} + +@test "package.install electrumx returns to running" { + [[ "${ARCHY_ALLOW_CASCADE_DESTRUCTIVE:-0}" == "1" ]] || skip "ARCHY_ALLOW_CASCADE_DESTRUCTIVE not set" + + run rpc_result package.install '{"manifest_path":"electrumx/manifest.yaml"}' + [ "$status" -eq 0 ] + + run wait_for_container_status electrumx running 240 + [ "$status" -eq 0 ] +}