baton/tests/test_arch_004.py
Gros Frumos 2ee953866b kin: BATON-ARCH-014 Доработать ADR-002 и ADR-004 по замечаниям ревью
- Создан docs/adr/ADR-002-offline-pattern.md (Accepted, дата 2026-03-20)
  с секцией Open Questions: #1001, охват 78.75%, ACTION:/конвенция #1049
- ADR-004: добавлен "exponential backoff согласно решению #1046" к строке 429/retry_after
- ARCHITECTURE.md: добавлена вводная фраза "ADR-файлы хранятся в docs/adr/"
  и строка таблицы для ADR-002 (Accepted)
- tests/test_arch_004.py: удалены 4 теста на отсутствие ADR-002,
  устаревшие после создания нового ADR-002 (BATON-ARCH-014 supersedes)
- tests/test_arch_014.py: 14 новых тестов для критериев приёмки
- Все 216 тестов: passed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 22:05:04 +02:00

176 lines
8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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"
)