This repository has been archived on 2026-03-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
token-gated-economy/attached_assets/Pasted-I-can-update-the-plan-text-but-I-cannot-directly-hit-yo_1773854768326.txt
alexpaynex 53bc93a9b4 Add automated testing script and expose payment hashes
Integrates a new bash script for automated end-to-end testing of the Timmy API. Updates API routes to expose payment hashes in stub mode for easier invoice payment simulation during testing. Modifies test plan documentation to include the new automated script.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 6f2776b0-a913-41d3-a988-759a82feb6f3
Replit-Helium-Checkpoint-Created: true
2026-03-18 17:30:13 +00:00

334 lines
12 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
I can update the plan text, but I cannot directly hit your Replit deployment from here, so Ill give you an executable script plus a report template you can run and then paste results into.[1]
## Updated plan: add automation + latency logging
Key updates layered on top of your existing suite.[1]
- Add a **smoke-test bash script** that executes Tests 110 sequentially using `BASE` and logs pass/fail.[1]
- Add **latency capture** for Tests 6 and 7 (time from request/work-payment to `complete` or demo response) and ask testers to record it in notes.[1]
- Clarify **how to get full payment hashes** in stub mode: by polling `GET /api/jobs/<jobId>` and reading the `evalInvoice.paymentHash` and `workInvoice.paymentHash` fields, assuming you expose them in the job payload for dev builds.[1]
Youll want to ensure your `GET /api/jobs/:id` response includes full hashes in stub/dev mode (even if only behind a `NODE_ENV !== "production"` guard).[1]
## Bash script to execute the plan
Below is a single-file script that runs Tests 110 in order, tracks state across steps, prints results, and records simple latency numbers.[1]
Save as `timmy_test.sh`, `chmod +x timmy_test.sh`, then run:
```bash
BASE="https://<your-timmy-url>.replit.app" ./timmy_test.sh
```
Script:
```bash
#!/usr/bin/env bash
set -euo pipefail
BASE="${BASE:-}"
if [[ -z "$BASE" ]]; then
echo "ERROR: BASE environment variable is required (e.g., BASE=https://your-url ./timmy_test.sh)" >&2
exit 1
fi
echo "Testing Timmy at BASE=$BASE"
echo
PASS=0
FAIL=0
SKIP=0
note() { echo "[$1] $2"; }
# Helpers
jq_field() {
echo "$1" | jq -r "$2" 2>/dev/null || echo ""
}
# Test 1 — Health check
echo "=== Test 1 — Health check ==="
T1_RES=$(curl -s -w "\n%{http_code}" "$BASE/api/healthz" || true)
T1_BODY=$(echo "$T1_RES" | head -n-1)
T1_CODE=$(echo "$T1_RES" | tail -n1)
if [[ "$T1_CODE" == "200" && "$(jq_field "$T1_BODY" '.status')" == "ok" ]]; then
note PASS "Health check OK (HTTP 200, status=ok)"
((PASS++))
else
note FAIL "Unexpected health response: code=$T1_CODE body=$T1_BODY"
((FAIL++))
fi
echo
# Test 2 — Create a job
echo "=== Test 2 — Create job ==="
REQ_MSG="Explain the Lightning Network in two sentences"
T2_RES=$(curl -s -w "\n%{http_code}" -X POST "$BASE/api/jobs" \
-H "Content-Type: application/json" \
-d "{\"request\":\"$REQ_MSG\"}" || true)
T2_BODY=$(echo "$T2_RES" | head -n-1)
T2_CODE=$(echo "$T2_RES" | tail -n1)
JOB_ID=$(jq_field "$T2_BODY" '.jobId')
EVAL_AMT=$(jq_field "$T2_BODY" '.evalInvoice.amountSats')
EVAL_PR=$(jq_field "$T2_BODY" '.evalInvoice.paymentRequest')
if [[ "$T2_CODE" == "201" && -n "$JOB_ID" && "$EVAL_AMT" == "10" ]]; then
note PASS "Created job: jobId=$JOB_ID evalInvoice.amountSats=10"
((PASS++))
else
note FAIL "Unexpected create-job response: code=$T2_CODE body=$T2_BODY"
((FAIL++))
fi
echo
# Test 3 — Poll job before payment
echo "=== Test 3 — Poll before payment ==="
T3_RES=$(curl -s -w "\n%{http_code}" "$BASE/api/jobs/$JOB_ID" || true)
T3_BODY=$(echo "$T3_RES" | head -n-1)
T3_CODE=$(echo "$T3_RES" | tail -n1)
STATE_T3=$(jq_field "$T3_BODY" '.state')
EVAL_AMT_ECHO=$(jq_field "$T3_BODY" '.evalInvoice.amountSats')
if [[ "$T3_CODE" == "200" && "$STATE_T3" == "awaiting_eval_payment" && "$EVAL_AMT_ECHO" == "10" ]]; then
note PASS "Job awaiting_eval_payment with evalInvoice echoed"
((PASS++))
else
note FAIL "Unexpected job state before payment: code=$T3_CODE body=$T3_BODY"
((FAIL++))
fi
echo
# Extract eval payment hash in dev mode
# Convention: stub format lnbcrt10u1stub_<first-16-chars>, full hash should be exposed by API if possible.
EVAL_HASH=$(jq_field "$T3_BODY" '.evalInvoice.paymentHash')
if [[ -z "$EVAL_HASH" ]]; then
note FAIL "evalInvoice.paymentHash missing from job payload; cannot drive stub pay automatically."
((FAIL++))
else
echo "Eval payment hash: $EVAL_HASH"
fi
echo
# Test 4 — Pay the eval invoice (stub mode)
echo "=== Test 4 — Pay eval invoice (stub) ==="
if [[ -n "$EVAL_HASH" ]]; then
T4_RES=$(curl -s -w "\n%{http_code}" -X POST "$BASE/api/dev/stub/pay/$EVAL_HASH" || true)
T4_BODY=$(echo "$T4_RES" | head -n-1)
T4_CODE=$(echo "$T4_RES" | tail -n1)
OK_T4=$(jq_field "$T4_BODY" '.ok')
if [[ "$T4_CODE" == "200" && "$OK_T4" == "true" ]]; then
note PASS "Eval stub payment accepted"
((PASS++))
else
note FAIL "Unexpected stub eval payment response: code=$T4_CODE body=$T4_BODY"
((FAIL++))
fi
else
note SKIP "No eval payment hash available; skipping Test 4"
((SKIP++))
fi
echo
# Test 5 — Poll after eval payment (state machine advance)
echo "=== Test 5 — Poll after eval (state advance) ==="
T5_RES=$(curl -s -w "\n%{http_code}" "$BASE/api/jobs/$JOB_ID" || true)
T5_BODY=$(echo "$T5_RES" | head -n-1)
T5_CODE=$(echo "$T5_RES" | tail -n1)
STATE_T5=$(jq_field "$T5_BODY" '.state')
WORK_AMT=$(jq_field "$T5_BODY" '.workInvoice.amountSats')
WORK_HASH=$(jq_field "$T5_BODY" '.workInvoice.paymentHash')
if [[ "$T5_CODE" == "200" && "$STATE_T5" == "awaiting_work_payment" && -n "$WORK_AMT" ]]; then
note PASS "Job advanced to awaiting_work_payment, workInvoice.amountSats=$WORK_AMT"
((PASS++))
elif [[ "$T5_CODE" == "200" && "$STATE_T5" == "rejected" ]]; then
note PASS "Job correctly rejected after eval: state=rejected"
((PASS++))
else
note FAIL "Unexpected state after eval payment: code=$T5_CODE body=$T5_BODY"
((FAIL++))
fi
echo
# Test 6 — Pay the work invoice and get the result
echo "=== Test 6 — Pay work invoice + get result ==="
if [[ "$STATE_T5" == "awaiting_work_payment" && -n "$WORK_HASH" ]]; then
T6_PAY_RES=$(curl -s -w "\n%{http_code}" -X POST "$BASE/api/dev/stub/pay/$WORK_HASH" || true)
T6_PAY_BODY=$(echo "$T6_PAY_RES" | head -n-1)
T6_PAY_CODE=$(echo "$T6_PAY_RES" | tail -n1)
OK_T6=$(jq_field "$T6_PAY_BODY" '.ok')
if [[ "$T6_PAY_CODE" != "200" || "$OK_T6" != "true" ]]; then
note FAIL "Work stub payment failed: code=$T6_PAY_CODE body=$T6_PAY_BODY"
((FAIL++))
else
START_TS=$(date +%s)
# Poll until complete or timeout
TIMEOUT=30
while :; do
T6_RES=$(curl -s -w "\n%{http_code}" "$BASE/api/jobs/$JOB_ID" || true)
T6_BODY=$(echo "$T6_RES" | head -n-1)
T6_CODE=$(echo "$T6_RES" | tail -n1)
STATE_T6=$(jq_field "$T6_BODY" '.state')
RESULT_T6=$(jq_field "$T6_BODY" '.result')
NOW_TS=$(date +%s)
ELAPSED=$((NOW_TS - START_TS))
if [[ "$STATE_T6" == "complete" && -n "$RESULT_T6" ]]; then
note PASS "Job complete with result in ${ELAPSED}s"
echo "Result snippet: ${RESULT_T6:0:160}..."
((PASS++))
break
fi
if (( ELAPSED > TIMEOUT )); then
note FAIL "Timed out waiting for complete state (>${TIMEOUT}s). Last body: $T6_BODY"
((FAIL++))
break
fi
sleep 2
done
fi
else
note SKIP "Work invoice not available (job may be rejected); skipping Test 6"
((SKIP++))
fi
echo
# Test 7 — Free demo endpoint
echo "=== Test 7 — Demo endpoint ==="
START_DEMO=$(date +%s)
T7_RES=$(curl -s -w "\n%{http_code}" "$BASE/api/demo?request=What+is+a+satoshi" || true)
T7_BODY=$(echo "$T7_RES" | head -n-1)
T7_CODE=$(echo "$T7_RES" | tail -n1)
END_DEMO=$(date +%s)
ELAPSED_DEMO=$((END_DEMO - START_DEMO))
RESULT_T7=$(jq_field "$T7_BODY" '.result')
if [[ "$T7_CODE" == "200" && -n "$RESULT_T7" ]]; then
note PASS "Demo returned result in ${ELAPSED_DEMO}s"
echo "Demo result snippet: ${RESULT_T7:0:160}..."
((PASS++))
else
note FAIL "Unexpected demo response: code=$T7_CODE body=$T7_BODY"
((FAIL++))
fi
echo
# Test 8 — Input validation
echo "=== Test 8 — Input validation ==="
# 8a: Missing request body
T8A_RES=$(curl -s -w "\n%{http_code}" -X POST "$BASE/api/jobs" \
-H "Content-Type: application/json" -d '{}' || true)
T8A_BODY=$(echo "$T8A_RES" | head -n-1)
T8A_CODE=$(echo "$T8A_RES" | tail -n1)
ERR_8A=$(jq_field "$T8A_BODY" '.error')
if [[ "$T8A_CODE" == "400" && -n "$ERR_8A" ]]; then
note PASS "8a: Missing request body correctly rejected (400)"
((PASS++))
else
note FAIL "8a: Unexpected response: code=$T8A_CODE body=$T8A_BODY"
((FAIL++))
fi
# 8b: Unknown job ID
T8B_RES=$(curl -s -w "\n%{http_code}" "$BASE/api/jobs/does-not-exist" || true)
T8B_BODY=$(echo "$T8B_RES" | head -n-1)
T8B_CODE=$(echo "$T8B_RES" | tail -n1)
ERR_8B=$(jq_field "$T8B_BODY" '.error')
if [[ "$T8B_CODE" == "404" && -n "$ERR_8B" ]]; then
note PASS "8b: Unknown job ID correctly returns 404"
((PASS++))
else
note FAIL "8b: Unexpected response: code=$T8B_CODE body=$T8B_BODY"
((FAIL++))
fi
# 8c: Demo without param
T8C_RES=$(curl -s -w "\n%{http_code}" "$BASE/api/demo" || true)
T8C_BODY=$(echo "$T8C_RES" | head -n-1)
T8C_CODE=$(echo "$T8C_RES" | tail -n1)
ERR_8C=$(jq_field "$T8C_BODY" '.error')
if [[ "$T8C_CODE" == "400" && -n "$ERR_8C" ]]; then
note PASS "8c: Missing demo param correctly returns 400"
((PASS++))
else
note FAIL "8c: Unexpected response: code=$T8C_CODE body=$T8C_BODY"
((FAIL++))
fi
echo
# Test 9 — Demo rate limiter
echo "=== Test 9 — Demo rate limiter ==="
RESULT_COUNT=0
ERROR_COUNT=0
LAST_CODE=""
for i in $(seq 1 6); do
RES=$(curl -s -w "\n%{http_code}" "$BASE/api/demo?request=ping+$i" || true)
BODY=$(echo "$RES" | head -n-1)
CODE=$(echo "$RES" | tail -n1)
LAST_CODE="$CODE"
if [[ "$(jq_field "$BODY" '.result')" != "" ]]; then
((RESULT_COUNT++))
echo "Request $i: result (code=$CODE)"
elif [[ "$(jq_field "$BODY" '.error')" != "" ]]; then
((ERROR_COUNT++))
echo "Request $i: error (code=$CODE)"
else
echo "Request $i: unexpected body (code=$CODE): $BODY"
fi
done
if [[ "$RESULT_COUNT" -ge 5 && "$ERROR_COUNT" -ge 1 && "$LAST_CODE" == "429" ]]; then
note PASS "Rate limiter triggered at or after request 6 (HTTP 429)"
((PASS++))
else
note FAIL "Rate limiter behavior unexpected: RESULT_COUNT=$RESULT_COUNT ERROR_COUNT=$ERROR_COUNT LAST_CODE=$LAST_CODE"
((FAIL++))
fi
echo
# Test 10 — Rejection path (adversarial request)
echo "=== Test 10 — Rejection path ==="
T10_RES=$(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"}' || true)
T10_BODY=$(echo "$T10_RES" | head -n-1)
T10_CODE=$(echo "$T10_RES" | tail -n1)
JOB10_ID=$(jq_field "$T10_BODY" '.jobId')
EVAL10_HASH=$(jq_field "$T10_BODY" '.evalInvoice.paymentHash')
if [[ "$T10_CODE" != "201" || -z "$JOB10_ID" ]]; then
note FAIL "Failed to create adversarial job: code=$T10_CODE body=$T10_BODY"
((FAIL++))
else
# Pay eval if possible
if [[ -n "$EVAL10_HASH" ]]; then
curl -s -X POST "$BASE/api/dev/stub/pay/$EVAL10_HASH" >/dev/null || true
fi
sleep 2
T10_POLL=$(curl -s -w "\n%{http_code}" "$BASE/api/jobs/$JOB10_ID" || true)
T10_POLL_BODY=$(echo "$T10_POLL" | head -n-1)
T10_POLL_CODE=$(echo "$T10_POLL" | tail -n1)
STATE_10=$(jq_field "$T10_POLL_BODY" '.state')
REASON_10=$(jq_field "$T10_POLL_BODY" '.reason')
if [[ "$T10_POLL_CODE" == "200" && "$STATE_10" == "rejected" && -n "$REASON_10" ]]; then
note PASS "Adversarial job correctly rejected with reason"
((PASS++))
else
note FAIL "Adversarial job not rejected as expected: code=$T10_POLL_CODE body=$T10_POLL_BODY"
((FAIL++))
fi
fi
echo
echo "=== Summary ==="
echo "PASS=$PASS FAIL=$FAIL SKIP=$SKIP"
```
This assumes your job JSON in dev includes `evalInvoice.paymentHash` and `workInvoice.paymentHash`; if it does not yet, thats the main code change youll need to make to “execute the plan” end-to-end from a script.[1]
Once youve run the script and have terminal output, paste it here and I can convert it into the final human-readable test report table.
Sources
[1] TIMMY_TEST_PLAN.md https://ppl-ai-file-upload.s3.amazonaws.com/web/direct-files/attachments/170804592/8a088fff-b9e4-42fc-a416-d9ea8348897d/TIMMY_TEST_PLAN.md