phase: 02_level_content_and_validation
Phase `02_level_content_and_validation` — final verification **green**. - **Gate:** `bash .agents/validate.sh` → ruff ✅ · pyright 0 errors ✅ · `undercooked-levels validate` OK (2 levels) ✅ · **668 passed** (incl. 10 Playwright e2e) ✅ · coverage **100%** ✅ · repo hygiene ✅ - **Criteria:** `validate` exit 0, one line per level; `list` = `01_first_kitchen`, `02_double_shift` in campaign order; live `:8000` `/healthz` 200, `/api/levels` (+detail) carries both levels with numeric `target_score`/`time_limit` and no grid/station leakage, `/api/config` correct, unknown id → 404, startup log `content loaded: 2 level(s), 4 recipe(s)`; `validate --dir tests/fixtures/levels/_bad/` → exit 1 with named `RECIPE_STATION_MISSING` / `SERVE_REACHABLE` / `TARGET_IMPLAUSIBLE`; A8 lock test proves a YAML-only level reaches loader+API+CLI. - **Defect fixed (pre-existing path, phase-02-triggered):** `undercooked` with unusable content printed the report then a raw traceback and exited `1`. `src/undercooked/__main__.py` now catches `ContentError` → one closing line, exit `2` (like a bad setting); 2 new tests in `tests/unit/test_main.py` (incl. non-content failures still propagate). - **Docs pass (full, project-wide):** README (content gate + exit `2`, config section), AGENTS.md §3, `docs/LEVELS.md` (startup refusal + exact CLI exit codes), `docs/DEPLOYMENT.md` (crash-loop diagnosis bullet); DESIGN/TESTING re-read, consistent. - **Container re-verified (no phase-01 regression):** `podman build -f Containerfile` succeeds; the image ships `levels/` and serves `/healthz` 200, `/api/levels` (2 levels), `/` 200 as non-root. - **Deviation to note:** the phase overview names `tests/fixtures/levels/bad/`; the actual broken-fixture dir is `tests/fixtures/levels/_bad/` (the `_` prefix is what keeps it out of normal discovery). Overview files are read-only to me — README/LEVELS.md document the real path. - No git index/history commands run; all changes left unstaged for the harness. ntfy notification sent.
This commit is contained in:
@@ -37,6 +37,7 @@ from undercooked.config import (
|
||||
get_settings,
|
||||
reset_settings_cache,
|
||||
)
|
||||
from undercooked.sim.recipe import ContentError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
@@ -214,6 +215,39 @@ def test_main_reports_an_invalid_environment(
|
||||
assert "UNDERCOOKED_TICK_HZ=99" in capsys.readouterr().err
|
||||
|
||||
|
||||
def test_main_exits_two_when_the_content_gate_refuses_startup(
|
||||
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
"""Unusable levels are a startup error, not a traceback.
|
||||
|
||||
Content is read when uvicorn imports the app string, so `create_app`'s
|
||||
`ContentError` surfaces out of `uvicorn.run` — and the CLI answers with exit 2,
|
||||
the same code an unusable `UNDERCOOKED_PORT` gets. The named-rule report itself
|
||||
is written by `create_app` before it raises, so this only adds the closing line.
|
||||
"""
|
||||
|
||||
def refusing_run(_target: str, **_kwargs: Any) -> None:
|
||||
raise ContentError("undercooked: level content failed validation:\nBOGUS_RULE: nope")
|
||||
|
||||
monkeypatch.setattr(uvicorn, "run", refusing_run)
|
||||
assert main([]) == EXIT_CONFIG_ERROR
|
||||
err = capsys.readouterr().err
|
||||
assert "refusing to start" in err
|
||||
assert "undercooked-levels validate" in err
|
||||
assert "Traceback" not in err
|
||||
|
||||
|
||||
def test_main_lets_any_other_serving_failure_propagate(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Only the content gate is translated: a real crash stays a real crash."""
|
||||
|
||||
def crashing_run(_target: str, **_kwargs: Any) -> None:
|
||||
raise OSError("port in use")
|
||||
|
||||
monkeypatch.setattr(uvicorn, "run", crashing_run)
|
||||
with pytest.raises(OSError, match="port in use"):
|
||||
main([])
|
||||
|
||||
|
||||
def test_unknown_flag_is_an_argument_error() -> None:
|
||||
with pytest.raises(SystemExit) as excinfo:
|
||||
main(["--nope"])
|
||||
|
||||
Reference in New Issue
Block a user