"""Tests for the check-shadow-tests pre-commit hook. Verifies that the hook flags test modules with no ``local_deep_research`` import (shadow tests) while allowing real tests, the ``# allow: no-sut-import`` opt-out, and non-test modules. """ import subprocess import sys import tempfile from pathlib import Path HOOK_SCRIPT = ( Path(__file__).parent.parent.parent / ".pre-commit-hooks" / "check-shadow-tests.py" ) def _run_hook(content: str, filename: str = "test_sample.py"): """Write content to a temp file named `filename` and run the hook.""" with tempfile.TemporaryDirectory() as d: path = Path(d) / filename path.write_text(content, encoding="utf-8") return subprocess.run( [sys.executable, str(HOOK_SCRIPT), str(path)], capture_output=True, text=True, ) class TestFlagsShadowTests: """Test modules that exercise no SUT must be flagged.""" def test_inline_logic_no_sut_import(self): result = _run_hook( "import datetime\n\n" "def test_year():\n" " assert datetime.date(2020, 1, 1).year == 2020\n" ) assert result.returncode == 1 assert "Shadow tests detected" in result.stdout def test_only_stdlib_imports(self): result = _run_hook( "import json\n\ndef test_roundtrip():\n" ' assert json.loads(json.dumps({"a": 1})) == {"a": 1}\n' ) assert result.returncode == 1 def test_type_checking_only_import_flagged(self): # SUT imported only for type hints never runs, so it's still a shadow. result = _run_hook( "from typing import TYPE_CHECKING\n\n" "if TYPE_CHECKING:\n" " from local_deep_research.config import Thing\n\n" "def test_x():\n assert 1 == 1\n" ) assert result.returncode == 1 class TestAllowsRealTests: """Files that import the SUT — or are exempt — must pass.""" def test_from_import_passes(self): result = _run_hook( "from local_deep_research.config import x\n\n" "def test_x():\n assert x\n" ) assert result.returncode == 0 def test_src_prefixed_import_passes(self): result = _run_hook( "from src.local_deep_research import app\n\n" "def test_app():\n assert app\n" ) assert result.returncode == 0 def test_plain_import_passes(self): result = _run_hook( "import local_deep_research\n\n" "def test_pkg():\n assert local_deep_research\n" ) assert result.returncode == 0 def test_type_checking_plus_runtime_import_passes(self): # A type-only import under TYPE_CHECKING is ignored, but the runtime # import at module level still counts. result = _run_hook( "from typing import TYPE_CHECKING\n\n" "if TYPE_CHECKING:\n" " from local_deep_research.config import Thing\n\n" "from local_deep_research.config import run\n\n" "def test_x():\n assert run\n" ) assert result.returncode == 0 def test_allow_marker_exempts(self): result = _run_hook( "# allow: no-sut-import — guardian test asserting repo structure\n" "import os\n\ndef test_repo():\n assert os.path.exists('.')\n" ) assert result.returncode == 0 def test_marker_without_reason_does_not_exempt(self): # The reason text after the marker is mandatory. result = _run_hook( "# allow: no-sut-import\n" "import os\n\ndef test_repo():\n assert os.path.exists('.')\n" ) assert result.returncode == 1 class TestScope: """Only pytest test modules are inspected.""" def test_non_test_module_ignored(self): result = _run_hook("import datetime\n", filename="helpers.py") assert result.returncode == 0 def test_conftest_ignored(self): result = _run_hook("import datetime\n", filename="conftest.py") assert result.returncode == 0 def test_underscore_test_suffix_inspected(self): result = _run_hook( "import datetime\n\ndef test_x():\n assert datetime\n", filename="sample_test.py", ) assert result.returncode == 1