baton/tests/test_arch_014.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

222 lines
9.9 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-014: Доработать ADR-002 и ADR-004 по замечаниям ревью.
Acceptance criteria:
1. docs/adr/ADR-002-offline-pattern.md существует в docs/adr/.
2. ADR-002: заголовок содержит «ADR-002».
3. ADR-002: дата 2026-03-20 присутствует.
4. ADR-002: статус «Accepted».
5. ADR-002: секция «Open Questions» присутствует.
6. ADR-002: Open Questions содержит вопрос о #1001 и BackgroundSync 78.75%.
7. ADR-002: Open Questions содержит ACTION item с отсылкой на #1049.
8. ADR-004: пункт о 429 содержит «exponential backoff» и ссылку на «#1046».
9. ARCHITECTURE.md: фраза «ADR-файлы хранятся в `docs/adr/`.» стоит после
заголовка «## Ссылки на ADR» и перед таблицей.
10. ARCHITECTURE.md: таблица ADR содержит строку с ADR-002.
"""
from __future__ import annotations
import re
from pathlib import Path
PROJECT_ROOT = Path(__file__).parent.parent
ADR_DIR = PROJECT_ROOT / "docs" / "adr"
ADR_002 = ADR_DIR / "ADR-002-offline-pattern.md"
ADR_004 = ADR_DIR / "ADR-004-telegram-strategy.md"
ARCHITECTURE_MD = PROJECT_ROOT / "ARCHITECTURE.md"
# ---------------------------------------------------------------------------
# Criterion 1 — ADR-002 file existence
# ---------------------------------------------------------------------------
def test_adr_002_offline_pattern_file_exists() -> None:
"""docs/adr/ADR-002-offline-pattern.md должен существовать."""
assert ADR_002.is_file(), (
f"Файл ADR-002-offline-pattern.md не найден в {ADR_DIR}"
)
# ---------------------------------------------------------------------------
# Criterion 2 — ADR-002 header
# ---------------------------------------------------------------------------
def test_adr_002_header_contains_adr_002() -> None:
"""Заголовок ADR-002 должен содержать идентификатор «ADR-002»."""
content = ADR_002.read_text(encoding="utf-8")
assert "ADR-002" in content, (
"ADR-002-offline-pattern.md не содержит идентификатор «ADR-002» в заголовке"
)
# ---------------------------------------------------------------------------
# Criterion 3 — ADR-002 date
# ---------------------------------------------------------------------------
def test_adr_002_contains_date_2026_03_20() -> None:
"""ADR-002 должен содержать дату «2026-03-20»."""
content = ADR_002.read_text(encoding="utf-8")
assert "2026-03-20" in content, (
"ADR-002-offline-pattern.md не содержит дату 2026-03-20"
)
# ---------------------------------------------------------------------------
# Criterion 4 — ADR-002 status
# ---------------------------------------------------------------------------
def test_adr_002_status_is_accepted() -> None:
"""ADR-002 должен иметь статус «Accepted»."""
content = ADR_002.read_text(encoding="utf-8")
assert "Accepted" in content, (
"ADR-002-offline-pattern.md не содержит статус «Accepted»"
)
# ---------------------------------------------------------------------------
# Criterion 5 — ADR-002 Open Questions section
# ---------------------------------------------------------------------------
def test_adr_002_has_open_questions_section() -> None:
"""ADR-002 должен содержать секцию «Open Questions»."""
content = ADR_002.read_text(encoding="utf-8")
assert "Open Questions" in content, (
"ADR-002-offline-pattern.md не содержит секцию «Open Questions»"
)
# ---------------------------------------------------------------------------
# Criterion 6 — Open Questions: #1001 and BackgroundSync 78.75%
# ---------------------------------------------------------------------------
def test_adr_002_open_questions_references_decision_1001() -> None:
"""Open Questions ADR-002 должен ссылаться на решение #1001."""
content = ADR_002.read_text(encoding="utf-8")
assert "#1001" in content, (
"ADR-002-offline-pattern.md Open Questions не содержит ссылку на решение #1001"
)
def test_adr_002_open_questions_mentions_backgroundsync_coverage() -> None:
"""Open Questions ADR-002 должен упоминать покрытие BackgroundSync 78.75%."""
content = ADR_002.read_text(encoding="utf-8")
assert "78.75" in content, (
"ADR-002-offline-pattern.md Open Questions не содержит покрытие «78.75%» "
"(BackgroundSync, caniuse март 2026)"
)
# ---------------------------------------------------------------------------
# Criterion 7 — Open Questions: ACTION item with #1049
# ---------------------------------------------------------------------------
def test_adr_002_open_questions_has_action_item() -> None:
"""Open Questions ADR-002 должен содержать явный ACTION item (конвенция #1049)."""
content = ADR_002.read_text(encoding="utf-8")
# Конвенция #1049: строки Open Questions с устаревшими решениями должны содержать ACTION:
assert re.search(r"ACTION:", content), (
"ADR-002-offline-pattern.md Open Questions не содержит маркер «ACTION:» "
"— нарушение конвенции #1049"
)
def test_adr_002_action_item_references_decision_1049() -> None:
"""ACTION item в ADR-002 должен ссылаться на конвенцию #1049."""
content = ADR_002.read_text(encoding="utf-8")
assert "#1049" in content, (
"ADR-002-offline-pattern.md не содержит ссылку на конвенцию #1049 в ACTION item"
)
# ---------------------------------------------------------------------------
# Criterion 8 — ADR-004: exponential backoff + #1046
# ---------------------------------------------------------------------------
def test_adr_004_retry_after_mentions_exponential_backoff() -> None:
"""Пункт о 429 в ADR-004 должен упоминать «exponential backoff»."""
content = ADR_004.read_text(encoding="utf-8")
# Проверяем, что "exponential backoff" присутствует в контексте retry_after
retry_section = re.search(
r"retry_after[^\n]*", content, re.IGNORECASE
)
assert retry_section is not None, (
"ADR-004 не содержит строки с упоминанием retry_after"
)
# Ищем exponential backoff в пределах абзаца о 429
para_429 = re.search(
r"(?:429|retry_after)[^\n]*(?:\n[^\n]+)*",
content,
re.IGNORECASE,
)
assert para_429 is not None
assert "exponential backoff" in para_429.group(0).lower(), (
"Пункт о retry_after/429 в ADR-004 не содержит «exponential backoff» — "
"требуется дополнение согласно решению #1046"
)
def test_adr_004_exponential_backoff_references_decision_1046() -> None:
"""Упоминание exponential backoff в ADR-004 должно ссылаться на решение #1046."""
content = ADR_004.read_text(encoding="utf-8")
assert "#1046" in content, (
"ADR-004 не содержит ссылку на решение #1046 рядом с exponential backoff"
)
# ---------------------------------------------------------------------------
# Criterion 9 — ARCHITECTURE.md: intro sentence in ADR section
# ---------------------------------------------------------------------------
def test_architecture_md_adr_section_has_intro_sentence() -> None:
"""Секция «Ссылки на ADR» в ARCHITECTURE.md должна начинаться с вводной фразы о пути docs/adr/."""
content = ARCHITECTURE_MD.read_text(encoding="utf-8")
# Ищем вводную фразу непосредственно после заголовка
pattern = re.compile(
r"##\s+Ссылки на ADR\s*\n+(?:[^\n]*\n)*?.*ADR-файлы хранятся в `docs/adr/`",
re.MULTILINE,
)
assert pattern.search(content), (
"ARCHITECTURE.md: секция «Ссылки на ADR» не содержит вводную фразу "
"«ADR-файлы хранятся в `docs/adr/`.»"
)
# ---------------------------------------------------------------------------
# Criterion 10 — ARCHITECTURE.md: ADR-002 row in table
# ---------------------------------------------------------------------------
def test_architecture_md_adr_table_contains_adr_002_row() -> None:
"""Таблица ADR в ARCHITECTURE.md должна содержать строку для ADR-002."""
content = ARCHITECTURE_MD.read_text(encoding="utf-8")
# Ищем строку таблицы с ADR-002 и ссылкой на файл
assert re.search(
r"\|\s*\[ADR-002\]\(docs/adr/ADR-002-offline-pattern\.md\)", content
), (
"ARCHITECTURE.md не содержит строки таблицы с "
"[ADR-002](docs/adr/ADR-002-offline-pattern.md)"
)
def test_architecture_md_adr_002_row_has_accepted_status() -> None:
"""Строка ADR-002 в таблице ARCHITECTURE.md должна иметь статус Accepted."""
content = ARCHITECTURE_MD.read_text(encoding="utf-8")
row_match = re.search(
r"\|\s*\[ADR-002\].*?\|\s*([^|]+)\|\s*(Accepted|Superseded|Draft)\s*\|",
content,
)
assert row_match, (
"Строка ADR-002 в таблице ARCHITECTURE.md не найдена или не содержит поля статуса"
)
assert "Accepted" in row_match.group(0), (
"Статус строки ADR-002 в ARCHITECTURE.md должен быть «Accepted»"
)