355 lines
12 KiB
Bash
355 lines
12 KiB
Bash
|
|
#!/usr/bin/env bash
|
||
|
|
# FINAL-201: Fresh Install End-to-End Test
|
||
|
|
# Run on a freshly installed Archipelago node to verify the complete user journey.
|
||
|
|
# Usage: scp this script to the node, then: bash test-fresh-install-e2e.sh <node-ip>
|
||
|
|
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
NODE="${1:-localhost}"
|
||
|
|
BASE="http://${NODE}"
|
||
|
|
PASS="${2:-password123}"
|
||
|
|
COOKIE_JAR="/tmp/e2e-cookies.txt"
|
||
|
|
PASS_COUNT=0
|
||
|
|
FAIL_COUNT=0
|
||
|
|
SKIP_COUNT=0
|
||
|
|
|
||
|
|
green() { printf "\033[32m✓ %s\033[0m\n" "$1"; }
|
||
|
|
red() { printf "\033[31m✗ %s\033[0m\n" "$1"; }
|
||
|
|
yellow(){ printf "\033[33m⊘ %s\033[0m\n" "$1"; }
|
||
|
|
header(){ printf "\n\033[1;36m━━━ %s ━━━\033[0m\n" "$1"; }
|
||
|
|
|
||
|
|
pass() { PASS_COUNT=$((PASS_COUNT + 1)); green "$1"; }
|
||
|
|
fail() { FAIL_COUNT=$((FAIL_COUNT + 1)); red "$1"; }
|
||
|
|
skip() { SKIP_COUNT=$((SKIP_COUNT + 1)); yellow "$1 (skipped)"; }
|
||
|
|
|
||
|
|
rpc() {
|
||
|
|
local method="$1"
|
||
|
|
local params="${2:-{}}"
|
||
|
|
curl -s -b "$COOKIE_JAR" -c "$COOKIE_JAR" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"$method\",\"params\":$params}" \
|
||
|
|
"${BASE}/rpc/" 2>/dev/null
|
||
|
|
}
|
||
|
|
|
||
|
|
# ─── Phase 1: Boot & Accessibility ───────────────────────────────
|
||
|
|
header "Phase 1: Boot & Accessibility"
|
||
|
|
|
||
|
|
if curl -s -o /dev/null -w "%{http_code}" "${BASE}/health" | grep -q "200"; then
|
||
|
|
pass "Backend health endpoint responds 200"
|
||
|
|
else
|
||
|
|
fail "Backend health endpoint not responding"
|
||
|
|
fi
|
||
|
|
|
||
|
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${BASE}/")
|
||
|
|
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "302" ]; then
|
||
|
|
pass "Web UI loads (HTTP $HTTP_CODE)"
|
||
|
|
else
|
||
|
|
fail "Web UI not loading (HTTP $HTTP_CODE)"
|
||
|
|
fi
|
||
|
|
|
||
|
|
if curl -s "${BASE}/" | grep -q "Archipelago"; then
|
||
|
|
pass "Web UI contains Archipelago branding"
|
||
|
|
else
|
||
|
|
fail "Web UI missing Archipelago branding"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ─── Phase 2: Onboarding ─────────────────────────────────────────
|
||
|
|
header "Phase 2: Onboarding & Authentication"
|
||
|
|
|
||
|
|
# Check if onboarding is needed or already done
|
||
|
|
LOGIN_RESP=$(curl -s -c "$COOKIE_JAR" -H "Content-Type: application/json" \
|
||
|
|
-d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"auth.login\",\"params\":{\"password\":\"$PASS\"}}" \
|
||
|
|
"${BASE}/rpc/" 2>/dev/null)
|
||
|
|
|
||
|
|
if echo "$LOGIN_RESP" | grep -q '"result"'; then
|
||
|
|
pass "Authentication successful"
|
||
|
|
else
|
||
|
|
fail "Authentication failed: $LOGIN_RESP"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Verify session works
|
||
|
|
SESSION_CHECK=$(rpc "system.info")
|
||
|
|
if echo "$SESSION_CHECK" | grep -q '"result"'; then
|
||
|
|
pass "Session is valid after login"
|
||
|
|
else
|
||
|
|
fail "Session invalid after login"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ─── Phase 3: Identity (DID) ─────────────────────────────────────
|
||
|
|
header "Phase 3: Identity System"
|
||
|
|
|
||
|
|
ID_LIST=$(rpc "identity.list")
|
||
|
|
if echo "$ID_LIST" | grep -q '"result"'; then
|
||
|
|
pass "identity.list RPC responds"
|
||
|
|
ID_COUNT=$(echo "$ID_LIST" | python3 -c "import sys,json; r=json.load(sys.stdin); print(len(r.get('result',{}).get('identities',[])))" 2>/dev/null || echo "0")
|
||
|
|
if [ "$ID_COUNT" -gt "0" ]; then
|
||
|
|
pass "At least one identity exists ($ID_COUNT found)"
|
||
|
|
else
|
||
|
|
# Create one
|
||
|
|
CREATE_ID=$(rpc "identity.create" '{"name":"Test Identity","purpose":"personal"}')
|
||
|
|
if echo "$CREATE_ID" | grep -q '"result"'; then
|
||
|
|
pass "Created test identity"
|
||
|
|
else
|
||
|
|
fail "Failed to create identity"
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
else
|
||
|
|
fail "identity.list RPC failed"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Test signing
|
||
|
|
SIGN_RESP=$(rpc "identity.sign" '{"message":"test message"}')
|
||
|
|
if echo "$SIGN_RESP" | grep -q '"result"'; then
|
||
|
|
pass "Identity signing works"
|
||
|
|
else
|
||
|
|
skip "Identity signing"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Test Nostr key
|
||
|
|
NOSTR_RESP=$(rpc "identity.create-nostr-key" '{}')
|
||
|
|
if echo "$NOSTR_RESP" | grep -q '"result"' || echo "$NOSTR_RESP" | grep -q "already"; then
|
||
|
|
pass "Nostr key generation works"
|
||
|
|
else
|
||
|
|
skip "Nostr key generation"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ─── Phase 4: App Installation ───────────────────────────────────
|
||
|
|
header "Phase 4: Core App Installation"
|
||
|
|
|
||
|
|
check_app_status() {
|
||
|
|
local app_id="$1"
|
||
|
|
local resp
|
||
|
|
resp=$(rpc "package.status" "{\"id\":\"$app_id\"}")
|
||
|
|
echo "$resp" | python3 -c "import sys,json; r=json.load(sys.stdin); print(r.get('result',{}).get('status','unknown'))" 2>/dev/null || echo "unknown"
|
||
|
|
}
|
||
|
|
|
||
|
|
install_app() {
|
||
|
|
local app_id="$1"
|
||
|
|
local timeout="${2:-120}"
|
||
|
|
local status
|
||
|
|
status=$(check_app_status "$app_id")
|
||
|
|
|
||
|
|
if [ "$status" = "running" ]; then
|
||
|
|
pass "$app_id already running"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
rpc "package.install" "{\"id\":\"$app_id\"}" > /dev/null 2>&1
|
||
|
|
|
||
|
|
local elapsed=0
|
||
|
|
while [ $elapsed -lt $timeout ]; do
|
||
|
|
sleep 5
|
||
|
|
elapsed=$((elapsed + 5))
|
||
|
|
status=$(check_app_status "$app_id")
|
||
|
|
if [ "$status" = "running" ]; then
|
||
|
|
pass "$app_id installed and running (${elapsed}s)"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
|
||
|
|
fail "$app_id failed to start within ${timeout}s (status: $status)"
|
||
|
|
return 1
|
||
|
|
}
|
||
|
|
|
||
|
|
# Install Bitcoin Knots (foundation)
|
||
|
|
install_app "bitcoin-knots" 180
|
||
|
|
|
||
|
|
# Install LND (requires Bitcoin)
|
||
|
|
install_app "lnd" 120
|
||
|
|
|
||
|
|
# Install Electrs (requires Bitcoin)
|
||
|
|
install_app "electrs" 120
|
||
|
|
|
||
|
|
# ─── Phase 5: Lightning Channels ─────────────────────────────────
|
||
|
|
header "Phase 5: Lightning (LND)"
|
||
|
|
|
||
|
|
LND_INFO=$(rpc "lnd.getinfo")
|
||
|
|
if echo "$LND_INFO" | grep -q '"result"'; then
|
||
|
|
pass "LND getinfo responds"
|
||
|
|
SYNCED=$(echo "$LND_INFO" | python3 -c "import sys,json; r=json.load(sys.stdin); print(r.get('result',{}).get('synced_to_chain',False))" 2>/dev/null)
|
||
|
|
if [ "$SYNCED" = "True" ]; then
|
||
|
|
pass "LND synced to chain"
|
||
|
|
else
|
||
|
|
skip "LND chain sync (may take time)"
|
||
|
|
fi
|
||
|
|
else
|
||
|
|
fail "LND getinfo failed"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Test wallet address generation
|
||
|
|
ADDR_RESP=$(rpc "lnd.newaddress")
|
||
|
|
if echo "$ADDR_RESP" | grep -q '"result"'; then
|
||
|
|
pass "LND new address generation works"
|
||
|
|
else
|
||
|
|
fail "LND new address generation failed"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Test invoice creation
|
||
|
|
INV_RESP=$(rpc "lnd.createinvoice" '{"value":1000,"memo":"E2E test invoice"}')
|
||
|
|
if echo "$INV_RESP" | grep -q '"result"'; then
|
||
|
|
pass "LND invoice creation works"
|
||
|
|
else
|
||
|
|
fail "LND invoice creation failed"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ─── Phase 6: Content Sharing ────────────────────────────────────
|
||
|
|
header "Phase 6: Content & Sharing"
|
||
|
|
|
||
|
|
CONTENT_LIST=$(rpc "content.list-mine")
|
||
|
|
if echo "$CONTENT_LIST" | grep -q '"result"'; then
|
||
|
|
pass "content.list-mine RPC responds"
|
||
|
|
else
|
||
|
|
skip "Content listing"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ─── Phase 7: Networking & Peers ─────────────────────────────────
|
||
|
|
header "Phase 7: Networking"
|
||
|
|
|
||
|
|
VIS_RESP=$(rpc "network.get-visibility")
|
||
|
|
if echo "$VIS_RESP" | grep -q '"result"'; then
|
||
|
|
pass "network.get-visibility RPC responds"
|
||
|
|
else
|
||
|
|
skip "Network visibility"
|
||
|
|
fi
|
||
|
|
|
||
|
|
DIAG_RESP=$(rpc "network.diagnostics")
|
||
|
|
if echo "$DIAG_RESP" | grep -q '"result"'; then
|
||
|
|
pass "network.diagnostics RPC responds"
|
||
|
|
else
|
||
|
|
skip "Network diagnostics"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ─── Phase 8: Tor Services ───────────────────────────────────────
|
||
|
|
header "Phase 8: Tor Services"
|
||
|
|
|
||
|
|
TOR_RESP=$(rpc "tor.list-services")
|
||
|
|
if echo "$TOR_RESP" | grep -q '"result"'; then
|
||
|
|
pass "tor.list-services RPC responds"
|
||
|
|
SVC_COUNT=$(echo "$TOR_RESP" | python3 -c "import sys,json; r=json.load(sys.stdin); print(len(r.get('result',{}).get('services',[])))" 2>/dev/null || echo "0")
|
||
|
|
if [ "$SVC_COUNT" -gt "0" ]; then
|
||
|
|
pass "Tor hidden services configured ($SVC_COUNT)"
|
||
|
|
else
|
||
|
|
skip "No Tor services configured yet"
|
||
|
|
fi
|
||
|
|
else
|
||
|
|
skip "Tor services"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ─── Phase 9: Easy Mode Goals ────────────────────────────────────
|
||
|
|
header "Phase 9: Easy Mode & Goals"
|
||
|
|
|
||
|
|
# Check goal pages load
|
||
|
|
for goal in open-a-shop accept-payments store-photos store-files run-lightning-node create-identity back-up-everything; do
|
||
|
|
GOAL_CODE=$(curl -s -o /dev/null -w "%{http_code}" -b "$COOKIE_JAR" "${BASE}/dashboard/goals/${goal}")
|
||
|
|
if [ "$GOAL_CODE" = "200" ]; then
|
||
|
|
pass "Goal page loads: $goal"
|
||
|
|
else
|
||
|
|
skip "Goal page: $goal (HTTP $GOAL_CODE)"
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
|
||
|
|
# ─── Phase 10: AIUI Chat ─────────────────────────────────────────
|
||
|
|
header "Phase 10: AIUI Chat"
|
||
|
|
|
||
|
|
AIUI_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${BASE}/aiui/")
|
||
|
|
if [ "$AIUI_CODE" = "200" ]; then
|
||
|
|
pass "AIUI loads"
|
||
|
|
else
|
||
|
|
skip "AIUI (HTTP $AIUI_CODE)"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ─── Phase 11: Multiple Identities ───────────────────────────────
|
||
|
|
header "Phase 11: Multi-Identity"
|
||
|
|
|
||
|
|
CREATE_BIZ=$(rpc "identity.create" '{"name":"Business","purpose":"business"}')
|
||
|
|
if echo "$CREATE_BIZ" | grep -q '"result"'; then
|
||
|
|
pass "Created business identity"
|
||
|
|
|
||
|
|
# Verify multiple identities exist
|
||
|
|
ID_LIST2=$(rpc "identity.list")
|
||
|
|
ID_COUNT2=$(echo "$ID_LIST2" | python3 -c "import sys,json; r=json.load(sys.stdin); print(len(r.get('result',{}).get('identities',[])))" 2>/dev/null || echo "0")
|
||
|
|
if [ "$ID_COUNT2" -ge "2" ]; then
|
||
|
|
pass "Multiple identities exist ($ID_COUNT2)"
|
||
|
|
else
|
||
|
|
fail "Expected 2+ identities, got $ID_COUNT2"
|
||
|
|
fi
|
||
|
|
else
|
||
|
|
skip "Business identity creation"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ─── Phase 12: Update System ─────────────────────────────────────
|
||
|
|
header "Phase 12: Update System"
|
||
|
|
|
||
|
|
UPDATE_STATUS=$(rpc "update.status")
|
||
|
|
if echo "$UPDATE_STATUS" | grep -q '"result"'; then
|
||
|
|
pass "update.status RPC responds"
|
||
|
|
else
|
||
|
|
skip "Update status"
|
||
|
|
fi
|
||
|
|
|
||
|
|
UPDATE_CHECK=$(rpc "update.check")
|
||
|
|
if echo "$UPDATE_CHECK" | grep -q '"result"' || echo "$UPDATE_CHECK" | grep -q "error"; then
|
||
|
|
pass "update.check RPC responds"
|
||
|
|
else
|
||
|
|
skip "Update check"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ─── Phase 13: WebSocket ─────────────────────────────────────────
|
||
|
|
header "Phase 13: WebSocket"
|
||
|
|
|
||
|
|
WS_CHECK=$(curl -s -o /dev/null -w "%{http_code}" -H "Upgrade: websocket" -H "Connection: Upgrade" "${BASE}/ws/")
|
||
|
|
if [ "$WS_CHECK" = "101" ] || [ "$WS_CHECK" = "400" ] || [ "$WS_CHECK" = "200" ]; then
|
||
|
|
pass "WebSocket endpoint responds (HTTP $WS_CHECK)"
|
||
|
|
else
|
||
|
|
skip "WebSocket (HTTP $WS_CHECK)"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ─── Phase 14: UI Asset Verification ─────────────────────────────
|
||
|
|
header "Phase 14: UI Assets"
|
||
|
|
|
||
|
|
# Check main app JS loads
|
||
|
|
ASSETS_CHECK=$(curl -s "${BASE}/" | grep -o 'src="[^"]*\.js"' | head -3)
|
||
|
|
if [ -n "$ASSETS_CHECK" ]; then
|
||
|
|
pass "JavaScript assets referenced in HTML"
|
||
|
|
else
|
||
|
|
# Vite uses different format
|
||
|
|
ASSETS_CHECK=$(curl -s "${BASE}/" | grep -o 'assets/[^"]*\.js' | head -3)
|
||
|
|
if [ -n "$ASSETS_CHECK" ]; then
|
||
|
|
pass "Vite assets referenced in HTML"
|
||
|
|
else
|
||
|
|
skip "Asset check"
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Check app icons exist
|
||
|
|
for icon in bitcoin-knots lnd electrs; do
|
||
|
|
ICON_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${BASE}/assets/img/app-icons/${icon}.png")
|
||
|
|
if [ "$ICON_CODE" = "200" ]; then
|
||
|
|
pass "App icon loads: $icon"
|
||
|
|
else
|
||
|
|
ICON_CODE2=$(curl -s -o /dev/null -w "%{http_code}" "${BASE}/assets/img/app-icons/${icon}.webp")
|
||
|
|
if [ "$ICON_CODE2" = "200" ]; then
|
||
|
|
pass "App icon loads: $icon (.webp)"
|
||
|
|
else
|
||
|
|
skip "App icon: $icon"
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
|
||
|
|
# ─── Summary ─────────────────────────────────────────────────────
|
||
|
|
header "RESULTS"
|
||
|
|
echo ""
|
||
|
|
printf "\033[32m Passed: %d\033[0m\n" "$PASS_COUNT"
|
||
|
|
printf "\033[31m Failed: %d\033[0m\n" "$FAIL_COUNT"
|
||
|
|
printf "\033[33m Skipped: %d\033[0m\n" "$SKIP_COUNT"
|
||
|
|
echo ""
|
||
|
|
|
||
|
|
TOTAL=$((PASS_COUNT + FAIL_COUNT + SKIP_COUNT))
|
||
|
|
if [ "$FAIL_COUNT" -eq 0 ]; then
|
||
|
|
printf "\033[1;32m🎉 ALL %d TESTS PASSED (%d skipped)\033[0m\n" "$PASS_COUNT" "$SKIP_COUNT"
|
||
|
|
exit 0
|
||
|
|
else
|
||
|
|
printf "\033[1;31m⚠ %d/%d TESTS FAILED\033[0m\n" "$FAIL_COUNT" "$TOTAL"
|
||
|
|
exit 1
|
||
|
|
fi
|