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>
This commit is contained in:
Gros Frumos 2026-03-20 22:05:04 +02:00
parent f082c75ff8
commit 2ee953866b
5 changed files with 283 additions and 37 deletions

222
tests/test_arch_014.py Normal file
View file

@ -0,0 +1,222 @@
"""
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»"
)