[claude] add testkit coverage for relay moderation endpoints (#39) (#62)

Co-authored-by: Claude (Opus 4.6) <claude@hermes.local>
Co-committed-by: Claude (Opus 4.6) <claude@hermes.local>
This commit was merged in pull request #62.
This commit is contained in:
2026-03-23 14:51:20 +00:00
committed by rockachopa
parent 572bd79eef
commit c191d556b1

View File

@@ -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 (T41T50)
# ===========================================================================
# 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