#!/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 5× 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 ] }