Merge architect's per-file test detail into the checkpoint doc fix
Architect independently reclaimed this task after my fix landed and rewrote the same doc with more precise per-file test descriptions and a more specific gap list -- genuinely better in places (caught real detail I'd summarized loosely) but its own investigation also introduced one factual error (claimed 18 tests in test_auth_age_gate.py; verified via pytest --collect-only, it's 17) and dropped two things worth keeping: the exact commit hash for reproducibility, and the Client export section (it never looked at the export step). Merged: kept the architect's richer per-file coverage descriptions and gap analysis, restored the verified counts/commit hash/export section/ --quit gotcha from my original. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,53 +20,60 @@ reachable Postgres — see the runner script).
|
|||||||
|
|
||||||
| File | Tests | What it covers |
|
| 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. Backs the 18+ registration gate (PR #23). |
|
| `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_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_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 | Real integration tests against the FastAPI app via `TestClient`: register, login, duplicate username/email, wrong password, JWT shape. Hits a real Postgres, not mocked (PR #35). |
|
| `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_refresh_endpoint.py` | 8 | `POST /auth/refresh` end-to-end: valid token exchange, distinct access tokens per call (jti-based), invalid/missing token rejection, login-issued tokens also work (PR #37). |
|
| `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 | Refresh-token `jti` uniqueness and the `refresh_tokens.token` column length/type (PR #33's RS256 upgrade). |
|
| `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 | Expired-token rejection on two distinct paths: an access JWT with a past `exp` claim, and a refresh token whose persisted `expires_at` is in the past (PR #38). |
|
| `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). |
|
||||||
|
|
||||||
**What changed since the last inventory (2026-08-19):** that version said
|
**What these are, honestly:** a mix of pure unit tests (`test_auth_age_gate.py`,
|
||||||
"no integration/endpoint tests at all" — that gap is closed.
|
`test_gltf_metadata.py`, `test_refresh_token_jti.py`) and real integration tests
|
||||||
`test_auth_endpoints.py`, `test_refresh_endpoint.py`,
|
(`test_auth_endpoints.py`, `test_refresh_endpoint.py`, `test_auth_expired_token.py`)
|
||||||
`test_refresh_token_jti.py`, and `test_auth_expired_token.py` all hit the
|
that hit the FastAPI app against a real Postgres database via `TestClient`.
|
||||||
real FastAPI app against a real Postgres, not mocks. The suite is also
|
The integration tests exercise the full stack: Pydantic validation → DB →
|
||||||
re-runnable without manual cleanup now — `backend/tests/conftest.py` has a
|
JWT → response. The suite is also re-runnable without manual cleanup —
|
||||||
session-scoped autouse fixture that truncates all app tables once before
|
`backend/tests/conftest.py` has a session-scoped autouse fixture that
|
||||||
the suite runs.
|
truncates all app tables once before the suite runs.
|
||||||
|
|
||||||
**Known coverage gaps** (real gaps, not placeholders):
|
**Known coverage gaps** (real gaps, not placeholders — nothing enforces filling
|
||||||
- `/worlds` (POST) and `/assets/*` have no endpoint tests yet — `GET /worlds`
|
these yet):
|
||||||
is covered indirectly via the Godot smoke tests below, but there's no
|
- No endpoint tests for `GET /api/worlds`, `GET /api/assets/*`, or any
|
||||||
pytest coverage for either.
|
world/asset CRUD routes — only auth endpoints have integration coverage
|
||||||
- No tests for the moderation schema/migration — it exists
|
(`GET /api/worlds` is covered indirectly via the Godot smoke tests below).
|
||||||
(`0003_add_moderation_tables_and_user_roles`) and the migration itself
|
- No tests for the moderation schema/migration beyond "the migration runs."
|
||||||
runs clean, but nothing in `backend/tests/` exercises the models or any
|
- No tests for the `GET /api/auth/me` endpoint's response body (only its
|
||||||
moderation endpoint (there isn't one yet).
|
auth-header validation is covered via expired-token tests).
|
||||||
|
- No tests for session management (create/validate/revoke sessions).
|
||||||
|
|
||||||
## Godot client — `scripts/smoke_test_*.gd`
|
## Godot client — `scripts/smoke_test_*.gd`
|
||||||
|
|
||||||
**4 real smoke tests merged on `main`.** Each needs a live backend —
|
**4 smoke tests merged into `main`.** Each needs a live backend —
|
||||||
`AGP_API_BASE_URL` env var, falling back to the
|
`AGP_API_BASE_URL` env var, falling back to the
|
||||||
`application/agp/api_base_url` project setting (default
|
`application/agp/api_base_url` project setting (default
|
||||||
`http://127.0.0.1:8000`) if unset.
|
`http://127.0.0.1:8000`) if unset.
|
||||||
|
|
||||||
Run: `godot --headless --path . --import` once (builds the class cache —
|
|
||||||
skip this and every `class_name`-declared class falsely errors as
|
|
||||||
undeclared), then for each file below: `godot --headless --path . --script
|
|
||||||
res://scripts/<name>.gd` — **no `--quit`**, it kills these before their
|
|
||||||
awaited HTTP calls finish.
|
|
||||||
|
|
||||||
| File | What it covers |
|
| File | What it covers |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `smoke_test_api.gd` | `ApiClient.health_check()` against a live backend — the HTTP request path itself (`HTTPRequest` node lifecycle, JSON parse, result-dictionary contract). |
|
| `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` | 6 real scenarios end-to-end: register, duplicate username, under-18 rejection, correct login, wrong password, non-existent user. |
|
| `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_e2e.gd` | Full loop: health check → register → login → fetch worlds list, 4/4 steps. |
|
| `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` | Instantiates the real `world_list.tscn` scene (not just the API layer) — registers a user, adds the scene to the tree, confirms it reaches a real end state (cards, empty-list, or error) and actually renders world cards when data exists. |
|
| `smoke_test_world_list.gd` | Instantiates `world_list.tscn` as a real scene, adds to tree (catches `@onready` node-path bugs), waits for `_refresh_worlds()` to complete, confirms the screen reaches a real end state (world cards, empty-list message, or error label — not stuck loading). |
|
||||||
|
|
||||||
**Known gap:** `get_me()` (`GET /auth/me`) is implemented in
|
Run (once, to build the class cache): `godot --headless --path . --import`,
|
||||||
`scripts/api_client.gd` but has no smoke coverage yet.
|
then for each smoke test: `godot --headless --path . --script
|
||||||
|
res://scripts/smoke_test_<name>.gd` — **no `--quit`**, it kills these before
|
||||||
|
their awaited HTTP calls finish.
|
||||||
|
|
||||||
|
**Known gaps:**
|
||||||
|
- No smoke tests for the main menu scene (`main_menu.tscn`).
|
||||||
|
- No smoke tests for chunk loading/streaming (`chunk.gd`, `chunk_scene.gd`).
|
||||||
|
- No smoke tests for dynamic/static object nodes.
|
||||||
|
- All smoke tests require a live backend; no offline/unit smoke tests exist.
|
||||||
|
- `smoke_test_world_list.gd` is the only one that exercises a real `.tscn`
|
||||||
|
scene instantiation — others only test the `ApiClient` autoload.
|
||||||
|
- `get_me()` (`GET /auth/me`) is implemented in `scripts/api_client.gd` but
|
||||||
|
has no smoke coverage yet.
|
||||||
|
|
||||||
## Client export
|
## Client export
|
||||||
|
|
||||||
@@ -76,12 +83,12 @@ exports complete clean from a fresh checkout and produce valid binaries
|
|||||||
(confirmed via `file` — real ELF64/PE32+, not empty or truncated). Neither
|
(confirmed via `file` — real ELF64/PE32+, not empty or truncated). Neither
|
||||||
export is exercised by an automated GUI test (headless Godot can't drive a
|
export is exercised by an automated GUI test (headless Godot can't drive a
|
||||||
window) — actually launching and clicking through the exported binary is a
|
window) — actually launching and clicking through the exported binary is a
|
||||||
manual step, see the checkpoint playtest instructions.
|
manual step, see `docs/checkpoints/PC1.md` for playtest instructions.
|
||||||
|
|
||||||
## What "full coverage" should eventually mean here
|
## What "full coverage" should eventually mean here
|
||||||
|
|
||||||
Not defined precisely yet — the honest target is "close the /worlds POST
|
Not defined precisely yet — the honest target as of 2026-08-20 is
|
||||||
and /assets/* gaps next, and add get_me() smoke coverage" rather than a
|
"integration tests for every endpoint + smoke tests for every major scene."
|
||||||
number. As real integration/endpoint tests get added, update the tables
|
As real integration/endpoint tests get added, update the tables above
|
||||||
above rather than letting this doc drift stale; a wrong inventory is worse
|
rather than letting this doc drift stale; a wrong inventory is worse than
|
||||||
than no inventory.
|
no inventory.
|
||||||
|
|||||||
Reference in New Issue
Block a user