task(#24): Bootstrap route + cost-ledger testkit coverage — 29/29 PASS
Task: Add T23 (bootstrap stub flow) and T24 (cost-ledger completeness) to the
testkit, bringing total from 27/27 to 29/29 PASS with 0 FAIL, 0 SKIP.
## What was changed
- `artifacts/api-server/src/routes/testkit.ts`:
- Updated audit-log comment block to document T23 + T24 additions.
- Inserted Test 23 after T22 (line ~654):
POST /api/bootstrap → assert 201 + bootstrapJobId present.
Guard on stubMode=true; SKIP if real DO mode (prevents hanging).
Stub-pay the paymentHash via /api/dev/stub/pay/:hash.
Poll GET /api/bootstrap/:id every 2s (20s timeout) until
state=provisioning or state=ready; assert message field present.
- Inserted Test 24 after T23:
Guarded on STATE_T6=complete (reuses completed job from T6).
GET /api/jobs/:id, extract costLedger.
Assert all 8 fields non-null: actualInputTokens, actualOutputTokens,
totalTokens, actualCostUsd, actualAmountSats, workAmountSats,
refundAmountSats, refundState.
Honest-accounting invariant: actualAmountSats <= workAmountSats.
refundAmountSats >= 0.
refundState must match ^(not_applicable|pending|paid)$.
## No deviations from task spec
- T23 guard logic matches spec exactly (stubMode check before poll).
- T24 fields match the 8 specified in task-24.md plus the invariants.
- No changes to bootstrap.ts or jobs.ts — existing routes already correct.
## Test run result
29/29 PASS, 0 FAIL, 0 SKIP (fresh server restart, rate-limit slots clean).
T23: state=provisioning in 1s. T24: actualAmountSats(179)<=workAmountSats(182),
refundAmountSats=3, refundState=pending.
This commit is contained in:
@@ -23,6 +23,10 @@ const router = Router();
|
||||
* - T20 ADDED: POST /api/jobs/:id/refund guards — financial endpoint, zero prior coverage.
|
||||
* - T21 ADDED: GET /api/jobs/:id/stream SSE replay on completed job — never tested.
|
||||
* - T22 ADDED: GET /api/sessions/:id unknown ID → 404 — never tested.
|
||||
* - T23 ADDED: POST /api/bootstrap stub flow — highest-value endpoint, zero prior coverage.
|
||||
* 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.
|
||||
*/
|
||||
router.get("/testkit", (req: Request, res: Response) => {
|
||||
const proto =
|
||||
@@ -644,6 +648,97 @@ else
|
||||
FAIL=\$((FAIL+1))
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Test 23 — Bootstrap: create → stub-pay → poll provisioning state
|
||||
# Highest-value paid feature (10,000 sats default) — previously zero coverage.
|
||||
# Guarded on stubMode=true: real DO provisioning requires DO_API_TOKEN (out of scope).
|
||||
# Polls GET /api/bootstrap/:id until state=provisioning or state=ready (20 s timeout).
|
||||
# ---------------------------------------------------------------------------
|
||||
sep "Test 23 — Bootstrap: create + stub-pay + poll provisioning"
|
||||
T23_RES=\$(curl -s -w "\\n%{http_code}" -X POST "\$BASE/api/bootstrap" \\
|
||||
-H "Content-Type: application/json")
|
||||
T23_BODY=\$(body_of "\$T23_RES"); T23_CODE=\$(code_of "\$T23_RES")
|
||||
BOOTSTRAP_ID=\$(echo "\$T23_BODY" | jq -r '.bootstrapJobId' 2>/dev/null || echo "")
|
||||
T23_STUB=\$(echo "\$T23_BODY" | jq -r '.stubMode' 2>/dev/null || echo "false")
|
||||
BOOTSTRAP_HASH=\$(echo "\$T23_BODY" | jq -r '.invoice.paymentHash' 2>/dev/null || echo "")
|
||||
if [[ "\$T23_CODE" != "201" || -z "\$BOOTSTRAP_ID" || "\$BOOTSTRAP_ID" == "null" ]]; then
|
||||
note FAIL "Bootstrap create failed: code=\$T23_CODE body=\$T23_BODY"
|
||||
FAIL=\$((FAIL+1))
|
||||
elif [[ "\$T23_STUB" != "true" ]]; then
|
||||
note SKIP "stubMode=false — skipping (requires DO_API_TOKEN for real provisioning)"
|
||||
SKIP=\$((SKIP+1))
|
||||
else
|
||||
if [[ -n "\$BOOTSTRAP_HASH" && "\$BOOTSTRAP_HASH" != "null" ]]; then
|
||||
curl -s -X POST "\$BASE/api/dev/stub/pay/\$BOOTSTRAP_HASH" >/dev/null
|
||||
fi
|
||||
START_T23=\$(date +%s); T23_TIMEOUT=20
|
||||
T23_STATE=""; T23_MSG=""; T23_POLL_CODE=""
|
||||
while :; do
|
||||
T23_POLL=\$(curl -s -w "\\n%{http_code}" "\$BASE/api/bootstrap/\$BOOTSTRAP_ID")
|
||||
T23_POLL_BODY=\$(body_of "\$T23_POLL"); T23_POLL_CODE=\$(code_of "\$T23_POLL")
|
||||
T23_STATE=\$(echo "\$T23_POLL_BODY" | jq -r '.state' 2>/dev/null || echo "")
|
||||
T23_MSG=\$(echo "\$T23_POLL_BODY" | jq -r '.message' 2>/dev/null || echo "")
|
||||
NOW_T23=\$(date +%s); ELAPSED_T23=\$((NOW_T23 - START_T23))
|
||||
if [[ "\$T23_STATE" == "provisioning" || "\$T23_STATE" == "ready" ]]; then break; fi
|
||||
if (( ELAPSED_T23 > T23_TIMEOUT )); then break; fi
|
||||
sleep 2
|
||||
done
|
||||
if [[ "\$T23_POLL_CODE" == "200" \\
|
||||
&& ("\$T23_STATE" == "provisioning" || "\$T23_STATE" == "ready") \\
|
||||
&& -n "\$T23_MSG" && "\$T23_MSG" != "null" ]]; then
|
||||
note PASS "state=\$T23_STATE in \$ELAPSED_T23 s, message present, bootstrapJobId=\$BOOTSTRAP_ID"
|
||||
PASS=\$((PASS+1))
|
||||
else
|
||||
note FAIL "code=\$T23_POLL_CODE state=\$T23_STATE elapsed=\${ELAPSED_T23}s body=\$T23_POLL_BODY"
|
||||
FAIL=\$((FAIL+1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Test 24 — Cost ledger completeness after job completion
|
||||
# Uses the completed JOB_ID from T6 (guarded on STATE_T6=complete).
|
||||
# Verifies 8 cost-ledger fields are non-null:
|
||||
# actualInputTokens, actualOutputTokens, totalTokens, actualCostUsd,
|
||||
# actualAmountSats, workAmountSats, refundAmountSats, refundState.
|
||||
# Honest-accounting invariant: actualAmountSats <= workAmountSats.
|
||||
# refundState must be one of: not_applicable | pending | paid.
|
||||
# ---------------------------------------------------------------------------
|
||||
sep "Test 24 — Cost ledger completeness (job completed)"
|
||||
if [[ -n "\$JOB_ID" && "\$STATE_T6" == "complete" ]]; then
|
||||
T24_RES=\$(curl -s -w "\\n%{http_code}" "\$BASE/api/jobs/\$JOB_ID")
|
||||
T24_BODY=\$(body_of "\$T24_RES"); T24_CODE=\$(code_of "\$T24_RES")
|
||||
T24_LEDGER=\$(echo "\$T24_BODY" | jq '.costLedger' 2>/dev/null || echo "null")
|
||||
T24_REFUND_STATE=\$(echo "\$T24_LEDGER" | jq -r '.refundState' 2>/dev/null || echo "")
|
||||
T24_FIELDS_OK=true
|
||||
for field in actualInputTokens actualOutputTokens totalTokens actualCostUsd actualAmountSats workAmountSats refundAmountSats refundState; do
|
||||
VAL=\$(echo "\$T24_LEDGER" | jq -r ".\$field" 2>/dev/null || echo "MISSING")
|
||||
[[ -z "\$VAL" || "\$VAL" == "null" ]] && T24_FIELDS_OK=false || true
|
||||
done
|
||||
T24_INV_OK=\$(echo "\$T24_LEDGER" | jq '
|
||||
(.actualAmountSats != null) and
|
||||
(.workAmountSats != null) and
|
||||
(.actualAmountSats <= .workAmountSats) and
|
||||
(.refundAmountSats >= 0)
|
||||
' 2>/dev/null || echo "false")
|
||||
if [[ "\$T24_CODE" == "200" \\
|
||||
&& "\$T24_LEDGER" != "null" \\
|
||||
&& "\$T24_FIELDS_OK" == "true" \\
|
||||
&& "\$T24_INV_OK" == "true" \\
|
||||
&& "\$T24_REFUND_STATE" =~ ^(not_applicable|pending|paid)\$ ]]; then
|
||||
T24_ACTUAL=\$(echo "\$T24_LEDGER" | jq -r '.actualAmountSats' 2>/dev/null || echo "?")
|
||||
T24_WORK=\$(echo "\$T24_LEDGER" | jq -r '.workAmountSats' 2>/dev/null || echo "?")
|
||||
T24_REFUND=\$(echo "\$T24_LEDGER" | jq -r '.refundAmountSats' 2>/dev/null || echo "?")
|
||||
note PASS "8 fields non-null, actualAmountSats(\$T24_ACTUAL)<=workAmountSats(\$T24_WORK), refundAmountSats=\$T24_REFUND, refundState=\$T24_REFUND_STATE"
|
||||
PASS=\$((PASS+1))
|
||||
else
|
||||
note FAIL "code=\$T24_CODE ledger=\$T24_LEDGER fields_ok=\$T24_FIELDS_OK inv_ok=\$T24_INV_OK refundState=\$T24_REFUND_STATE"
|
||||
FAIL=\$((FAIL+1))
|
||||
fi
|
||||
else
|
||||
note SKIP "Skipping — job not complete (STATE_T6=\${STATE_T6:-not_set}) or no JOB_ID"
|
||||
SKIP=\$((SKIP+1))
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Summary
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user