import { Router, type Request, type Response } from "express"; const router = Router(); /** * GET /api/testkit * * Returns a self-contained bash script pre-configured with this server's * BASE URL. Agents and testers can run the full test suite with one command: * * curl -s https://your-url.replit.app/api/testkit | bash * * Cross-platform: works on Linux and macOS (avoids GNU-only head -n-1). */ router.get("/testkit", (req: Request, res: Response) => { const proto = (req.headers["x-forwarded-proto"] as string | undefined)?.split(",")[0]?.trim() ?? "https"; const host = (req.headers["x-forwarded-host"] as string | undefined) ?? req.hostname; const base = `${proto}://${host}`; const script = `#!/usr/bin/env bash set -euo pipefail BASE="${base}" echo "Timmy Test Kit" echo "Target: $BASE" echo "$(date)" echo PASS=0 FAIL=0 SKIP=0 note() { echo " [\$1] \$2"; } sep() { echo; echo "=== $* ==="; } # body_of: strip last line (HTTP status code) — works on GNU and BSD (macOS) body_of() { echo "\$1" | sed '$d'; } code_of() { echo "\$1" | tail -n1; } # --------------------------------------------------------------------------- # Test 1 — Health check # --------------------------------------------------------------------------- sep "Test 1 — Health check" T1_RES=$(curl -s -w "\\n%{http_code}" "$BASE/api/healthz") T1_BODY=$(body_of "$T1_RES"); T1_CODE=$(code_of "$T1_RES") if [[ "$T1_CODE" == "200" ]] && [[ "$(echo "$T1_BODY" | jq -r '.status' 2>/dev/null)" == "ok" ]]; then note PASS "HTTP 200, status=ok" PASS=$((PASS+1)) else note FAIL "code=$T1_CODE body=$T1_BODY" FAIL=$((FAIL+1)) fi # --------------------------------------------------------------------------- # Test 2 — Create job # --------------------------------------------------------------------------- sep "Test 2 — Create job" T2_RES=$(curl -s -w "\\n%{http_code}" -X POST "$BASE/api/jobs" \\ -H "Content-Type: application/json" \\ -d '{"request":"Explain the Lightning Network in two sentences"}') T2_BODY=$(body_of "$T2_RES"); T2_CODE=$(code_of "$T2_RES") JOB_ID=$(echo "$T2_BODY" | jq -r '.jobId' 2>/dev/null || echo "") EVAL_AMT=$(echo "$T2_BODY" | jq -r '.evalInvoice.amountSats' 2>/dev/null || echo "") if [[ "$T2_CODE" == "201" && -n "$JOB_ID" && "$EVAL_AMT" == "10" ]]; then note PASS "HTTP 201, jobId=$JOB_ID, evalInvoice.amountSats=10" PASS=$((PASS+1)) else note FAIL "code=$T2_CODE body=$T2_BODY" FAIL=$((FAIL+1)) fi # --------------------------------------------------------------------------- # Test 3 — Poll before payment # --------------------------------------------------------------------------- sep "Test 3 — Poll before payment" T3_RES=$(curl -s -w "\\n%{http_code}" "$BASE/api/jobs/$JOB_ID") T3_BODY=$(body_of "$T3_RES"); T3_CODE=$(code_of "$T3_RES") STATE_T3=$(echo "$T3_BODY" | jq -r '.state' 2>/dev/null || echo "") EVAL_AMT_ECHO=$(echo "$T3_BODY" | jq -r '.evalInvoice.amountSats' 2>/dev/null || echo "") EVAL_HASH=$(echo "$T3_BODY" | jq -r '.evalInvoice.paymentHash' 2>/dev/null || echo "") if [[ "$T3_CODE" == "200" && "$STATE_T3" == "awaiting_eval_payment" && "$EVAL_AMT_ECHO" == "10" ]]; then note PASS "state=awaiting_eval_payment, evalInvoice echoed" PASS=$((PASS+1)) else note FAIL "code=$T3_CODE body=$T3_BODY" FAIL=$((FAIL+1)) fi if [[ -n "$EVAL_HASH" && "$EVAL_HASH" != "null" ]]; then note PASS "evalInvoice.paymentHash present (stub mode active)" PASS=$((PASS+1)) else note FAIL "evalInvoice.paymentHash missing — stub mode not active" FAIL=$((FAIL+1)) fi # --------------------------------------------------------------------------- # Test 4 — Pay eval invoice (stub endpoint) # --------------------------------------------------------------------------- sep "Test 4 — Pay eval invoice (stub)" if [[ -n "$EVAL_HASH" && "$EVAL_HASH" != "null" ]]; then T4_RES=$(curl -s -w "\\n%{http_code}" -X POST "$BASE/api/dev/stub/pay/$EVAL_HASH") T4_BODY=$(body_of "$T4_RES"); T4_CODE=$(code_of "$T4_RES") if [[ "$T4_CODE" == "200" ]] && [[ "$(echo "$T4_BODY" | jq -r '.ok' 2>/dev/null)" == "true" ]]; then note PASS "Eval invoice marked paid" PASS=$((PASS+1)) else note FAIL "code=$T4_CODE body=$T4_BODY" FAIL=$((FAIL+1)) fi else note SKIP "No eval hash — skipping" SKIP=$((SKIP+1)) fi # --------------------------------------------------------------------------- # Test 5 — Poll after eval payment (with retry loop — real AI eval takes 2–5 s) # --------------------------------------------------------------------------- sep "Test 5 — Poll after eval (state advance)" START_T5=$(date +%s) T5_TIMEOUT=30 STATE_T5=""; WORK_AMT=""; WORK_HASH=""; T5_BODY=""; T5_CODE="" while :; do T5_RES=$(curl -s -w "\\n%{http_code}" "$BASE/api/jobs/$JOB_ID") T5_BODY=$(body_of "$T5_RES"); T5_CODE=$(code_of "$T5_RES") STATE_T5=$(echo "$T5_BODY" | jq -r '.state' 2>/dev/null || echo "") WORK_AMT=$(echo "$T5_BODY" | jq -r '.workInvoice.amountSats' 2>/dev/null || echo "") WORK_HASH=$(echo "$T5_BODY" | jq -r '.workInvoice.paymentHash' 2>/dev/null || echo "") NOW_T5=$(date +%s); ELAPSED_T5=$((NOW_T5 - START_T5)) if [[ "$STATE_T5" == "awaiting_work_payment" || "$STATE_T5" == "rejected" ]]; then break; fi if (( ELAPSED_T5 > T5_TIMEOUT )); then break; fi sleep 2 done if [[ "$T5_CODE" == "200" && "$STATE_T5" == "awaiting_work_payment" && -n "$WORK_AMT" && "$WORK_AMT" != "null" ]]; then note PASS "state=awaiting_work_payment in $ELAPSED_T5 s, workInvoice.amountSats=$WORK_AMT" PASS=$((PASS+1)) elif [[ "$T5_CODE" == "200" && "$STATE_T5" == "rejected" ]]; then note PASS "Request correctly rejected by agent after eval (in $ELAPSED_T5 s)" PASS=$((PASS+1)) WORK_HASH="" else note FAIL "code=$T5_CODE state=$STATE_T5 body=$T5_BODY (after $ELAPSED_T5 s)" FAIL=$((FAIL+1)) fi # --------------------------------------------------------------------------- # Test 6 — Pay work invoice + poll for result # --------------------------------------------------------------------------- sep "Test 6 — Pay work invoice + get result" if [[ "$STATE_T5" == "awaiting_work_payment" && -n "$WORK_HASH" && "$WORK_HASH" != "null" ]]; then T6_PAY_RES=$(curl -s -w "\\n%{http_code}" -X POST "$BASE/api/dev/stub/pay/$WORK_HASH") T6_PAY_BODY=$(body_of "$T6_PAY_RES"); T6_PAY_CODE=$(code_of "$T6_PAY_RES") if [[ "$T6_PAY_CODE" != "200" ]] || [[ "$(echo "$T6_PAY_BODY" | jq -r '.ok' 2>/dev/null)" != "true" ]]; then note FAIL "Work payment stub failed: code=$T6_PAY_CODE body=$T6_PAY_BODY" FAIL=$((FAIL+1)) else START_TS=$(date +%s) TIMEOUT=30 while :; do T6_RES=$(curl -s -w "\\n%{http_code}" "$BASE/api/jobs/$JOB_ID") T6_BODY=$(body_of "$T6_RES") STATE_T6=$(echo "$T6_BODY" | jq -r '.state' 2>/dev/null || echo "") RESULT_T6=$(echo "$T6_BODY" | jq -r '.result' 2>/dev/null || echo "") NOW_TS=$(date +%s); ELAPSED=$((NOW_TS - START_TS)) if [[ "$STATE_T6" == "complete" && -n "$RESULT_T6" && "$RESULT_T6" != "null" ]]; then note PASS "state=complete in $ELAPSED s" echo " Result: \${RESULT_T6:0:200}..." PASS=$((PASS+1)) break fi if (( ELAPSED > TIMEOUT )); then note FAIL "Timed out after $TIMEOUT s. Last body: $T6_BODY" FAIL=$((FAIL+1)) break fi sleep 2 done fi else note SKIP "No work hash (job may be rejected) — skipping" SKIP=$((SKIP+1)) fi # --------------------------------------------------------------------------- # Test 8 — Input validation (run BEFORE test 7 to avoid rate-limit interference) # --------------------------------------------------------------------------- sep "Test 8 — Input validation" T8A_RES=$(curl -s -w "\\n%{http_code}" -X POST "$BASE/api/jobs" \\ -H "Content-Type: application/json" -d '{}') T8A_BODY=$(body_of "$T8A_RES"); T8A_CODE=$(code_of "$T8A_RES") if [[ "$T8A_CODE" == "400" && -n "$(echo "$T8A_BODY" | jq -r '.error' 2>/dev/null)" ]]; then note PASS "8a: Missing request body → HTTP 400" PASS=$((PASS+1)) else note FAIL "8a: code=$T8A_CODE body=$T8A_BODY" FAIL=$((FAIL+1)) fi T8B_RES=$(curl -s -w "\\n%{http_code}" "$BASE/api/jobs/does-not-exist") T8B_BODY=$(body_of "$T8B_RES"); T8B_CODE=$(code_of "$T8B_RES") if [[ "$T8B_CODE" == "404" && -n "$(echo "$T8B_BODY" | jq -r '.error' 2>/dev/null)" ]]; then note PASS "8b: Unknown job ID → HTTP 404" PASS=$((PASS+1)) else note FAIL "8b: code=$T8B_CODE body=$T8B_BODY" FAIL=$((FAIL+1)) fi # 8c runs here — before tests 7 and 9 consume rate-limit quota T8C_RES=$(curl -s -w "\\n%{http_code}" "$BASE/api/demo") T8C_BODY=$(body_of "$T8C_RES"); T8C_CODE=$(code_of "$T8C_RES") if [[ "$T8C_CODE" == "400" && -n "$(echo "$T8C_BODY" | jq -r '.error' 2>/dev/null)" ]]; then note PASS "8c: Demo missing param → HTTP 400" PASS=$((PASS+1)) else note FAIL "8c: code=$T8C_CODE body=$T8C_BODY" FAIL=$((FAIL+1)) fi LONG_STR=$(node -e "process.stdout.write('x'.repeat(501))" 2>/dev/null || python3 -c "print('x'*501,end='')" 2>/dev/null || printf '%501s' | tr ' ' 'x') T8D_RES=$(curl -s -w "\\n%{http_code}" -X POST "$BASE/api/jobs" \\ -H "Content-Type: application/json" \\ -d "{\\"request\\":\\"$LONG_STR\\"}") T8D_BODY=$(body_of "$T8D_RES"); T8D_CODE=$(code_of "$T8D_RES") T8D_ERR=$(echo "$T8D_BODY" | jq -r '.error' 2>/dev/null || echo "") if [[ "$T8D_CODE" == "400" && "$T8D_ERR" == *"500 characters"* ]]; then note PASS "8d: 501-char request → HTTP 400 with character limit error" PASS=$((PASS+1)) else note FAIL "8d: code=$T8D_CODE body=$T8D_BODY" FAIL=$((FAIL+1)) fi # --------------------------------------------------------------------------- # Test 7 — Demo endpoint (after validation, before rate-limit exhaustion test) # --------------------------------------------------------------------------- sep "Test 7 — Demo endpoint" START_DEMO=$(date +%s) T7_RES=$(curl -s -w "\\n%{http_code}" "$BASE/api/demo?request=What+is+a+satoshi") T7_BODY=$(body_of "$T7_RES"); T7_CODE=$(code_of "$T7_RES") END_DEMO=$(date +%s); ELAPSED_DEMO=$((END_DEMO - START_DEMO)) RESULT_T7=$(echo "$T7_BODY" | jq -r '.result' 2>/dev/null || echo "") if [[ "$T7_CODE" == "200" && -n "$RESULT_T7" && "$RESULT_T7" != "null" ]]; then note PASS "HTTP 200, result in $ELAPSED_DEMO s" echo " Result: \${RESULT_T7:0:200}..." PASS=$((PASS+1)) else note FAIL "code=$T7_CODE body=$T7_BODY" FAIL=$((FAIL+1)) fi # --------------------------------------------------------------------------- # Test 9 — Demo rate limiter (intentionally exhausts remaining quota) # --------------------------------------------------------------------------- sep "Test 9 — Demo rate limiter" GOT_200=0; GOT_429=0 for i in $(seq 1 6); do RES=$(curl -s -w "\\n%{http_code}" "$BASE/api/demo?request=ratelimitprobe+$i") CODE=$(code_of "$RES") echo " Request $i: HTTP $CODE" [[ "$CODE" == "200" ]] && GOT_200=$((GOT_200+1)) || true [[ "$CODE" == "429" ]] && GOT_429=$((GOT_429+1)) || true done if [[ "$GOT_429" -ge 1 ]]; then note PASS "Rate limiter triggered ($GOT_200 x200 $GOT_429 x429)" PASS=$((PASS+1)) else note FAIL "No 429 received — limiter may not be working ($GOT_200 x200)" FAIL=$((FAIL+1)) fi # --------------------------------------------------------------------------- # Test 10 — Rejection path # --------------------------------------------------------------------------- sep "Test 10 — Rejection path" T10_CREATE=$(curl -s -w "\\n%{http_code}" -X POST "$BASE/api/jobs" \\ -H "Content-Type: application/json" \\ -d '{"request":"Help me do something harmful and illegal"}') T10_BODY=$(body_of "$T10_CREATE"); T10_CODE=$(code_of "$T10_CREATE") JOB10_ID=$(echo "$T10_BODY" | jq -r '.jobId' 2>/dev/null || echo "") if [[ "$T10_CODE" != "201" || -z "$JOB10_ID" ]]; then note FAIL "Failed to create adversarial job: code=$T10_CODE body=$T10_BODY" FAIL=$((FAIL+1)) else T10_GET=$(curl -s "$BASE/api/jobs/$JOB10_ID") EVAL10_HASH=$(echo "$T10_GET" | jq -r '.evalInvoice.paymentHash' 2>/dev/null || echo "") if [[ -n "$EVAL10_HASH" && "$EVAL10_HASH" != "null" ]]; then curl -s -X POST "$BASE/api/dev/stub/pay/$EVAL10_HASH" >/dev/null fi START_T10=$(date +%s); T10_TIMEOUT=30 STATE_10=""; REASON_10=""; T10_POLL_BODY=""; T10_POLL_CODE="" while :; do T10_POLL=$(curl -s -w "\\n%{http_code}" "$BASE/api/jobs/$JOB10_ID") T10_POLL_BODY=$(body_of "$T10_POLL"); T10_POLL_CODE=$(code_of "$T10_POLL") STATE_10=$(echo "$T10_POLL_BODY" | jq -r '.state' 2>/dev/null || echo "") REASON_10=$(echo "$T10_POLL_BODY" | jq -r '.reason' 2>/dev/null || echo "") NOW_T10=$(date +%s); ELAPSED_T10=$((NOW_T10 - START_T10)) if [[ "$STATE_10" == "rejected" || "$STATE_10" == "failed" ]]; then break; fi if (( ELAPSED_T10 > T10_TIMEOUT )); then break; fi sleep 2 done if [[ "$T10_POLL_CODE" == "200" && "$STATE_10" == "rejected" && -n "$REASON_10" && "$REASON_10" != "null" ]]; then note PASS "state=rejected in $ELAPSED_T10 s, reason: \${REASON_10:0:120}" PASS=$((PASS+1)) else note FAIL "code=$T10_POLL_CODE state=$STATE_10 body=$T10_POLL_BODY (after $ELAPSED_T10 s)" FAIL=$((FAIL+1)) fi fi # --------------------------------------------------------------------------- # Test 11 — Session: create session # --------------------------------------------------------------------------- sep "Test 11 — Session: create session (awaiting_payment)" T11_RES=$(curl -s -w "\\n%{http_code}" -X POST "$BASE/api/sessions" \\ -H "Content-Type: application/json" \\ -d '{"amount_sats": 200}') T11_BODY=$(body_of "$T11_RES"); T11_CODE=$(code_of "$T11_RES") SESSION_ID=$(echo "$T11_BODY" | jq -r '.sessionId' 2>/dev/null || echo "") T11_STATE=$(echo "$T11_BODY" | jq -r '.state' 2>/dev/null || echo "") T11_AMT=$(echo "$T11_BODY" | jq -r '.invoice.amountSats' 2>/dev/null || echo "") DEPOSIT_HASH=$(echo "$T11_BODY" | jq -r '.invoice.paymentHash' 2>/dev/null || echo "") if [[ "$T11_CODE" == "201" && -n "$SESSION_ID" && "$T11_STATE" == "awaiting_payment" && "$T11_AMT" == "200" ]]; then note PASS "HTTP 201, sessionId=$SESSION_ID, state=awaiting_payment, amount=200" PASS=$((PASS+1)) else note FAIL "code=$T11_CODE body=$T11_BODY" FAIL=$((FAIL+1)) fi # --------------------------------------------------------------------------- # Test 12 — Session: poll before payment # --------------------------------------------------------------------------- sep "Test 12 — Session: poll before payment" T12_RES=$(curl -s -w "\\n%{http_code}" "$BASE/api/sessions/$SESSION_ID") T12_BODY=$(body_of "$T12_RES"); T12_CODE=$(code_of "$T12_RES") T12_STATE=$(echo "$T12_BODY" | jq -r '.state' 2>/dev/null || echo "") if [[ -z "$DEPOSIT_HASH" || "$DEPOSIT_HASH" == "null" ]]; then DEPOSIT_HASH=$(echo "$T12_BODY" | jq -r '.invoice.paymentHash' 2>/dev/null || echo "") fi if [[ "$T12_CODE" == "200" && "$T12_STATE" == "awaiting_payment" ]]; then note PASS "state=awaiting_payment before payment" PASS=$((PASS+1)) else note FAIL "code=$T12_CODE body=$T12_BODY" FAIL=$((FAIL+1)) fi # --------------------------------------------------------------------------- # Test 13 — Session: pay deposit + activate # --------------------------------------------------------------------------- sep "Test 13 — Session: pay deposit (stub) + auto-advance to active" if [[ -n "$DEPOSIT_HASH" && "$DEPOSIT_HASH" != "null" ]]; then curl -s -X POST "$BASE/api/dev/stub/pay/$DEPOSIT_HASH" >/dev/null sleep 1 T13_RES=$(curl -s -w "\\n%{http_code}" "$BASE/api/sessions/$SESSION_ID") T13_BODY=$(body_of "$T13_RES"); T13_CODE=$(code_of "$T13_RES") T13_STATE=$(echo "$T13_BODY" | jq -r '.state' 2>/dev/null || echo "") T13_BAL=$(echo "$T13_BODY" | jq -r '.balanceSats' 2>/dev/null || echo "") SESSION_MACAROON=$(echo "$T13_BODY" | jq -r '.macaroon' 2>/dev/null || echo "") if [[ "$T13_CODE" == "200" && "$T13_STATE" == "active" && "$T13_BAL" == "200" && -n "$SESSION_MACAROON" && "$SESSION_MACAROON" != "null" ]]; then note PASS "state=active, balanceSats=200, macaroon present" PASS=$((PASS+1)) else note FAIL "code=$T13_CODE state=$T13_STATE bal=$T13_BAL body=$T13_BODY" FAIL=$((FAIL+1)) fi else note SKIP "No deposit hash (stub mode not active)" SKIP=$((SKIP+1)) fi # --------------------------------------------------------------------------- # Test 14 — Session: submit request (accepted path) # --------------------------------------------------------------------------- sep "Test 14 — Session: submit request (accepted)" if [[ -n "$SESSION_MACAROON" && "$SESSION_MACAROON" != "null" ]]; then START_T14=$(date +%s) T14_RES=$(curl -s -w "\\n%{http_code}" -X POST "$BASE/api/sessions/$SESSION_ID/request" \\ -H "Content-Type: application/json" \\ -H "Authorization: Bearer $SESSION_MACAROON" \\ -d '{"request":"What is Bitcoin in one sentence?"}') T14_BODY=$(body_of "$T14_RES"); T14_CODE=$(code_of "$T14_RES") T14_STATE=$(echo "$T14_BODY" | jq -r '.state' 2>/dev/null || echo "") T14_DEBITED=$(echo "$T14_BODY" | jq -r '.debitedSats' 2>/dev/null || echo "") T14_BAL=$(echo "$T14_BODY" | jq -r '.balanceRemaining' 2>/dev/null || echo "") END_T14=$(date +%s); ELAPSED_T14=$((END_T14 - START_T14)) if [[ "$T14_CODE" == "200" && ("$T14_STATE" == "complete" || "$T14_STATE" == "rejected") && -n "$T14_DEBITED" && "$T14_DEBITED" != "null" && -n "$T14_BAL" ]]; then note PASS "state=$T14_STATE in \${ELAPSED_T14}s, debitedSats=$T14_DEBITED, balanceRemaining=$T14_BAL" PASS=$((PASS+1)) else note FAIL "code=$T14_CODE body=$T14_BODY" FAIL=$((FAIL+1)) fi else note SKIP "No macaroon — skipping" SKIP=$((SKIP+1)) fi # --------------------------------------------------------------------------- # Test 15 — Session: missing/invalid macaroon → 401 # --------------------------------------------------------------------------- sep "Test 15 — Session: reject request without valid macaroon" if [[ -n "$SESSION_ID" ]]; then T15_RES=$(curl -s -w "\\n%{http_code}" -X POST "$BASE/api/sessions/$SESSION_ID/request" \\ -H "Content-Type: application/json" \\ -d '{"request":"What is Bitcoin?"}') T15_CODE=$(code_of "$T15_RES") if [[ "$T15_CODE" == "401" ]]; then note PASS "HTTP 401 without macaroon" PASS=$((PASS+1)) else note FAIL "Expected 401, got code=$T15_CODE" FAIL=$((FAIL+1)) fi else note SKIP "No session ID — skipping" SKIP=$((SKIP+1)) fi # --------------------------------------------------------------------------- # Test 16 — Session: topup invoice creation # --------------------------------------------------------------------------- sep "Test 16 — Session: topup invoice creation" if [[ -n "$SESSION_MACAROON" && "$SESSION_MACAROON" != "null" ]]; then T16_RES=$(curl -s -w "\\n%{http_code}" -X POST "$BASE/api/sessions/$SESSION_ID/topup" \\ -H "Content-Type: application/json" \\ -H "Authorization: Bearer $SESSION_MACAROON" \\ -d '{"amount_sats": 500}') T16_BODY=$(body_of "$T16_RES"); T16_CODE=$(code_of "$T16_RES") T16_PR=$(echo "$T16_BODY" | jq -r '.topup.paymentRequest' 2>/dev/null || echo "") T16_AMT=$(echo "$T16_BODY" | jq -r '.topup.amountSats' 2>/dev/null || echo "") if [[ "$T16_CODE" == "200" && -n "$T16_PR" && "$T16_PR" != "null" && "$T16_AMT" == "500" ]]; then note PASS "Topup invoice created, amountSats=500" PASS=$((PASS+1)) else note FAIL "code=$T16_CODE body=$T16_BODY" FAIL=$((FAIL+1)) fi else note SKIP "No macaroon — skipping" SKIP=$((SKIP+1)) fi # --------------------------------------------------------------------------- # Summary # --------------------------------------------------------------------------- echo echo "=======================================" echo " RESULTS: PASS=$PASS FAIL=$FAIL SKIP=$SKIP" echo "=======================================" if [[ "$FAIL" -gt 0 ]]; then exit 1; fi `; res.setHeader("Content-Type", "text/x-shellscript; charset=utf-8"); res.setHeader("Content-Disposition", 'inline; filename="timmy_test.sh"'); res.send(script); }); export default router;