task/35: Testkit T25–T36 — Nostr identity + trust engine coverage (v2)

## 12 new tests (T25–T36) + stubs (T37–T40) added to testkit.ts

T25 — POST /identity/challenge: HTTP 200, nonce=64-char hex, expiresAt=ISO AND in future
      (future check: lexicographic ISO 8601 string comparison via `date -u`)
T26 — POST /identity/verify {}: HTTP 400, non-empty error
T27 — POST /identity/verify fake nonce: HTTP 401, error contains "Nonce not found"
T28 — GET /identity/me no header: HTTP 401, error contains "Missing"
T29 — GET /identity/me invalid token: HTTP 401 (Invalid/expired wording)
T30 — POST /sessions bad X-Nostr-Token: HTTP 401, no sessionId created
      (null-safe: jq returns literal "null" for absent fields — now treated as absent)
T31 — POST /jobs bad X-Nostr-Token: HTTP 401, "Invalid or expired"
T32 — POST /sessions anonymous: HTTP 201, trust_tier="anonymous", sessionId != "null"
T33 — POST /jobs anonymous: HTTP 201, trust_tier="anonymous", jobId != "null"
T34 — GET /jobs/:id (using T33_JOB_ID): HTTP 200, trust_tier non-null and "anonymous"
T35 — GET /sessions/:id (using T32_SESSION_ID): HTTP 200, trust_tier="anonymous"
T36 — Full challenge→sign→verify E2E: inline node CJS script; guarded under `set -e`:
      uses `T36_OUT=$(node ...) || T36_EXIT=$?` pattern to never abort the suite.
      tier=new, interactionCount=0, pubkey matches /identity/me — SKIP on node failure.

## Bug fixes vs original (code review REJECTED → v2):
- T36: `set -e` guard: was `T36_OUT=$(node ...); T36_EXIT=$?` — would abort suite
        on node exit-code != 0. Fixed: `T36_OUT="" T36_EXIT=0; ... || T36_EXIT=$?`
- T30: was `-z "$T30_SESSION"` — jq returns "null" not "" for missing fields.
        Fixed: `( -z "$T30_SESSION" || "$T30_SESSION" == "null" )`
- T25: missing future-time assertion. Added lexicographic ISO comparison:
        `date -u +"%Y-%m-%dT%H:%M:%SZ"` vs `expiresAt`
- FUTURE stubs: changed `# FUTURE T37 (Task...)` → `# FUTURE T37: ...` (greppable)
- T32/T33: added `!= "null"` guards on sessionId/jobId to reject jq "null" as absent

## TIMMY_TEST_PLAN.md: added "Nostr identity + trust engine (tests 25–36)" table

## TypeScript: 0 errors. All 12 tests smoke-tested individually against localhost:8080.
This commit is contained in:
alexpaynex
2026-03-19 21:14:01 +00:00
parent c7bb5de5e6
commit 6b6aa83e80

View File

