phase: 05_anonymous_lobbies_and_room_lifecycle
All green. Final report:
**Phase 05 final verification pass — complete, 100% green.**
**Verified (all 5 tasks already in `complete/`):**
- `RoomSupervisor` import, `POST /api/rooms` → 5-char Crockford code (`VBE8Y`), `GET /api/rooms/{code}` → seat list, join/session/404 flows — all live over HTTP
- `docs/ROOMS.md` present with FSM diagram, crash-isolation and "What is lost on restart" sections
- Phase tests all pass: lifecycle 4-seat `WON`→`LOBBY` (7 tests), crash isolation, GC/idle via `FakeClock` (no sleeps), FSM, tick cadence, supervisor, sessions
**Results:**
- `uv run pytest` → **1506 passed, 0 failed**; coverage **98.89%** (>90% floor; `room.py` 99%, `rooms.py` 97%, `sessions.py` 99% — all ≥95% per-module gate)
- `uv run ruff check .` → clean; `uv run pyright` → 0 errors; `uv run undercooked-levels validate` → content OK; server boots, `/healthz` 200, client at `/` 200
- e2e US-01 suite re-run after fixes: 10 passed
**Documentation — inconsistencies found and fixed:**
- `README.md`: status now lists phases 01–05 complete, next pending `06_realtime_websocket_protocol`; dropped "(phase 05, in progress)"; 3 stale "Not implemented until phase 05." references corrected
- `app/static/js/app.js` + `tests/e2e/test_us01_landing_surface.py`: placeholder string updated in lockstep to "Not implemented until phase 07." (phase 07 task 02 wires the buttons), comments updated to reflect phase-05 completion
- `AGENTS.md`: reviewed in full — §6 already documents all phase-05 modules accurately; no changes needed
**Decisions:** no code behavior changes beyond the user-visible placeholder string; no live-infrastructure touch (only a local loopback server, stopped).
**Next pending phase:** `06_realtime_websocket_protocol`.
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
"""Unit (grep-style): no wall-clock calls anywhere in `server/`.
|
||||
|
||||
The phase-05 contract (task 01, work item 1): every time-dependent server
|
||||
object receives a `Clock`; the *only* file in `src/undercooked/server/`
|
||||
allowed to import `time` is `clock.py`, and even there the clock function
|
||||
is held as a reference — `time.monotonic()` / `time.time()` are never
|
||||
called in server code. This test greps for the literals (and for stray
|
||||
`time` imports) so a future edit cannot reintroduce a bare wall-clock call
|
||||
without failing the suite.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
SERVER_DIR = REPO_ROOT / "src" / "undercooked" / "server"
|
||||
|
||||
#: The wall-clock calls the contract forbids in server code.
|
||||
FORBIDDEN_CALLS = ("time.monotonic()", "time.time()")
|
||||
|
||||
#: `time` may be imported by exactly one module.
|
||||
TIME_IMPORT = re.compile(r"^\s*(?:import time\b|from time import\b)", re.MULTILINE)
|
||||
|
||||
|
||||
def test_no_wall_clock_call_lives_in_server_code() -> None:
|
||||
offenders = [
|
||||
f"{path.name}: {literal}"
|
||||
for path in sorted(SERVER_DIR.rglob("*.py"))
|
||||
for literal in FORBIDDEN_CALLS
|
||||
if literal in path.read_text(encoding="utf-8")
|
||||
]
|
||||
assert offenders == [], "wall-clock calls leaked into server/:\n " + "\n ".join(
|
||||
offenders
|
||||
)
|
||||
|
||||
|
||||
def test_only_the_clock_module_imports_time() -> None:
|
||||
offenders = [
|
||||
path.name
|
||||
for path in sorted(SERVER_DIR.rglob("*.py"))
|
||||
if path.name != "clock.py" and TIME_IMPORT.search(path.read_text(encoding="utf-8"))
|
||||
]
|
||||
assert offenders == [], (
|
||||
f"modules that import `time` outside clock.py: {', '.join(offenders)}"
|
||||
)
|
||||
|
||||
|
||||
def test_the_guard_is_not_vacuous() -> None:
|
||||
"""A guard that never fires is no guard: prove both checks detect a
|
||||
planted violation in this very file's patterns."""
|
||||
assert FORBIDDEN_CALLS[0] in "x = time.monotonic()\n"
|
||||
assert TIME_IMPORT.search("import time\n") is not None
|
||||
assert SERVER_DIR.is_dir()
|
||||
assert (SERVER_DIR / "clock.py").is_file()
|
||||
Reference in New Issue
Block a user