"""Tests that src/models.py has NO top-level pydantic import. Per the Main Thread Purity Invariant (spec.md:2.1), pydantic is heavy and must not appear in the main-thread import chain. The two pydantic classes (GenerateRequest, ConfirmRequest) are exposed via a module-level __getattr__ proxy (PEP 562) that materializes them on first access. These tests run in a fresh subprocess to ensure no warmup state leaks from the test runner. We assert: - pydantic is NOT imported as a side effect of `import src.models` - GenerateRequest and ConfirmRequest can be imported and instantiated - Accessing the proxy triggers the pydantic import - The static audit no longer flags src/models.py for pydantic """ import subprocess import sys import textwrap from pathlib import Path ROOT = Path(__file__).resolve().parent.parent def _run_in_subprocess(snippet: str) -> subprocess.CompletedProcess: script = textwrap.dedent(snippet) return subprocess.run( [sys.executable, "-c", script], capture_output=True, text=True, cwd=str(ROOT), timeout=30, ) def test_models_does_not_import_pydantic_at_module_level() -> None: res = _run_in_subprocess(""" import sys import src.models print('pydantic' in sys.modules) """) assert res.returncode == 0, f"stderr: {res.stderr}" assert res.stdout.strip() == "False", ( f"src.models triggered pydantic import: {res.stdout}" ) def test_generate_request_works_when_explicitly_imported() -> None: res = _run_in_subprocess(""" from src.models import GenerateRequest req = GenerateRequest(prompt="hello") print(req.prompt) print(req.auto_add_history) """) assert res.returncode == 0, f"stderr: {res.stderr}" assert res.stdout.strip() == "hello\nTrue" def test_confirm_request_works_when_explicitly_imported() -> None: res = _run_in_subprocess(""" from src.models import ConfirmRequest req = ConfirmRequest(approved=True, script="echo hi") print(req.approved) print(req.script) """) assert res.returncode == 0, f"stderr: {res.stderr}" assert res.stdout.strip() == "True\necho hi" def test_pydantic_only_loaded_after_explicit_class_access() -> None: res = _run_in_subprocess(""" import sys import src.models assert 'pydantic' not in sys.modules, 'pydantic leaked into sys.modules at import time' from src.models import GenerateRequest print('pydantic' in sys.modules) print(GenerateRequest.__bases__[0].__name__) print(GenerateRequest.__bases__[0].__module__) """) assert res.returncode == 0, f"stderr: {res.stderr}" lines = res.stdout.strip().splitlines() assert lines[0] == "True", f"GenerateRequest access did not trigger pydantic import: {res.stdout}" assert lines[1] == "BaseModel", f"GenerateRequest base is not BaseModel: {res.stdout}" assert lines[2].startswith("pydantic"), f"GenerateRequest base is not from pydantic package: {res.stdout}" def test_proxy_caches_real_class_for_repeated_access() -> None: res = _run_in_subprocess(""" from src.models import GenerateRequest cls1 = GenerateRequest cls2 = GenerateRequest print(cls1 is cls2) """) assert res.returncode == 0, f"stderr: {res.stderr}" assert res.stdout.strip() == "True", f"proxy did not cache the real class: {res.stdout}" def test_generate_request_validation_rejects_missing_prompt() -> None: res = _run_in_subprocess(""" from src.models import GenerateRequest try: GenerateRequest() print("NO_RAISE") except Exception as e: print(type(e).__name__) """) assert res.returncode == 0, f"stderr: {res.stderr}" assert res.stdout.strip() != "NO_RAISE", "pydantic validation did not fire on missing required field" assert "ValidationError" in res.stdout, f"expected ValidationError, got: {res.stdout}" def test_audit_sees_no_pydantic_violation_in_models() -> None: res = _run_in_subprocess(""" import ast from pathlib import Path root = Path('.').resolve() models_path = root / 'src' / 'models.py' tree = ast.parse(models_path.read_text(encoding='utf-8')) for node in tree.body: if isinstance(node, ast.Import): for alias in node.names: if alias.name == 'pydantic' or alias.name.startswith('pydantic.'): print('VIOLATION:', alias.name) elif isinstance(node, ast.ImportFrom): if node.module and (node.module == 'pydantic' or node.module.startswith('pydantic.')): print('VIOLATION:', node.module) print('OK') """) assert res.returncode == 0, f"stderr: {res.stderr}" assert "OK" in res.stdout, f"audit script errored: {res.stderr}" assert "VIOLATION" not in res.stdout, f"src/models.py still has top-level pydantic: {res.stdout}"