step 10.7: done

This commit is contained in:
bepis
2026-10-23 00:07:22 +11:00
parent 9a31c7c460
commit add65e9e5b
5 changed files with 288 additions and 21 deletions
+19 -15
View File
@@ -4,8 +4,8 @@
> If this file disagrees with the code, trust the code and fix this file.
## 1. Where we are
- **Last completed step:** 10.6 — Restore REAL rate limiting (re-enabled login/register limiters, moved global /api limiter before auth routes, 4 integration tests) — TICKED in STATUS.md
- **Next step:** Phase 10.7 — Error handling gaps: malformed JSON → 400, body over limit → 413, unknown /api/* → 404 JSON, fix throw in session regenerate callback
- **Last completed step:** 10.7 — Error handling gaps: malformed JSON → 400 INVALID_JSON, body over limit → 413 PAYLOAD_TOO_LARGE, unknown /api/* → 404 JSON, session regenerate throw → next(err) — TICKED in STATUS.md
- **Next step:** Phase 10.8 — POST /api/character/allocate (stat point allocation)
- **Updated:** 2026-10-22
- **Was the last session cut off mid-task?** No
@@ -14,16 +14,18 @@
|---|---|
| `npm install` | succeeds |
| `npm run migrate` | applies 001–006 / "nothing to do" |
| `npm test` | **197 passing, 0 failing** (85 unit + 112 integration incl. 4 rate-limiting tests) |
| `npm test` | **202 passing, 0 failing** (85 unit + 112 integration incl. 4 rate-limiting + 5 error-handling tests) |
| `npm run seed:dev` | creates demo user "demo"/"demo123", hero "Aldric" (warrior), starter gear |
| `npm start` + `curl localhost:3000/` | 200 HTML landing page |
| `npm start` + `curl localhost:3000/api/health` | `{"ok":true}` |
| `curl -X POST localhost:3000/api/auth/login -H 'Content-Type: application/json' -d '{bad'` | 400 `{"ok":false,"error":{"code":"INVALID_JSON",...}}` |
| `curl -X POST localhost:3000/api/auth/login -H 'Content-Type: application/json' -d '{"data":"x"' --data-binary @<(python3 -c "print('x'*12000)")` | 413 `{"ok":false,"error":{"code":"PAYLOAD_TOO_LARGE",...}}` |
| `curl localhost:3000/api/nonexistent` | 404 `{"ok":false,"error":{"code":"NOT_FOUND",...}}` |
## 3. What exists now (files that matter)
- `src/routes/auth.js` — Rate limiters RE-ENABLED (login: 100/15min, register: 100/1hr; 100 in test env). Added X-RateLimit-* headers to responses.
- `src/app.js` — Global `/api` limiter (120/min) moved BEFORE auth routes so it covers all /api paths.
- `tests/integration/rate-limiting.test.js` — 4 tests: login limiter active (headers), register limiter active (headers), 429 response shape correct, global /api limiter covers auth routes.
- All other files from step 10.5 unchanged (landing page, leaderboard stub, link-checker, 404/500 pages).
- `src/app.js` — JSON parser error handling added: SyntaxError (400 INVALID_JSON), PayloadTooLargeError (413 PAYLOAD_TOO_LARGE). Catch-all for /api/* paths always returns JSON.
- `src/routes/auth.js` — Session regenerate/destroy callbacks use `next(err)` instead of `throw`.
- `tests/integration/step-10.7.test.js` — 5 tests: malformed JSON on login, malformed JSON on character, body over limit, unknown /api/*, session regenerate error.
## 4. Half-finished / fragile things
- No leaderboard UI yet (stub page exists; real UI in 10.17).
@@ -34,8 +36,7 @@
- Starter gear documented but not auto-created on character creation.
- `GET /api/zones` documented in API.md but not implemented (Issue #2).
- Mobile nav uses emoji icons (⚔️ 🏕️ 🎒) — verify they render acceptably on all platforms.
- **10.7 next:** Error handling gaps — malformed JSON → 400, body over limit → 413, unknown /api/* → 404 JSON, fix `throw` inside `req.session.regenerate` callback.
- **10.17 next (after 10.6–10.16):** Real leaderboard UI; remove stub; update link-checker exception.
- **10.8 next:** POST /api/character/allocate — stat point allocation endpoint.
## 5. Decisions & deviations from the docs
- D-004..D-007 in `docs/DECISIONS.md` (combat rounds, equipment counted once, session store, clock).
@@ -83,11 +84,14 @@
- **10.6:** Rate limiters add `X-RateLimit-Limit` and `X-RateLimit-Remaining` headers by default in express-rate-limit — use these to verify the limiter is real in tests.
- **10.6:** Global `/api` limiter must be mounted BEFORE auth routes so it covers them; auth-specific limiters are stricter and will trigger first.
- **10.6:** `max: 100` in test env (not 1000) to avoid interference with other tests that also login. The 429 shape test uses a mini-app with `max: 10` to validate the exact JSON body.
- **10.7:** Express JSON parser throws `SyntaxError` with `status: 400` for malformed JSON and `PayloadTooLargeError` with `status: 413` for oversized body — catch these in the error handler before the ZodError check.
- **10.7:** `req.path.startsWith('/api/')` is more reliable than `req.accepts('html')` for deciding JSON vs HTML on unknown paths — Node.js fetch defaults to accepting HTML.
- **10.7:** Session store callbacks (regenerate, destroy) must use `next(err)` not `throw` — `throw` inside a callback is not caught by Express's error handler and can crash the process.
## 7. Next-step briefing
- **Next step: 10.7** — Error handling gaps:
- Malformed JSON body → 400 `INVALID_JSON`
- Body over 10kb limit → 413 `PAYLOAD_TOO_LARGE`
- Unknown `/api/*` path → 404 JSON `{ok:false,...}` (not HTML)
- Replace `throw` inside `req.session.regenerate((err) => { throw ... })` with `next(err)` so a store error can't crash the process
- Add tests for each of the four behaviors
- **Next step: 10.8** — Add `POST /api/character/allocate` endpoint:
- Body: `{str,agi,int,vit}` as points to add (zod `.strict()`, non-negative integers)
- Use existing `allocatePoints` from `src/game/leveling.js`
- One transaction; reject if total exceeds `unspent_points` (422 `NOT_ENOUGH_POINTS`)
- Return `{character, derived}`
- Tests: spend partial points, overspend → 422, negative/float/unknown keys → 400, cross-user rejection