kin: BATON-002 [Research] UX Designer
This commit is contained in:
commit
057e500d5f
29 changed files with 3530 additions and 0 deletions
137
tests/test_structure.py
Normal file
137
tests/test_structure.py
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
"""
|
||||
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 <file>`).
|
||||
"""
|
||||
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 <file>)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@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})"
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue