Covers federation, content sharing, DWN messages + sync, health monitor auto-restart, Tor rotation endpoints, and NIP-07 signing. Fixed content.list → content.list-mine, system.stats field name. (INSTALL-04) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
241 lines
9.8 KiB
Bash
Executable File
241 lines
9.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# test-integration-full.sh — Full federation + sharing + DWN integration test
|
|
#
|
|
# Tests the complete feature set on the primary server:
|
|
# 1. Federation peer connectivity
|
|
# 2. Content sharing (add, catalog, access control)
|
|
# 3. DWN message write + query
|
|
# 4. DWN sync trigger
|
|
# 5. Health monitor (container crash + restart detection)
|
|
# 6. Tor rotation (already tested separately, just verify endpoint)
|
|
# 7. NIP-07 signing (server-side)
|
|
#
|
|
# Usage: ./scripts/test-integration-full.sh [target-ip]
|
|
|
|
set -uo pipefail
|
|
|
|
TARGET="${1:-192.168.1.228}"
|
|
SSH_KEY="${ARCHIPELAGO_SSH_KEY:-$HOME/.ssh/archipelago-deploy}"
|
|
SSH="ssh -i $SSH_KEY -o StrictHostKeyChecking=no -o ConnectTimeout=10 archipelago@$TARGET"
|
|
PASS=0
|
|
FAIL=0
|
|
WARN=0
|
|
|
|
check() {
|
|
local name="$1"
|
|
local ok="$2"
|
|
if [ "$ok" = "true" ]; then
|
|
echo " ✅ $name"
|
|
((PASS++))
|
|
else
|
|
echo " ❌ $name"
|
|
((FAIL++))
|
|
fi
|
|
}
|
|
|
|
warn() {
|
|
echo " ⚠️ $1"
|
|
((WARN++))
|
|
}
|
|
|
|
json_get() {
|
|
python3 -c "import sys,json; d=json.load(sys.stdin); r=d.get('result',{}); print(r.get('$1','') if isinstance(r,dict) else '')" 2>/dev/null
|
|
}
|
|
|
|
json_err() {
|
|
python3 -c "import sys,json; d=json.load(sys.stdin); e=d.get('error'); print(e.get('message','') if e else '')" 2>/dev/null
|
|
}
|
|
|
|
echo "🔗 Full Integration Test — $TARGET"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo ""
|
|
|
|
# Login
|
|
echo "Authenticating..."
|
|
$SSH "curl -s -c /tmp/cookiejar http://localhost:5678/rpc/v1 -H 'Content-Type: application/json' -d '{\"method\":\"auth.login\",\"params\":{\"password\":\"password123\"}}'" >/dev/null 2>&1
|
|
CSRF=$($SSH "grep csrf_token /tmp/cookiejar 2>/dev/null | awk '{print \$NF}'" 2>/dev/null)
|
|
|
|
rpc() {
|
|
local method="$1"
|
|
local params="${2:-}"
|
|
local body
|
|
if [ -n "$params" ]; then
|
|
body="{\"method\":\"$method\",\"params\":$params}"
|
|
else
|
|
body="{\"method\":\"$method\"}"
|
|
fi
|
|
$SSH "curl -s -b /tmp/cookiejar -H 'Content-Type: application/json' -H 'X-CSRF-Token: $CSRF' http://localhost:5678/rpc/v1 -d '$body'" 2>/dev/null
|
|
}
|
|
|
|
# ━━━━━━━━━━ 1. FEDERATION ━━━━━━━━━━
|
|
echo ""
|
|
echo "1. Federation Peers"
|
|
FED_RESP=$(rpc "federation.list-nodes")
|
|
PEER_COUNT=$(echo "$FED_RESP" | python3 -c "import sys,json; print(len(json.load(sys.stdin).get('result',{}).get('nodes',[])))" 2>/dev/null)
|
|
check "Federation peers exist ($PEER_COUNT peers)" "$([ "$PEER_COUNT" -ge 1 ] && echo true || echo false)"
|
|
|
|
# Node DID
|
|
DID_RESP=$(rpc "node.did")
|
|
DID=$(echo "$DID_RESP" | json_get "did")
|
|
check "Node DID valid" "$(echo "$DID" | grep -q '^did:key:z' && echo true || echo false)"
|
|
|
|
# Nostr pubkey
|
|
PK_RESP=$(rpc "node.nostr-pubkey")
|
|
NPUB=$(echo "$PK_RESP" | json_get "nostr_npub")
|
|
check "Nostr npub valid" "$(echo "$NPUB" | grep -q '^npub1' && echo true || echo false)"
|
|
|
|
# Tor address
|
|
TOR_RESP=$(rpc "node.tor-address")
|
|
TOR_ADDR=$(echo "$TOR_RESP" | json_get "tor_address")
|
|
check "Tor address valid" "$(echo "$TOR_ADDR" | grep -q '.onion$' && echo true || echo false)"
|
|
|
|
# ━━━━━━━━━━ 2. CONTENT SHARING ━━━━━━━━━━
|
|
echo ""
|
|
echo "2. Content Sharing"
|
|
|
|
# Create a test file
|
|
$SSH "echo 'Integration test content $(date)' | sudo tee /var/lib/archipelago/filebrowser/integration-test.txt > /dev/null" 2>/dev/null
|
|
|
|
# Add to content catalog
|
|
ADD_RESP=$(rpc "content.add" "{\"filename\":\"integration-test.txt\",\"title\":\"Integration Test\",\"description\":\"Automated test file\"}")
|
|
ADD_ERR=$(echo "$ADD_RESP" | json_err)
|
|
if [ -n "$ADD_ERR" ] && echo "$ADD_ERR" | grep -q "already exists"; then
|
|
check "Content add (already exists, OK)" "true"
|
|
else
|
|
ADD_ID=$(echo "$ADD_RESP" | python3 -c "import sys,json; r=json.load(sys.stdin).get('result',{}); i=r.get('item',r); print(i.get('id',''))" 2>/dev/null)
|
|
check "Content added to catalog" "$([ -n "$ADD_ID" ] && echo true || echo false)"
|
|
fi
|
|
|
|
# List catalog
|
|
LIST_RESP=$(rpc "content.list-mine")
|
|
ITEM_COUNT=$(echo "$LIST_RESP" | python3 -c "import sys,json; print(len(json.load(sys.stdin).get('result',{}).get('items',[])))" 2>/dev/null)
|
|
check "Content catalog has items ($ITEM_COUNT)" "$([ "$ITEM_COUNT" -ge 1 ] && echo true || echo false)"
|
|
|
|
# Set access to free (uses content ID)
|
|
FIRST_ID=$(echo "$LIST_RESP" | python3 -c "import sys,json; items=json.load(sys.stdin).get('result',{}).get('items',[]); print(items[0].get('id','') if items else '')" 2>/dev/null)
|
|
PRICE_RESP=$(rpc "content.set-pricing" "{\"id\":\"$FIRST_ID\",\"access\":\"free\"}")
|
|
PRICE_ERR=$(echo "$PRICE_RESP" | json_err)
|
|
check "Set access mode" "$([ -z "$PRICE_ERR" ] && echo true || echo false)"
|
|
|
|
# Verify content is accessible via HTTP
|
|
CONTENT_STATUS=$($SSH "curl -s -o /dev/null -w '%{http_code}' http://localhost/content" 2>/dev/null)
|
|
check "Content catalog HTTP endpoint" "$([ "$CONTENT_STATUS" = "200" ] && echo true || echo false)"
|
|
|
|
# ━━━━━━━━━━ 3. DWN MESSAGES ━━━━━━━━━━
|
|
echo ""
|
|
echo "3. DWN Protocol & Messages"
|
|
|
|
# DWN status
|
|
DWN_RESP=$(rpc "dwn.status")
|
|
DWN_ERR=$(echo "$DWN_RESP" | json_err)
|
|
check "DWN status endpoint" "$([ -z "$DWN_ERR" ] && echo true || echo false)"
|
|
|
|
# Register a test protocol
|
|
PROTO_RESP=$(rpc "dwn.register-protocol" "{\"protocol\":\"https://archipelago.dev/protocols/integration-test\",\"published\":true}")
|
|
PROTO_ERR=$(echo "$PROTO_RESP" | json_err)
|
|
check "Register DWN protocol" "$([ -z "$PROTO_ERR" ] && echo true || echo false)"
|
|
|
|
# Write a test message
|
|
WRITE_RESP=$(rpc "dwn.write-message" "{\"author\":\"$DID\",\"protocol\":\"https://archipelago.dev/protocols/integration-test\",\"data\":{\"test\":true,\"timestamp\":$(date +%s)}}")
|
|
RECORD_ID=$(echo "$WRITE_RESP" | json_get "record_id")
|
|
check "Write DWN message" "$([ -n "$RECORD_ID" ] && echo true || echo false)"
|
|
|
|
# Query messages
|
|
QUERY_RESP=$(rpc "dwn.query-messages" "{\"protocol\":\"https://archipelago.dev/protocols/integration-test\"}")
|
|
MSG_COUNT=$(echo "$QUERY_RESP" | json_get "count")
|
|
check "Query DWN messages (count: $MSG_COUNT)" "$([ "$MSG_COUNT" -ge 1 ] && echo true || echo false)"
|
|
|
|
# List protocols
|
|
PROTOS_RESP=$(rpc "dwn.list-protocols")
|
|
PROTO_COUNT=$(echo "$PROTOS_RESP" | python3 -c "import sys,json; print(len(json.load(sys.stdin).get('result',{}).get('protocols',[])))" 2>/dev/null)
|
|
check "List DWN protocols ($PROTO_COUNT)" "$([ "$PROTO_COUNT" -ge 1 ] && echo true || echo false)"
|
|
|
|
# ━━━━━━━━━━ 4. DWN SYNC ━━━━━━━━━━
|
|
echo ""
|
|
echo "4. DWN Sync"
|
|
SYNC_RESP=$(rpc "dwn.sync")
|
|
SYNC_ERR=$(echo "$SYNC_RESP" | json_err)
|
|
SYNC_STATUS=$(echo "$SYNC_RESP" | json_get "sync_status")
|
|
# Sync may fail if peers are unreachable over Tor, but endpoint should work
|
|
if [ -z "$SYNC_ERR" ]; then
|
|
check "DWN sync executed ($SYNC_STATUS)" "true"
|
|
else
|
|
warn "DWN sync returned error: $SYNC_ERR (peers may be unreachable)"
|
|
check "DWN sync endpoint exists" "true"
|
|
fi
|
|
|
|
# ━━━━━━━━━━ 5. HEALTH MONITOR ━━━━━━━━━━
|
|
echo ""
|
|
echo "5. Health Monitor"
|
|
|
|
# System stats
|
|
STATS_RESP=$(rpc "system.stats")
|
|
CPU=$(echo "$STATS_RESP" | json_get "cpu_usage_percent")
|
|
check "System stats (CPU: ${CPU:-?}%)" "$([ -n "$CPU" ] && echo true || echo false)"
|
|
|
|
# Container list
|
|
CONTAINERS=$($SSH "sudo podman ps --format '{{.Names}}' 2>/dev/null | wc -l" 2>/dev/null | tr -d '[:space:]')
|
|
check "Containers running ($CONTAINERS)" "$([ "$CONTAINERS" -ge 5 ] && echo true || echo false)"
|
|
|
|
# Health endpoint
|
|
HEALTH_STATUS=$($SSH "curl -s -o /dev/null -w '%{http_code}' http://localhost/health" 2>/dev/null)
|
|
check "Health endpoint OK" "$([ "$HEALTH_STATUS" = "200" ] && echo true || echo false)"
|
|
|
|
# Container crash + auto-restart test
|
|
echo " Stopping filebrowser to test auto-restart..."
|
|
$SSH "sudo podman stop filebrowser 2>/dev/null" >/dev/null 2>&1
|
|
sleep 5
|
|
|
|
# Check if health monitor detected + restarted (poll for up to 90s)
|
|
RESTARTED="false"
|
|
for i in $(seq 1 18); do
|
|
FB_STATUS=$($SSH "sudo podman inspect filebrowser --format '{{.State.Status}}' 2>/dev/null" 2>/dev/null | tr -d '[:space:]')
|
|
if [ "$FB_STATUS" = "running" ]; then
|
|
RESTARTED="true"
|
|
echo " Restarted after ~$((i * 5))s"
|
|
break
|
|
fi
|
|
sleep 5
|
|
done
|
|
check "Health monitor auto-restarted filebrowser" "$RESTARTED"
|
|
|
|
# ━━━━━━━━━━ 6. TOR ROTATION ━━━━━━━━━━
|
|
echo ""
|
|
echo "6. Tor Rotation (endpoint check only)"
|
|
# Don't actually rotate again — just verify endpoint responds
|
|
TOR_LIST_RESP=$(rpc "tor.list-services")
|
|
TOR_LIST_ERR=$(echo "$TOR_LIST_RESP" | json_err)
|
|
check "tor.list-services endpoint" "$([ -z "$TOR_LIST_ERR" ] && echo true || echo false)"
|
|
|
|
CLEANUP_RESP=$(rpc "tor.cleanup-rotated")
|
|
CLEANUP_ERR=$(echo "$CLEANUP_RESP" | json_err)
|
|
check "tor.cleanup-rotated endpoint" "$([ -z "$CLEANUP_ERR" ] && echo true || echo false)"
|
|
|
|
# ━━━━━━━━━━ 7. NIP-07 SIGNING ━━━━━━━━━━
|
|
echo ""
|
|
echo "7. NIP-07 Signing"
|
|
NODE_PK=$(echo "$PK_RESP" | json_get "nostr_pubkey")
|
|
SIGN_RESP=$(rpc "node.nostr-sign" "{\"event\":{\"kind\":1,\"content\":\"integration test\",\"created_at\":$(date +%s),\"tags\":[]}}")
|
|
SIGN_PK=$(echo "$SIGN_RESP" | json_get "pubkey")
|
|
SIGN_SIG=$(echo "$SIGN_RESP" | json_get "sig")
|
|
check "Event signed" "$([ ${#SIGN_SIG} -gt 60 ] && echo true || echo false)"
|
|
check "Signing pubkey matches node key" "$([ "$SIGN_PK" = "$NODE_PK" ] && echo true || echo false)"
|
|
|
|
# nostr-provider.js injection
|
|
JS_OK=$($SSH "curl -s -o /dev/null -w '%{http_code}' http://localhost/nostr-provider.js" 2>/dev/null)
|
|
check "nostr-provider.js served" "$([ "$JS_OK" = "200" ] && echo true || echo false)"
|
|
|
|
# ━━━━━━━━━━ SUMMARY ━━━━━━━━━━
|
|
echo ""
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo "Results: $PASS passed, $FAIL failed, $WARN warnings"
|
|
echo ""
|
|
|
|
if [ $FAIL -eq 0 ]; then
|
|
echo "✅ All integration tests passed!"
|
|
else
|
|
echo "❌ $FAIL tests failed — review output above"
|
|
fi
|
|
|
|
[ $FAIL -eq 0 ] && exit 0 || exit 1
|