""" Tests for BATON-ARCH-004: Переименование ADR-002-offline-pattern.md. Acceptance criteria: 1. No file named ADR-002-offline-pattern*.md exists in docs/adr/. 2. No references to 'ADR-002-offline-pattern' anywhere in docs/ and ARCHITECTURE.md. 3. No dangling bare 'ADR-002' references in docs/, ARCHITECTURE.md, or tests/. 4. ADR-007-offline-queue-v2.md exists in docs/adr/. 5. tech_report.md references ADR-007 (not ADR-002). 6. ADR-006 references ADR-007 (not ADR-002). 7. ARCHITECTURE.md references ADR-007 (not ADR-002) for offline-related rows. """ from __future__ import annotations import re from pathlib import Path PROJECT_ROOT = Path(__file__).parent.parent ADR_DIR = PROJECT_ROOT / "docs" / "adr" ARCHITECTURE_MD = PROJECT_ROOT / "ARCHITECTURE.md" TECH_REPORT_MD = PROJECT_ROOT / "docs" / "tech_report.md" ADR_006 = ADR_DIR / "ADR-006-offline-ios-constraints.md" ADR_007 = ADR_DIR / "ADR-007-offline-queue-v2.md" # --------------------------------------------------------------------------- # Criterion 1 — old ADR-002-offline-pattern file must not exist # --------------------------------------------------------------------------- # Criterion 1 superseded by BATON-ARCH-014: ADR-002-offline-pattern.md now exists # as a legitimate new ADR document. # --------------------------------------------------------------------------- # Criterion 2 — no stale 'ADR-002-offline-pattern' textual references in docs/ # --------------------------------------------------------------------------- def _all_md_in_docs() -> list[Path]: return list((PROJECT_ROOT / "docs").rglob("*.md")) def test_no_adr_002_offline_pattern_in_docs() -> None: """Ни один файл в docs/ не должен содержать строку 'ADR-002-offline-pattern'.""" for path in _all_md_in_docs(): content = path.read_text(encoding="utf-8") assert "ADR-002-offline-pattern" not in content, ( f"Найдена устаревшая ссылка 'ADR-002-offline-pattern' в {path.relative_to(PROJECT_ROOT)}" ) # test_no_adr_002_offline_pattern_in_architecture_md superseded by BATON-ARCH-014: # ARCHITECTURE.md now legitimately links to ADR-002-offline-pattern.md. # test_no_bare_adr_002_in_docs superseded: ADR-002-offline-pattern.md is a valid new ADR. # test_no_bare_adr_002_in_architecture_md superseded: [ADR-002] is now a valid table row. # --------------------------------------------------------------------------- # Criterion 3 — no dangling bare ADR-002 references in test files # --------------------------------------------------------------------------- def test_no_bare_adr_002_in_tests() -> None: """Файлы тестов (кроме легитимных исключений) не должны содержать голую метку 'ADR-002'.""" pattern = re.compile(r"\bADR-002\b") # Легитимные исключения: файлы, документирующие задачи, которые явно работают с ADR-002. _ALLOWED = { Path(__file__).resolve(), # test_arch_004.py: задача по переименованию (PROJECT_ROOT / "tests" / "test_arch_014.py").resolve(), # задача по созданию ADR-002 } for path in (PROJECT_ROOT / "tests").glob("*.py"): if path.resolve() in _ALLOWED: continue content = path.read_text(encoding="utf-8") assert not pattern.search(content), ( f"Найдена висячая ссылка 'ADR-002' в {path.relative_to(PROJECT_ROOT)}" ) # --------------------------------------------------------------------------- # Criterion 4 — ADR-007-offline-queue-v2.md exists # --------------------------------------------------------------------------- def test_adr_007_offline_queue_file_exists() -> None: """Файл ADR-007-offline-queue-v2.md должен существовать в docs/adr/.""" assert ADR_007.is_file(), ( f"Переименованный файл ADR-007-offline-queue-v2.md не найден в {ADR_DIR}" ) # --------------------------------------------------------------------------- # Criterion 5 — tech_report.md references ADR-007 # --------------------------------------------------------------------------- def test_tech_report_references_adr_007() -> None: """docs/tech_report.md должен содержать ссылку на ADR-007.""" content = TECH_REPORT_MD.read_text(encoding="utf-8") assert "ADR-007" in content, ( "tech_report.md не ссылается на ADR-007 (переименованный offline-pattern)" ) # --------------------------------------------------------------------------- # Criterion 6 — ADR-006 references ADR-007 (not ADR-002) # --------------------------------------------------------------------------- def test_adr_006_references_adr_007() -> None: """ADR-006-offline-ios-constraints.md должен ссылаться на ADR-007.""" content = ADR_006.read_text(encoding="utf-8") assert "ADR-007" in content, ( "ADR-006 не содержит ссылки на ADR-007" ) def test_adr_006_has_no_adr_002_references() -> None: """ADR-006-offline-ios-constraints.md не должен ссылаться на ADR-002.""" content = ADR_006.read_text(encoding="utf-8") assert not re.search(r"\bADR-002\b", content), ( "ADR-006 всё ещё содержит ссылку 'ADR-002'" ) # --------------------------------------------------------------------------- # Criterion 7 — ARCHITECTURE.md references ADR-007 for offline rows # --------------------------------------------------------------------------- def test_architecture_md_references_adr_007_for_service_worker() -> None: """ARCHITECTURE.md должен ссылаться на ADR-007 в строке Service Worker.""" content = ARCHITECTURE_MD.read_text(encoding="utf-8") sw_line = next( (line for line in content.splitlines() if "Service Worker" in line), None ) assert sw_line is not None, "Строка 'Service Worker' не найдена в ARCHITECTURE.md" assert "ADR-007" in sw_line, ( f"Строка Service Worker в ARCHITECTURE.md не содержит ADR-007: {sw_line!r}" ) def test_architecture_md_references_adr_007_for_offline() -> None: """ARCHITECTURE.md должен ссылаться на ADR-007 в строке Offline.""" content = ARCHITECTURE_MD.read_text(encoding="utf-8") offline_line = next( (line for line in content.splitlines() if line.startswith("| Offline")), None ) assert offline_line is not None, "Строка '| Offline' не найдена в ARCHITECTURE.md" assert "ADR-007" in offline_line, ( f"Строка Offline в ARCHITECTURE.md не содержит ADR-007: {offline_line!r}" ) # --------------------------------------------------------------------------- # Criterion 8 — ADR-007 строка, помечающая #1001 устаревшим, содержит ACTION: # --------------------------------------------------------------------------- def test_adr_007_stale_reference_has_action_item() -> None: """Строка ADR-007, ссылающаяся на решение #1001, должна содержать маркер ACTION:. Конвенция #1049: все ссылки на устаревшие решения обязаны быть оформлены как явный ACTION item, а не как пассивная заметка. """ content = ADR_007.read_text(encoding="utf-8") lines_with_1001 = [line for line in content.splitlines() if "#1001" in line] assert lines_with_1001, ( "ADR-007 не содержит ни одной строки со ссылкой на решение #1001" ) has_action = any(re.search(r"ACTION:", line) for line in lines_with_1001) assert has_action, ( "Строка, помечающая решение #1001 устаревшим, не содержит явного маркера ACTION: " "— нарушение конвенции #1049" )