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:
@@ -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 37–39)
|
||||
|
||||
| # | 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user