"""Unit tests for the A5 purity guard (phase 02 task 01). Proves the shipped `sim/` package imports nothing forbidden, and — just as important — that the guard actually catches a violation when one exists. """ from __future__ import annotations from pathlib import Path import pytest from undercooked.sim.purity import ( FORBIDDEN_ROOTS, PurityError, assert_pure_imports, find_violations, ) pytestmark = pytest.mark.unit def test_forbidden_roots_are_the_plan_a5_list() -> None: assert frozenset({"asyncio", "fastapi", "socket", "time", "uvicorn"}) == FORBIDDEN_ROOTS def test_shipped_sim_package_is_pure() -> None: assert find_violations() == [] assert_pure_imports() # must not raise def test_clean_module_with_stdlib_sibling_and_relative_imports(tmp_path: Path) -> None: (tmp_path / "ok.py").write_text( "import math\n" "from hashlib import sha256\n" "from undercooked.sim.model import Vec2\n" "from . import model\n", encoding="utf-8", ) assert find_violations(tmp_path) == [] def test_comments_and_strings_are_not_imports(tmp_path: Path) -> None: (tmp_path / "prose.py").write_text( '# we must never "import asyncio" here\nDOC = "import time is forbidden"\n', encoding="utf-8", ) assert find_violations(tmp_path) == [] @pytest.mark.parametrize( ("statement", "root"), [ ("import asyncio", "asyncio"), ("import time", "time"), ("from time import monotonic", "time"), ("import socket", "socket"), ("from socket import socket", "socket"), ("from fastapi import FastAPI", "fastapi"), ("import uvicorn", "uvicorn"), ("import uvicorn.protocols", "uvicorn"), ], ) def test_find_violations_detects_each_forbidden_import( tmp_path: Path, statement: str, root: str ) -> None: (tmp_path / "bad.py").write_text(f"{statement}\n", encoding="utf-8") assert find_violations(tmp_path) == [f"bad.py: imports {root}"] def test_find_violations_scans_subpackages(tmp_path: Path) -> None: sub = tmp_path / "deep" sub.mkdir() (sub / "leak.py").write_text("import asyncio\n", encoding="utf-8") assert find_violations(tmp_path) == ["deep/leak.py: imports asyncio"] def test_assert_pure_imports_raises_with_the_violation_list(tmp_path: Path) -> None: (tmp_path / "bad.py").write_text("import time\nimport socket\n", encoding="utf-8") with pytest.raises(PurityError, match=r"bad\.py: imports socket"): assert_pure_imports(tmp_path)