""" Tests for BATON-ARCH-001: Project structure verification. Verifies that all required files and directories exist on disk, and that all Python source files have valid syntax (equivalent to running `python3 -m ast `). """ from __future__ import annotations import ast from pathlib import Path import pytest # Project root: tests/ -> project root PROJECT_ROOT = Path(__file__).parent.parent # --------------------------------------------------------------------------- # Required files (acceptance criteria) # --------------------------------------------------------------------------- REQUIRED_FILES = [ "backend/__init__.py", "backend/config.py", "backend/models.py", "backend/db.py", "backend/telegram.py", "backend/middleware.py", "backend/main.py", "requirements.txt", "requirements-dev.txt", ".env.example", "docs/tech_report.md", ] # ADR files: matched by prefix because filenames include descriptive suffixes ADR_PREFIXES = ["ADR-001", "ADR-002", "ADR-003", "ADR-004"] PYTHON_SOURCES = [ "backend/__init__.py", "backend/config.py", "backend/models.py", "backend/db.py", "backend/telegram.py", "backend/middleware.py", "backend/main.py", ] # --------------------------------------------------------------------------- # File existence # --------------------------------------------------------------------------- @pytest.mark.parametrize("rel_path", REQUIRED_FILES) def test_required_file_exists(rel_path: str) -> None: """Every file listed in the acceptance criteria must exist on disk.""" assert (PROJECT_ROOT / rel_path).is_file(), ( f"Required file missing: {rel_path}" ) @pytest.mark.parametrize("prefix", ADR_PREFIXES) def test_adr_file_exists(prefix: str) -> None: """Each ADR document (ADR-001..004) must have a file in docs/adr/.""" adr_dir = PROJECT_ROOT / "docs" / "adr" matches = list(adr_dir.glob(f"{prefix}*.md")) assert len(matches) >= 1, ( f"ADR file with prefix '{prefix}' not found in {adr_dir}" ) # --------------------------------------------------------------------------- # Repository metadata # --------------------------------------------------------------------------- def test_git_directory_exists() -> None: """.git directory must exist — project must be a git repository.""" assert (PROJECT_ROOT / ".git").is_dir(), ( f".git directory not found at {PROJECT_ROOT}" ) def test_gitignore_exists() -> None: """.gitignore must be present in the project root.""" assert (PROJECT_ROOT / ".gitignore").is_file(), ( f".gitignore not found at {PROJECT_ROOT}" ) # --------------------------------------------------------------------------- # Python syntax validation (replaces: python3 -m ast ) # --------------------------------------------------------------------------- @pytest.mark.parametrize("rel_path", PYTHON_SOURCES) def test_python_file_has_valid_syntax(rel_path: str) -> None: """Every backend Python file must parse without SyntaxError.""" path = PROJECT_ROOT / rel_path assert path.is_file(), f"Python file not found: {rel_path}" source = path.read_text(encoding="utf-8") try: ast.parse(source, filename=str(path)) except SyntaxError as exc: pytest.fail(f"Syntax error in {rel_path}: {exc}") # --------------------------------------------------------------------------- # BATON-ARCH-008: monkey-patch must live only in conftest.py # --------------------------------------------------------------------------- _PATCH_MARKER = "_safe_aiosqlite_await" _FILES_MUST_NOT_HAVE_PATCH = [ "tests/test_register.py", "tests/test_signal.py", "tests/test_webhook.py", ] def test_monkeypatch_present_in_conftest() -> None: """conftest.py must contain the aiosqlite monkey-patch.""" conftest = (PROJECT_ROOT / "tests" / "conftest.py").read_text(encoding="utf-8") assert _PATCH_MARKER in conftest, ( "conftest.py is missing the aiosqlite monkey-patch (_safe_aiosqlite_await)" ) @pytest.mark.parametrize("rel_path", _FILES_MUST_NOT_HAVE_PATCH) def test_monkeypatch_absent_in_test_file(rel_path: str) -> None: """Test files other than conftest.py must NOT contain duplicate monkey-patch.""" source = (PROJECT_ROOT / rel_path).read_text(encoding="utf-8") assert _PATCH_MARKER not in source, ( f"{rel_path} still contains a duplicate monkey-patch block ({_PATCH_MARKER!r})" )