""" 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 # --------------------------------------------------------------------------- def test_adr_002_offline_pattern_file_does_not_exist() -> None: """Файл ADR-002-offline-pattern*.md не должен существовать в docs/adr/.""" matches = list(ADR_DIR.glob("ADR-002-offline-pattern*.md")) assert len(matches) == 0, ( f"Старый файл ADR-002-offline-pattern найден: {matches}" ) # --------------------------------------------------------------------------- # Criterion 2 — no 'ADR-002-offline-pattern' textual references # --------------------------------------------------------------------------- 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)}" ) def test_no_adr_002_offline_pattern_in_architecture_md() -> None: """ARCHITECTURE.md не должен содержать строку 'ADR-002-offline-pattern'.""" content = ARCHITECTURE_MD.read_text(encoding="utf-8") assert "ADR-002-offline-pattern" not in content, ( "Найдена устаревшая ссылка 'ADR-002-offline-pattern' в ARCHITECTURE.md" ) # --------------------------------------------------------------------------- # Criterion 3 — no dangling bare ADR-002 references # --------------------------------------------------------------------------- def test_no_bare_adr_002_in_docs() -> None: """Ни один файл в docs/ не должен содержать голую метку 'ADR-002' (без корректного имени файла).""" pattern = re.compile(r"\bADR-002\b") for path in _all_md_in_docs(): content = path.read_text(encoding="utf-8") assert not pattern.search(content), ( f"Найдена висячая ссылка 'ADR-002' в {path.relative_to(PROJECT_ROOT)}" ) def test_no_bare_adr_002_in_architecture_md() -> None: """ARCHITECTURE.md не должен содержать голую метку 'ADR-002'.""" content = ARCHITECTURE_MD.read_text(encoding="utf-8") assert not re.search(r"\bADR-002\b", content), ( "Найдена висячая ссылка 'ADR-002' в ARCHITECTURE.md" ) def test_no_bare_adr_002_in_tests() -> None: """Файлы тестов (кроме этого самого файла) не должны содержать голую метку 'ADR-002'.""" pattern = re.compile(r"\bADR-002\b") this_file = Path(__file__).resolve() for path in (PROJECT_ROOT / "tests").glob("*.py"): if path.resolve() == this_file: continue # этот файл документирует задачу и легитимно упоминает ADR-002 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" )