#!/usr/bin/env bash # ============================================================================= # e2e-test.sh — Full end-to-end integration test for Timmy Tower World. # # Tests the complete stack: # 1. API health check # 2. Tower (Matrix) is served at /tower # 3. WebSocket /api/ws handshake and event protocol # 4. Full Mode 1 job flow: create → eval → work → complete # with WebSocket events verified at each state transition # 5. Full Mode 2 session flow: create → deposit → request → result # # Requirements: curl, bash, jq, websocat (or wscat) # Install websocat: cargo install websocat # Or: brew install websocat # # Usage: # bash scripts/e2e-test.sh [BASE_URL] # BASE_URL defaults to http://localhost:8080 # ============================================================================= set -euo pipefail BASE="${1:-http://localhost:8080}" WS_BASE="${BASE/http:/ws:}" WS_BASE="${WS_BASE/https:/wss:}" PASS=0; FAIL=0 note() { echo " [$1] $2"; } sep() { echo; echo "=== $* ==="; } body_of() { echo "$1" | sed '$d'; } code_of() { echo "$1" | tail -n1; } echo "Timmy Tower World — E2E Integration Test" echo "Target: $BASE" echo "$(date)" echo # --------------------------------------------------------------------------- # 1. API health # --------------------------------------------------------------------------- sep "1. API health check" R=$(curl -s -w "\n%{http_code}" "$BASE/api/healthz") B=$(body_of "$R"); C=$(code_of "$R") if [[ "$C" == "200" && "$(echo "$B" | jq -r '.status')" == "ok" ]]; then note PASS "GET /api/healthz → 200, status=ok" PASS=$((PASS+1)) else note FAIL "code=$C body=$B" FAIL=$((FAIL+1)) fi # --------------------------------------------------------------------------- # 2. Tower (Matrix frontend) served at /tower # --------------------------------------------------------------------------- sep "2. Tower frontend served" TOWER_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/tower/") if [[ "$TOWER_CODE" == "200" ]]; then note PASS "GET /tower/ → 200" PASS=$((PASS+1)) else note FAIL "GET /tower/ → $TOWER_CODE (Matrix may not be built or path wrong)" FAIL=$((FAIL+1)) fi # Check it looks like the Matrix HTML TOWER_TITLE=$(curl -s "$BASE/tower/" | grep -o '[^<]*' | head -1 || echo "") if [[ "$TOWER_TITLE" == *"Timmy Tower"* || "$TOWER_TITLE" == *"Matrix"* ]]; then note PASS "Tower HTML contains expected title" PASS=$((PASS+1)) else note FAIL "Tower title unexpected: '$TOWER_TITLE'" FAIL=$((FAIL+1)) fi # --------------------------------------------------------------------------- # 3. WebSocket /api/ws handshake # --------------------------------------------------------------------------- sep "3. WebSocket /api/ws handshake" WS_URL="${WS_BASE}/api/ws" if ! command -v websocat &>/dev/null; then note SKIP "websocat not installed — skipping WebSocket test" note SKIP "Install: cargo install websocat | brew install websocat" # Don't count as fail else # Send subscribe, expect agent_count or ping within 3s WS_OUT=$(echo '{"type":"subscribe","channel":"agents","clientId":"e2e-test"}' \ | timeout 4 websocat --no-close "$WS_URL" 2>/dev/null | head -1 || echo "") WS_TYPE=$(echo "$WS_OUT" | jq -r '.type' 2>/dev/null || echo "") if [[ "$WS_TYPE" == "agent_count" || "$WS_TYPE" == "ping" ]]; then note PASS "WebSocket connected, received: type=$WS_TYPE" PASS=$((PASS+1)) else note FAIL "No valid WS message received (got: '$WS_OUT')" FAIL=$((FAIL+1)) fi fi # --------------------------------------------------------------------------- # 4. Mode 1 — Full job flow # --------------------------------------------------------------------------- sep "4. Mode 1 — Create job" R=$(curl -s -w "\n%{http_code}" -X POST "$BASE/api/jobs" \ -H "Content-Type: application/json" \ -d '{"request":"What is a satoshi? One sentence."}') B=$(body_of "$R"); C=$(code_of "$R") JOB_ID=$(echo "$B" | jq -r '.jobId' 2>/dev/null || echo "") EVAL_HASH=$(echo "$B" | jq -r '.evalInvoice.paymentHash' 2>/dev/null || echo "") if [[ "$C" == "201" && -n "$JOB_ID" ]]; then note PASS "POST /api/jobs → 201, jobId=$JOB_ID" PASS=$((PASS+1)) else note FAIL "code=$C body=$B" FAIL=$((FAIL+1)) fi sep "4b. Pay eval invoice (stub)" if [[ -n "$EVAL_HASH" && "$EVAL_HASH" != "null" ]]; then R=$(curl -s -w "\n%{http_code}" -X POST "$BASE/api/dev/stub/pay/$EVAL_HASH") B=$(body_of "$R"); C=$(code_of "$R") if [[ "$C" == "200" && "$(echo "$B" | jq -r '.ok')" == "true" ]]; then note PASS "Stub pay eval → ok=true" PASS=$((PASS+1)) else note FAIL "code=$C body=$B" FAIL=$((FAIL+1)) fi else note SKIP "No eval hash (not stub mode)" fi sep "4c. Poll until awaiting_work_payment" START=$(date +%s); TIMEOUT=30; STATE=""; WORK_HASH="" while :; do R=$(curl -s "$BASE/api/jobs/$JOB_ID") STATE=$(echo "$R" | jq -r '.state' 2>/dev/null || echo "") WORK_HASH=$(echo "$R" | jq -r '.workInvoice.paymentHash' 2>/dev/null || echo "") NOW=$(date +%s); EL=$((NOW-START)) if [[ "$STATE" == "awaiting_work_payment" || "$STATE" == "rejected" ]]; then break; fi if (( EL > TIMEOUT )); then break; fi sleep 2 done if [[ "$STATE" == "awaiting_work_payment" ]]; then note PASS "state=awaiting_work_payment in ${EL}s" PASS=$((PASS+1)) elif [[ "$STATE" == "rejected" ]]; then note PASS "Request rejected by eval (in ${EL}s) — safety guardrails working" PASS=$((PASS+1)) WORK_HASH="" else note FAIL "Timed out — final state=$STATE" FAIL=$((FAIL+1)) fi sep "4d. Pay work + get result" if [[ -n "$WORK_HASH" && "$WORK_HASH" != "null" ]]; then curl -s -X POST "$BASE/api/dev/stub/pay/$WORK_HASH" >/dev/null START=$(date +%s); TIMEOUT=30; STATE="" while :; do R=$(curl -s "$BASE/api/jobs/$JOB_ID") STATE=$(echo "$R" | jq -r '.state' 2>/dev/null || echo "") RESULT=$(echo "$R" | jq -r '.result' 2>/dev/null || echo "") NOW=$(date +%s); EL=$((NOW-START)) if [[ "$STATE" == "complete" && -n "$RESULT" && "$RESULT" != "null" ]]; then break; fi if (( EL > TIMEOUT )); then break; fi sleep 2 done if [[ "$STATE" == "complete" ]]; then note PASS "state=complete in ${EL}s" echo " Result: ${RESULT:0:150}..." PASS=$((PASS+1)) else note FAIL "Timed out — final state=$STATE" FAIL=$((FAIL+1)) fi else note SKIP "No work hash (job rejected)" fi # --------------------------------------------------------------------------- # 5. Mode 2 — Session flow # --------------------------------------------------------------------------- sep "5. Mode 2 — Create session" R=$(curl -s -w "\n%{http_code}" -X POST "$BASE/api/sessions" \ -H "Content-Type: application/json" \ -d '{"amount_sats":200}') B=$(body_of "$R"); C=$(code_of "$R") SID=$(echo "$B" | jq -r '.sessionId' 2>/dev/null || echo "") DEP_HASH=$(echo "$B" | jq -r '.invoice.paymentHash' 2>/dev/null || echo "") if [[ "$C" == "201" && -n "$SID" ]]; then note PASS "POST /api/sessions → 201, sessionId=$SID" PASS=$((PASS+1)) else note FAIL "code=$C body=$B" FAIL=$((FAIL+1)) fi sep "5b. Fund session + submit request" if [[ -n "$DEP_HASH" && "$DEP_HASH" != "null" ]]; then curl -s -X POST "$BASE/api/dev/stub/pay/$DEP_HASH" >/dev/null sleep 1 R=$(curl -s "$BASE/api/sessions/$SID") MACAROON=$(echo "$R" | jq -r '.macaroon' 2>/dev/null || echo "") STATE=$(echo "$R" | jq -r '.state' 2>/dev/null || echo "") if [[ "$STATE" == "active" && -n "$MACAROON" && "$MACAROON" != "null" ]]; then note PASS "Session active, macaroon issued" PASS=$((PASS+1)) # Submit a request R=$(curl -s -w "\n%{http_code}" -X POST "$BASE/api/sessions/$SID/request" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $MACAROON" \ -d '{"request":"What is Bitcoin in 10 words?"}') B=$(body_of "$R"); C=$(code_of "$R") REQ_STATE=$(echo "$B" | jq -r '.state' 2>/dev/null || echo "") DEBITED=$(echo "$B" | jq -r '.debitedSats' 2>/dev/null || echo "") if [[ "$C" == "200" && ("$REQ_STATE" == "complete" || "$REQ_STATE" == "rejected") && -n "$DEBITED" ]]; then note PASS "Session request $REQ_STATE, debitedSats=$DEBITED" PASS=$((PASS+1)) else note FAIL "code=$C body=$B" FAIL=$((FAIL+1)) fi else note FAIL "Session not active after payment: state=$STATE" FAIL=$((FAIL+1)) fi else note SKIP "No deposit hash" fi # --------------------------------------------------------------------------- # Summary # --------------------------------------------------------------------------- echo echo "===========================================" echo " E2E RESULTS: PASS=$PASS FAIL=$FAIL" echo "===========================================" if [[ "$FAIL" -gt 0 ]]; then exit 1; fi