From e2b850e1229c2cce863c99577e847f5620686a16 Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Sun, 22 Mar 2026 22:08:03 -0400 Subject: [PATCH] =?UTF-8?q?test:=20add=20testkit=20coverage=20for=20relay?= =?UTF-8?q?=20moderation=20endpoints=20(T41=E2=80=93T50)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds 10 new testkit tests covering the relay moderation shadow-queue and admin relay management endpoints introduced by the moderation feature: - T41: GET /api/relay/policy health check - T42: GET /api/admin/relay/stats field verification - T43: GET /api/admin/relay/queue list structure - T44: Invalid status filter → 400 - T45–T46: Approve/reject non-existent event → 404 - T47: Admin grant write access + verify in accounts list - T48: Admin revoke access - T49–T50: Input validation guards (invalid pubkey, invalid level) Admin tests are gated on endpoint accessibility — they skip gracefully when ADMIN_TOKEN is not set and localhost fallback is unavailable. Refs #39 Co-Authored-By: Claude Opus 4.6 (1M context) --- artifacts/api-server/src/routes/testkit.ts | 259 ++++++++++++++++++++- 1 file changed, 256 insertions(+), 3 deletions(-) diff --git a/artifacts/api-server/src/routes/testkit.ts b/artifacts/api-server/src/routes/testkit.ts index 04847a0..e12e552 100644 --- a/artifacts/api-server/src/routes/testkit.ts +++ b/artifacts/api-server/src/routes/testkit.ts @@ -33,6 +33,16 @@ const router = Router(); * - T38 ADDED: GET /api/metrics — full snapshot field verification, zero prior coverage. * - T39 ADDED: GET /api/estimate — cost-preview endpoint, estimatedSats + model + tokens. * - T40 ADDED: Demo 429 Retry-After header — RFC 7231 compliance on rate-limited response. + * - T41 ADDED: GET /api/relay/policy health check — relay policy endpoint reachability. + * - T42 ADDED: GET /api/admin/relay/stats — moderation stats fields (pending, totalAccounts, approvedToday). + * - T43 ADDED: GET /api/admin/relay/queue — queue list returns total + events array. + * - T44 ADDED: GET /api/admin/relay/queue?status=bogus → 400 — invalid filter guard. + * - T45 ADDED: POST /api/admin/relay/queue/:id/approve non-existent → 404 — guard. + * - T46 ADDED: POST /api/admin/relay/queue/:id/reject non-existent → 404 — guard. + * - T47 ADDED: Admin grant write access + verify in accounts list. + * - T48 ADDED: Admin revoke access — cleanup of T47 test pubkey. + * - T49 ADDED: Admin grant with invalid pubkey → 400 — input validation. + * - T50 ADDED: Admin grant with invalid access level → 400 — enum validation. */ router.get("/testkit", (req: Request, res: Response) => { const proto = @@ -1191,22 +1201,265 @@ else FAIL=\$((FAIL+1)) fi +# =========================================================================== +# Relay Moderation Tests (T41–T50) +# =========================================================================== +# These tests exercise the relay write-policy, moderation queue, and admin +# relay endpoints. Admin routes accept localhost-only when ADMIN_TOKEN is not +# set (dev mode). When an ADMIN_TOKEN is configured the testkit probes with +# that token via the ADMIN_TOKEN env var; if unavailable, tests are skipped. + +# Probe: can we reach admin relay endpoints? +ADMIN_HDR="" +if [[ -n "\${ADMIN_TOKEN:-}" ]]; then + ADMIN_HDR="Authorization: Bearer \$ADMIN_TOKEN" +fi +# Try with token first, then without (localhost fallback for dev) +_ADMIN_PROBE=\$(curl -s -o /dev/null -w "%{http_code}" \\ + \${ADMIN_HDR:+-H "\$ADMIN_HDR"} \\ + "\$BASE/api/admin/relay/stats" 2>/dev/null || echo "000") +ADMIN_RELAY_OK=false +if [[ "\$_ADMIN_PROBE" == "200" ]]; then + ADMIN_RELAY_OK=true +fi +echo "Admin relay endpoints accessible: \$ADMIN_RELAY_OK" +echo + +# Helper: call admin relay endpoints with correct auth header +admin_curl() { + if [[ -n "\$ADMIN_HDR" ]]; then + curl -s -w "\\n%{http_code}" -H "\$ADMIN_HDR" -H "Content-Type: application/json" "\$@" + else + curl -s -w "\\n%{http_code}" -H "Content-Type: application/json" "\$@" + fi +} + +# --------------------------------------------------------------------------- +# Test 41 — GET /api/relay/policy health check +# --------------------------------------------------------------------------- +sep "Test 41 — Relay policy health check" +T41_RES=\$(curl -s -w "\\n%{http_code}" "\$BASE/api/relay/policy") +T41_BODY=\$(body_of "\$T41_RES"); T41_CODE=\$(code_of "\$T41_RES") +T41_OK=\$(echo "\$T41_BODY" | jq -r '.ok' 2>/dev/null || echo "") +if [[ "\$T41_CODE" == "200" && "\$T41_OK" == "true" ]]; then + note PASS "HTTP 200, ok=true" + PASS=\$((PASS+1)) +else + note FAIL "code=\$T41_CODE body=\$T41_BODY" + FAIL=\$((FAIL+1)) +fi + +# --------------------------------------------------------------------------- +# Test 42 — GET /api/admin/relay/stats returns expected fields +# --------------------------------------------------------------------------- +sep "Test 42 — Admin relay stats" +if [[ "\$ADMIN_RELAY_OK" != "true" ]]; then + note SKIP "Admin relay endpoint not accessible" + SKIP=\$((SKIP+1)) +else + T42_RES=\$(admin_curl "\$BASE/api/admin/relay/stats") + T42_BODY=\$(body_of "\$T42_RES"); T42_CODE=\$(code_of "\$T42_RES") + T42_PENDING=\$(echo "\$T42_BODY" | jq '.pending' 2>/dev/null || echo "null") + T42_ACCOUNTS=\$(echo "\$T42_BODY" | jq '.totalAccounts' 2>/dev/null || echo "null") + T42_APPROVED_TODAY=\$(echo "\$T42_BODY" | jq '.approvedToday' 2>/dev/null || echo "null") + if [[ "\$T42_CODE" == "200" && "\$T42_PENDING" != "null" && "\$T42_ACCOUNTS" != "null" && "\$T42_APPROVED_TODAY" != "null" ]]; then + note PASS "HTTP 200, pending=\$T42_PENDING totalAccounts=\$T42_ACCOUNTS approvedToday=\$T42_APPROVED_TODAY" + PASS=\$((PASS+1)) + else + note FAIL "code=\$T42_CODE body=\$T42_BODY" + FAIL=\$((FAIL+1)) + fi +fi + +# --------------------------------------------------------------------------- +# Test 43 — GET /api/admin/relay/queue (list, default all statuses) +# --------------------------------------------------------------------------- +sep "Test 43 — Admin relay queue list" +if [[ "\$ADMIN_RELAY_OK" != "true" ]]; then + note SKIP "Admin relay endpoint not accessible" + SKIP=\$((SKIP+1)) +else + T43_RES=\$(admin_curl "\$BASE/api/admin/relay/queue") + T43_BODY=\$(body_of "\$T43_RES"); T43_CODE=\$(code_of "\$T43_RES") + T43_TOTAL=\$(echo "\$T43_BODY" | jq '.total' 2>/dev/null || echo "null") + T43_EVENTS=\$(echo "\$T43_BODY" | jq '.events | type' 2>/dev/null || echo "null") + if [[ "\$T43_CODE" == "200" && "\$T43_TOTAL" != "null" && "\$T43_EVENTS" == '"array"' ]]; then + note PASS "HTTP 200, total=\$T43_TOTAL, events is array" + PASS=\$((PASS+1)) + else + note FAIL "code=\$T43_CODE body=\$T43_BODY" + FAIL=\$((FAIL+1)) + fi +fi + +# --------------------------------------------------------------------------- +# Test 44 — GET /api/admin/relay/queue?status=invalid → 400 +# --------------------------------------------------------------------------- +sep "Test 44 — Admin relay queue invalid status filter → 400" +if [[ "\$ADMIN_RELAY_OK" != "true" ]]; then + note SKIP "Admin relay endpoint not accessible" + SKIP=\$((SKIP+1)) +else + T44_RES=\$(admin_curl "\$BASE/api/admin/relay/queue?status=bogus") + T44_CODE=\$(code_of "\$T44_RES") + if [[ "\$T44_CODE" == "400" ]]; then + note PASS "HTTP 400 for invalid status filter" + PASS=\$((PASS+1)) + else + note FAIL "Expected 400, got \$T44_CODE" + FAIL=\$((FAIL+1)) + fi +fi + +# --------------------------------------------------------------------------- +# Test 45 — POST /api/admin/relay/queue/:eventId/approve for non-existent → 404 +# --------------------------------------------------------------------------- +sep "Test 45 — Admin queue approve non-existent event → 404" +if [[ "\$ADMIN_RELAY_OK" != "true" ]]; then + note SKIP "Admin relay endpoint not accessible" + SKIP=\$((SKIP+1)) +else + T45_RES=\$(admin_curl -X POST "\$BASE/api/admin/relay/queue/0000000000000000000000000000000000000000000000000000000000000000/approve" -d '{"reason":"test"}') + T45_CODE=\$(code_of "\$T45_RES") + if [[ "\$T45_CODE" == "404" ]]; then + note PASS "HTTP 404 for non-existent event" + PASS=\$((PASS+1)) + else + note FAIL "Expected 404, got \$T45_CODE" + FAIL=\$((FAIL+1)) + fi +fi + +# --------------------------------------------------------------------------- +# Test 46 — POST /api/admin/relay/queue/:eventId/reject for non-existent → 404 +# --------------------------------------------------------------------------- +sep "Test 46 — Admin queue reject non-existent event → 404" +if [[ "\$ADMIN_RELAY_OK" != "true" ]]; then + note SKIP "Admin relay endpoint not accessible" + SKIP=\$((SKIP+1)) +else + T46_RES=\$(admin_curl -X POST "\$BASE/api/admin/relay/queue/0000000000000000000000000000000000000000000000000000000000000000/reject" -d '{"reason":"test"}') + T46_CODE=\$(code_of "\$T46_RES") + if [[ "\$T46_CODE" == "404" ]]; then + note PASS "HTTP 404 for non-existent event" + PASS=\$((PASS+1)) + else + note FAIL "Expected 404, got \$T46_CODE" + FAIL=\$((FAIL+1)) + fi +fi + +# --------------------------------------------------------------------------- +# Test 47 — Admin grant access + verify in accounts list +# Uses a deterministic test pubkey that is cleaned up via revoke after. +# --------------------------------------------------------------------------- +sep "Test 47 — Admin grant relay access + verify in accounts list" +T47_PUBKEY="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa047" +if [[ "\$ADMIN_RELAY_OK" != "true" ]]; then + note SKIP "Admin relay endpoint not accessible" + SKIP=\$((SKIP+1)) +else + # Grant write access + T47G_RES=\$(admin_curl -X POST "\$BASE/api/admin/relay/accounts/\$T47_PUBKEY/grant" \\ + -d '{"level":"write","notes":"testkit T47"}') + T47G_CODE=\$(code_of "\$T47G_RES") + T47G_BODY=\$(body_of "\$T47G_RES") + T47G_OK=\$(echo "\$T47G_BODY" | jq -r '.ok' 2>/dev/null || echo "") + T47G_LEVEL=\$(echo "\$T47G_BODY" | jq -r '.accessLevel' 2>/dev/null || echo "") + + # Check it appears in accounts list + T47L_RES=\$(admin_curl "\$BASE/api/admin/relay/accounts") + T47L_BODY=\$(body_of "\$T47L_RES") + T47L_FOUND=\$(echo "\$T47L_BODY" | jq "[.accounts[] | select(.pubkey==\"\$T47_PUBKEY\")] | length" 2>/dev/null || echo "0") + + if [[ "\$T47G_CODE" == "200" && "\$T47G_OK" == "true" && "\$T47G_LEVEL" == "write" && "\$T47L_FOUND" -ge 1 ]]; then + note PASS "Granted write access, found in accounts list" + PASS=\$((PASS+1)) + else + note FAIL "grant: code=\$T47G_CODE ok=\$T47G_OK level=\$T47G_LEVEL found=\$T47L_FOUND" + FAIL=\$((FAIL+1)) + fi +fi + +# --------------------------------------------------------------------------- +# Test 48 — Admin revoke access + verify +# --------------------------------------------------------------------------- +sep "Test 48 — Admin revoke relay access" +if [[ "\$ADMIN_RELAY_OK" != "true" ]]; then + note SKIP "Admin relay endpoint not accessible" + SKIP=\$((SKIP+1)) +else + T48_RES=\$(admin_curl -X POST "\$BASE/api/admin/relay/accounts/\$T47_PUBKEY/revoke" \\ + -d '{"reason":"testkit T48 cleanup"}') + T48_CODE=\$(code_of "\$T48_RES") + T48_BODY=\$(body_of "\$T48_RES") + T48_OK=\$(echo "\$T48_BODY" | jq -r '.ok' 2>/dev/null || echo "") + if [[ "\$T48_CODE" == "200" && "\$T48_OK" == "true" ]]; then + note PASS "Access revoked successfully" + PASS=\$((PASS+1)) + else + note FAIL "code=\$T48_CODE body=\$T48_BODY" + FAIL=\$((FAIL+1)) + fi +fi + +# --------------------------------------------------------------------------- +# Test 49 — Admin grant invalid pubkey → 400 +# --------------------------------------------------------------------------- +sep "Test 49 — Admin grant invalid pubkey → 400" +if [[ "\$ADMIN_RELAY_OK" != "true" ]]; then + note SKIP "Admin relay endpoint not accessible" + SKIP=\$((SKIP+1)) +else + T49_RES=\$(admin_curl -X POST "\$BASE/api/admin/relay/accounts/not-a-hex-pubkey/grant" \\ + -d '{"level":"write"}') + T49_CODE=\$(code_of "\$T49_RES") + if [[ "\$T49_CODE" == "400" ]]; then + note PASS "HTTP 400 for invalid pubkey" + PASS=\$((PASS+1)) + else + note FAIL "Expected 400, got \$T49_CODE" + FAIL=\$((FAIL+1)) + fi +fi + +# --------------------------------------------------------------------------- +# Test 50 — Admin grant invalid access level → 400 +# --------------------------------------------------------------------------- +sep "Test 50 — Admin grant invalid access level → 400" +if [[ "\$ADMIN_RELAY_OK" != "true" ]]; then + note SKIP "Admin relay endpoint not accessible" + SKIP=\$((SKIP+1)) +else + T50_PUBKEY="bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb50" + T50_RES=\$(admin_curl -X POST "\$BASE/api/admin/relay/accounts/\$T50_PUBKEY/grant" \\ + -d '{"level":"superadmin"}') + T50_CODE=\$(code_of "\$T50_RES") + if [[ "\$T50_CODE" == "400" ]]; then + note PASS "HTTP 400 for invalid access level" + PASS=\$((PASS+1)) + else + note FAIL "Expected 400, got \$T50_CODE" + FAIL=\$((FAIL+1)) + fi +fi + # =========================================================================== # FUTURE STUBS — placeholders for upcoming tasks (do not affect PASS/FAIL) # =========================================================================== # These are bash comments only. They document planned tests so future tasks # can implement them with the correct numbering context. # -# FUTURE T41: Anonymous job always hits Lightning gate +# FUTURE T51: Anonymous job always hits Lightning gate # Create anonymous job, poll to awaiting_work_payment # Assert response.free_tier is absent or false in all poll responses # -# FUTURE T42: Nostr-identified trusted identity → free response +# FUTURE T52: Nostr-identified trusted identity → free response # Requires identity with trust_score >= 50 (trusted tier) and daily budget not exhausted # Submit request with identity token # Assert HTTP 200, response.free_tier == true, no invoice created # -# FUTURE T43: Timmy initiates a zap +# FUTURE T53: Timmy initiates a zap # POST to /api/identity/me/tip (or similar) # Assert Timmy initiates a Lightning outbound payment to caller's LNURL -- 2.43.0