feat: testkit T37-T39 for Timmy economic peer + fix mobile typecheck (Refs #45)

Add testkit coverage for the Timmy economic peer endpoints (issue #45):
- T37: GET /api/identity/timmy — npub, pubkeyHex, zapCount shape assertions
- T38/T39: POST /api/identity/vouch auth guards (no token / invalid token → 401)
- Update TIMMY_TEST_PLAN.md with new test table rows
- Renumber former FUTURE T37-T40 stubs to T40-T43

Fix pre-existing mobile typecheck error: move slideStyles declaration before
the slides array in artifacts/mobile/app/onboarding.tsx to resolve TS2448/2454.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Alexander Whitestone
2026-03-23 16:19:23 -04:00
parent 113095d2f0
commit 9aaf5b3ab7
3 changed files with 84 additions and 19 deletions

View File

@@ -74,6 +74,14 @@ Requirements: `curl`, `bash`, `jq` — nothing else.
| 35 | GET sessions/:id includes tier | `GET /api/sessions/:id` → HTTP 200, `trust_tier == "anonymous"` |
| 36 | Full challenge→sign→verify | Inline node script: generate keypair, challenge, sign kind=27235 event, verify → token; GET /identity/me → tier=new, pubkey matches |
### Timmy economic peer — identity card + vouching guards (tests 3739)
| # | Name | What it checks |
|---|------|----------------|
| 37 | Timmy identity card | `GET /api/identity/timmy` → HTTP 200, `npub` starts with `npub1`, `pubkeyHex` is 64 hex chars, `zapCount` is a non-negative integer |
| 38 | Vouch: no token | `POST /api/identity/vouch` without `X-Nostr-Token` → HTTP 401 |
| 39 | Vouch: invalid token | `POST /api/identity/vouch` with `X-Nostr-Token: totally.invalid.token` → HTTP 401 |
---
## Architecture notes for reviewers

View File

@@ -29,6 +29,9 @@ const router = Router();
* Guarded on stubMode=true; polls until state=provisioning|ready (20 s timeout).
* - T24 ADDED: costLedger completeness after job completion — 8 fields, honest-accounting
* invariant (actualAmountSats ≤ workAmountSats), refundState enum check.
* - T37 ADDED: GET /api/identity/timmy — Timmy's npub + zap count (issue #45).
* - T38 ADDED: POST /api/identity/vouch no token → 401 guard.
* - T39 ADDED: POST /api/identity/vouch invalid token → 401 guard.
*/
router.get("/testkit", (req: Request, res: Response) => {
const proto =
@@ -1092,29 +1095,83 @@ NODESCRIPT
fi
fi
# ---------------------------------------------------------------------------
# Test 37 — GET /api/identity/timmy returns Timmy's npub and zap count
# ---------------------------------------------------------------------------
sep "Test 37 — GET /api/identity/timmy"
T37_RES=\$(curl -s -w "\n%{http_code}" "\$BASE/api/identity/timmy")
T37_BODY=\$(body_of "\$T37_RES"); T37_CODE=\$(code_of "\$T37_RES")
T37_NPUB=\$(echo "\$T37_BODY" | jq -r '.npub' 2>/dev/null || echo "")
T37_HEX=\$(echo "\$T37_BODY" | jq -r '.pubkeyHex' 2>/dev/null || echo "")
T37_ZAPS=\$(echo "\$T37_BODY" | jq '.zapCount' 2>/dev/null || echo "")
if [[ "\$T37_CODE" == "200" \\
&& "\$T37_NPUB" =~ ^npub1 \\
&& \${#T37_HEX} -eq 64 \\
&& "\$T37_ZAPS" =~ ^[0-9]+\$ ]]; then
note PASS "HTTP 200, npub=\${T37_NPUB:0:12}… pubkeyHex=\${T37_HEX:0:8}… zapCount=\$T37_ZAPS"
PASS=\$((PASS+1))
else
note FAIL "code=\$T37_CODE npub=\$T37_NPUB hex_len=\${#T37_HEX} zapCount=\$T37_ZAPS"
FAIL=\$((FAIL+1))
fi
# ---------------------------------------------------------------------------
# Test 38 — POST /api/identity/vouch with no token → 401
# ---------------------------------------------------------------------------
sep "Test 38 — POST /api/identity/vouch no token → 401"
T38_RES=\$(curl -s -w "\n%{http_code}" -X POST "\$BASE/api/identity/vouch" \\
-H "Content-Type: application/json" \\
-d '{"voucheePubkey":"aabbccdd00112233aabbccdd00112233aabbccdd00112233aabbccdd00112233"}')
T38_BODY=\$(body_of "\$T38_RES"); T38_CODE=\$(code_of "\$T38_RES")
if [[ "\$T38_CODE" == "401" ]]; then
note PASS "HTTP 401 as expected (no X-Nostr-Token)"
PASS=\$((PASS+1))
else
note FAIL "code=\$T38_CODE body=\$T38_BODY"
FAIL=\$((FAIL+1))
fi
# ---------------------------------------------------------------------------
# Test 39 — POST /api/identity/vouch with invalid token → 401
# ---------------------------------------------------------------------------
sep "Test 39 — POST /api/identity/vouch invalid token → 401"
T39_RES=\$(curl -s -w "\n%{http_code}" -X POST "\$BASE/api/identity/vouch" \\
-H "Content-Type: application/json" \\
-H "X-Nostr-Token: totally.invalid.token" \\
-d '{"voucheePubkey":"aabbccdd00112233aabbccdd00112233aabbccdd00112233aabbccdd00112233"}')
T39_BODY=\$(body_of "\$T39_RES"); T39_CODE=\$(code_of "\$T39_RES")
if [[ "\$T39_CODE" == "401" ]]; then
note PASS "HTTP 401 as expected (invalid token)"
PASS=\$((PASS+1))
else
note FAIL "code=\$T39_CODE body=\$T39_BODY"
FAIL=\$((FAIL+1))
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 T37: GET /api/estimate returns cost preview
# FUTURE T40: 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: Anonymous job always hits Lightning gate
# FUTURE T41: 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: Nostr-identified trusted identity → free response
# FUTURE T42: 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: Timmy initiates a zap
# POST to /api/identity/me/tip (or similar)
# Assert Timmy initiates a Lightning outbound payment to caller's LNURL
# FUTURE T43: Elite vouch grants trust boost
# Requires two identities: voucher at elite tier (score >= 200), fresh vouchee
# POST /api/identity/vouch with voucher's token + signed event tagging vouchee
# Assert HTTP 200, newScore = oldScore + 20, newTier reflects boost
# ---------------------------------------------------------------------------
# Summary

View File

@@ -21,6 +21,19 @@ import { ONBOARDING_COMPLETED_KEY } from "@/constants/storage-keys";
const C = Colors.dark;
const { width: SCREEN_WIDTH } = Dimensions.get("window");
const slideStyles = StyleSheet.create({
iconCircle: {
width: 140,
height: 140,
borderRadius: 70,
backgroundColor: C.surfaceElevated,
borderWidth: 1,
borderColor: C.border,
alignItems: "center",
justifyContent: "center",
},
});
type Slide = {
id: string;
icon: React.ReactNode;
@@ -158,19 +171,6 @@ export default function OnboardingScreen() {
);
}
const slideStyles = StyleSheet.create({
iconCircle: {
width: 140,
height: 140,
borderRadius: 70,
backgroundColor: C.surfaceElevated,
borderWidth: 1,
borderColor: C.border,
alignItems: "center",
justifyContent: "center",
},
});
const styles = StyleSheet.create({
container: {
flex: 1,