Registration: replace date-of-birth field with a simple 18+ confirmation

The register form collected and validated an actual date of birth just to
throw it away (backend never stored it beyond a nullable column). Per the
roadmap note: don't collect/store DOB right now, just get a client-side
"I am 18 or older" confirmation. RegisterRequest.date_of_birth (str) is now
over_18 (bool); the endpoint rejects over_18=false with 400 same as before.
calculate_age() is kept (unused by /register now) since real DOB collection
is coming back in a future pass and deleting it broke test_auth_age_gate.py's
import for no benefit.

Frontend: DOBEdit LineEdit replaced with an Over18Check CheckBox; api_client.gd's
register() now takes a bool instead of a date string. All Godot callers
(smoke_test_auth/e2e/world_list/main_menu_ui/main_menu_full_flow) and backend
test files updated to match. Full suite verified against a live backend:
61/61 pytest, 6/6 Godot smoke tests (33 individual assertions), all passing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M6rUEynmv2QmweocAQYARN
This commit is contained in:
claude-checkpoint-tooling
2026-08-20 16:25:27 +00:00
parent 4ec71b87b9
commit 0ecc16d963
14 changed files with 122 additions and 125 deletions
+4 -4
View File
@@ -20,13 +20,13 @@ covered by the smoke tests below so they can't silently regress.
Run: `cd backend && python -m pytest -v` (needs `DATABASE_URL` pointed at a
reachable Postgres — see the runner script).
**Current total: 68 tests, all passing on a clean `main` checkout.**
**Current total: 61 tests, all passing on a clean `main` checkout.**
| File | Tests | What it covers |
|---|---|---|
| `test_auth_age_gate.py` | 17 | `calculate_age()` date-math correctness (exact boundary, leap years, birthday-not-yet-this-year) + `RegisterRequest` date-of-birth validation (parametrized valid/invalid date rejection). Backs the 18+ registration gate (PR #23). |
| `test_auth_age_gate.py` | 11 | `calculate_age()` date-math correctness (exact boundary, leap years, birthday-not-yet-this-year — kept as a tested utility for a future real-DOB pass even though `/register` no longer calls it) + `RegisterRequest.over_18` field tests (true accepted, false accepted, missing rejected). 2026-08-20: the register flow was simplified from collecting/validating a date of birth to a plain client-side "I am 18 or older" confirmation, so the old date-validation parametrized cases were replaced. Backs the 18+ registration gate (PR #23, revised PC1). |
| `test_gltf_metadata.py` | 21 | glTF/GLB binary + JSON parsing: magic-byte detection, chunk-padding handling, mesh/animation/texture-reference metadata extraction, malformed-input rejection. Backs the asset-import pipeline (PR #27). |
| `test_auth_endpoints.py` | 11 | **Integration tests** for `POST /api/auth/register` and `POST /api/auth/login` against the real FastAPI app + real Postgres. Covers: successful register (201), duplicate username (409), duplicate email (409), underage rejection (400), invalid date format (422), exact-18 registration, successful login with correct credentials (200), wrong password (401), nonexistent user (401), form-encoded login (OAuth2), and JWT validity of returned tokens. |
| `test_auth_endpoints.py` | 10 | **Integration tests** for `POST /api/auth/register` and `POST /api/auth/login` against the real FastAPI app + real Postgres. Covers: successful register (201), duplicate username (409), duplicate email (409), `over_18=false` rejection (400), missing `over_18` field (422), successful login with correct credentials (200), wrong password (401), nonexistent user (401), form-encoded login (OAuth2), and JWT validity of returned tokens. |
| `test_refresh_endpoint.py` | 8 | **Integration tests** for `POST /api/auth/refresh`. Covers: successful refresh (200, new access token, same refresh token returned), refresh produces different access tokens on repeated calls, invalid refresh token (401), empty body (422), missing field (422), refresh token from login also works, returned access token is valid JWT decodable with public key, `access_token_expires_minutes` in response. |
| `test_refresh_token_jti.py` | 6 | Unit tests for refresh token uniqueness (jti claim) and model column length. Covers: two calls with same sub produce different tokens (jti uniqueness), jti is valid UUID, rapid repeated calls all produce unique jtis, jti survives decode, `RefreshToken.token` column is `String(512)`, column type is `String`. |
| `test_auth_expired_token.py` | 5 | **Integration tests** for expired-token rejection across auth endpoints. Covers: expired access JWT rejected by `GET /api/auth/me` (401), garbage access token rejected by `/me` (401), missing bearer token returns 401, expired refresh token (DB `expires_at` in past) rejected by `/refresh` (401), expired refresh JWT (past `exp` claim) rejected before DB lookup (401). |
@@ -60,7 +60,7 @@ these yet):
| File | What it covers |
|---|---|
| `smoke_test_api.gd` | Exercises `ApiClient.health_check()` against a live backend. Verifies the real HTTP request path (`HTTPRequest` node lifecycle, JSON parse, result-dictionary contract). Exits 1 on failure. |
| `smoke_test_auth.gd` | Tests the full auth flow via `ApiClient` autoload: register unique user (201), duplicate username (409), under-18 registration (400), correct login (200), wrong password (401), nonexistent user (401). |
| `smoke_test_auth.gd` | Tests the full auth flow via `ApiClient` autoload: register unique user (201), duplicate username (409), `over_18=false` registration (400), correct login (200), wrong password (401), nonexistent user (401). |
| `smoke_test_e2e.gd` | End-to-end flow: health check → register → login → worlds list. Verifies the full request chain with a fresh user, including token storage and `get_worlds()` with auth header. |
| `smoke_test_world_list.gd` | Registers a user, **seeds a real world via `POST /api/worlds/`** (without this, `run_checkpoint_tests.py`'s always-fresh DB means the populated-card render path never actually runs in CI, only the empty-list path would), instantiates `world_list.tscn` as a real scene, waits for `_refresh_worlds()` to complete, confirms it reached the populated (not empty) state, and asserts **each rendered card's real computed width is >= 200px**. That last check is the one that matters: 2026-08-20, a real bug shipped where every card existed in the tree with a non-zero child count and no crash, but rendered at 1.0px wide (`ScrollContainer`'s `size_flags_horizontal`/`vertical` used the raw integer `4` = `Control.SIZE_SHRINK_CENTER` instead of `3` = `SIZE_EXPAND_FILL`) — invisible on screen, and a child-count-only check would never have caught it. Confirmed by deliberately reintroducing the bug: card width check correctly fails at 25px. |
| `smoke_test_main_menu_ui.gd` | Instantiates `main_menu.tscn`, confirms the auth panel starts hidden, and confirms a **real signal emission** on LoginButton/RegisterButton (not calling the handler method directly) actually opens it. This is the check that would have caught 2026-08-20's real bug: the auth panel and its logic were fully correct, but nothing in the scene was ever wired to open it, so Login/Register were completely unreachable from the UI. |