@@ -757,14 +757,17 @@ T25_RES=\$(curl -s -w "\\n%{http_code}" -X POST "\$BASE/api/identity/challenge")
T25_BODY=\$(body_of "\$T25_RES"); T25_CODE=\$(code_of "\$T25_RES")
T25_NONCE=\$(echo "\$T25_BODY" | jq -r '.nonce' 2>/dev/null || echo "")
T25_EXP=\$(echo "\$T25_BODY" | jq -r '.expiresAt' 2>/dev/null || echo "")
T25_NONCE_OK=false; T25_EXP_OK=false
T25_NONCE_OK=false; T25_EXP_OK=false; T25_EXP_FUTURE=false
[[ "\$T25_NONCE" =~ ^[0-9a-f]{64}\$ ]] && T25_NONCE_OK=true || true
[[ "\$T25_EXP" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T ]] && T25_EXP_OK=true || true
if [[ "\$T25_CODE" == "200" && "\$T25_NONCE_OK" == "true" && "\$T25_EXP_OK" == "true" ]]; then
note PASS "HTTP 200, nonce=64-char-hex, expiresAt=ISO"
# Verify expiresAt is actually in the future (ISO 8601 sorts lexicographically)
T25_NOW=\$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "")
[[ -n "\$T25_NOW" && "\$T25_EXP" > "\$T25_NOW" ]] && T25_EXP_FUTURE=true || true
if [[ "\$T25_CODE" == "200" && "\$T25_NONCE_OK" == "true" && "\$T25_EXP_OK" == "true" && "\$T25_EXP_FUTURE" == "true" ]]; then
note PASS "HTTP 200, nonce=64-char-hex, expiresAt=ISO and in future (\$T25_EXP)"
PASS=\$((PASS+1))
else
note FAIL "code=\$T25_CODE nonce='\$T25_NONCE' expiresAt='\$T25_EXP'"
note FAIL "code=\$T25_CODE nonce_ok=\$T25_NONCE_OK exp_ok=\$T25_EXP_OK exp_future=\$T25_EXP_FUTURE exp='\$T25_EXP'"
FAIL=\$((FAIL+1))
fi
@@ -844,7 +847,8 @@ T30_RES=\$(curl -s -w "\\n%{http_code}" -X POST "\$BASE/api/sessions" \\
T30_BODY=\$(body_of "\$T30_RES"); T30_CODE=\$(code_of "\$T30_RES")
T30_ERR=\$(echo "\$T30_BODY" | jq -r '.error' 2>/dev/null || echo "")
T30_SESSION=\$(echo "\$T30_BODY" | jq -r '.sessionId' 2>/dev/null || echo "")
if [[ "\$T30_CODE" == "401" && "\$T30_ERR" == *"Invalid or expired"* && -z "\$T30_SESSION" ]]; then
# jq returns literal "null" for absent fields; treat both empty and "null" as absent
if [[ "\$T30_CODE" == "401" && "\$T30_ERR" == *"Invalid or expired"* && ( -z "\$T30_SESSION" || "\$T30_SESSION" == "null" ) ]]; then
note PASS "HTTP 401, error contains 'Invalid or expired', no sessionId created"
PASS=\$((PASS+1))
else
@@ -881,7 +885,7 @@ T32_RES=\$(curl -s -w "\\n%{http_code}" -X POST "\$BASE/api/sessions" \\
T32_BODY=\$(body_of "\$T32_RES"); T32_CODE=\$(code_of "\$T32_RES")
T32_SESSION_ID=\$(echo "\$T32_BODY" | jq -r '.sessionId' 2>/dev/null || echo "")
T32_TIER=\$(echo "\$T32_BODY" | jq -r '.trust_tier' 2>/dev/null || echo "")
if [[ "\$T32_CODE" == "201" && "\$T32_TIER" == "anonymous" && -n "\$T32_SESSION_ID" ]]; then
if [[ "\$T32_CODE" == "201" && "\$T32_TIER" == "anonymous" && -n "\$T32_SESSION_ID" && "\$T32_SESSION_ID" != "null" ]]; then
note PASS "HTTP 201, trust_tier=anonymous, sessionId=\${T32_SESSION_ID:0:8}..."
PASS=\$((PASS+1))
else
@@ -900,7 +904,7 @@ T33_RES=\$(curl -s -w "\\n%{http_code}" -X POST "\$BASE/api/jobs" \\
T33_BODY=\$(body_of "\$T33_RES"); T33_CODE=\$(code_of "\$T33_RES")
T33_JOB_ID=\$(echo "\$T33_BODY" | jq -r '.jobId' 2>/dev/null || echo "")
T33_TIER=\$(echo "\$T33_BODY" | jq -r '.trust_tier' 2>/dev/null || echo "")
if [[ "\$T33_CODE" == "201" && "\$T33_TIER" == "anonymous" && -n "\$T33_JOB_ID" ]]; then
if [[ "\$T33_CODE" == "201" && "\$T33_TIER" == "anonymous" && -n "\$T33_JOB_ID" && "\$T33_JOB_ID" != "null" ]]; then
note PASS "HTTP 201, trust_tier=anonymous, jobId=\${T33_JOB_ID:0:8}..."
PASS=\$((PASS+1))
else
@@ -1029,8 +1033,10 @@ async function main() {
main().catch(err => { process.stderr.write(String(err) + '\n'); process.exit(1); });
NODESCRIPT
T36_OUT=\$(node "\$T36_TMPFILE" "\$BASE" 2>/dev/null)
T36_EXIT=\$?
# Capture output and exit code safely under set -e.
# "|| T36_EXIT=$?" prevents errexit if node exits non-zero.
T36_OUT="" T36_EXIT=0
T36_OUT=\$(node "\$T36_TMPFILE" "\$BASE" 2>/dev/null) || T36_EXIT=\$?
rm -f "\$T36_TMPFILE"
if [[ \$T36_EXIT -ne 0 || -z "\$T36_OUT" ]]; then
@@ -1059,21 +1065,21 @@ fi
# These are bash comments only. They document planned tests so future tasks
# can implement them with the correct numbering context.
#
# FUTURE T37 (Task #27 — cost routing): GET /api/estimate returns cost preview
# FUTURE T37: GET /api/estimate returns cost preview
# GET \$BASE/api/estimate?request=<text>
# Assert HTTP 200, estimatedSats is a positive integer
# Assert model, inputTokens, outputTokens are present
#
# FUTURE T38 (Task #27 — cost routing): Anonymous job always hits Lightning gate
# FUTURE T38: 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 T39 (Task #27 — free tier): Nostr-identified trusted identity → free response
# FUTURE T39: 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 T40 (Task #29 — Timmy as peer): Timmy initiates a zap
# FUTURE T40: Timmy initiates a zap
# POST to /api/identity/me/tip (or similar)
# Assert Timmy initiates a Lightning outbound payment to caller's LNURL