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:
parent
f082c75ff8
commit
2ee953866b
5 changed files with 283 additions and 37 deletions
|
|
@ -256,9 +256,12 @@ VPS (один сервер)
|
||||||
|
|
||||||
## Ссылки на ADR
|
## Ссылки на ADR
|
||||||
|
|
||||||
|
ADR-файлы хранятся в `docs/adr/`.
|
||||||
|
|
||||||
| ADR | Тема | Статус |
|
| ADR | Тема | Статус |
|
||||||
|-----|------|--------|
|
|-----|------|--------|
|
||||||
| [ADR-001](docs/adr/ADR-001-backend-stack.md) | Backend stack: FastAPI | Accepted |
|
| [ADR-001](docs/adr/ADR-001-backend-stack.md) | Backend stack: FastAPI | Accepted |
|
||||||
|
| [ADR-002](docs/adr/ADR-002-offline-pattern.md) | Offline pattern (v1): IndexedDB+BackgroundSync | Accepted |
|
||||||
| [ADR-007](docs/adr/ADR-007-offline-queue-v2.md) | Offline pattern: IndexedDB+BackgroundSync (v2) | Accepted |
|
| [ADR-007](docs/adr/ADR-007-offline-queue-v2.md) | Offline pattern: IndexedDB+BackgroundSync (v2) | Accepted |
|
||||||
| [ADR-003](docs/adr/ADR-003-auth-pattern.md) | Auth: UUID v4 + localStorage fallback | Accepted |
|
| [ADR-003](docs/adr/ADR-003-auth-pattern.md) | Auth: UUID v4 + localStorage fallback | Accepted |
|
||||||
| [ADR-004](docs/adr/ADR-004-telegram-strategy.md) | Telegram: direct sendMessage (v1) | Accepted |
|
| [ADR-004](docs/adr/ADR-004-telegram-strategy.md) | Telegram: direct sendMessage (v1) | Accepted |
|
||||||
|
|
|
||||||
41
docs/adr/ADR-002-offline-pattern.md
Normal file
41
docs/adr/ADR-002-offline-pattern.md
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# ADR-002: Паттерн офлайн-очереди
|
||||||
|
|
||||||
|
**Дата:** 2026-03-20
|
||||||
|
**Статус:** Accepted
|
||||||
|
**Автор:** Architect Agent (Kin pipeline, BATON-001)
|
||||||
|
**Решения:** #1001, #1003, #1006
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
|
||||||
|
Baton — приложение экстренного сигнала. Критичное требование: сигнал не должен быть потерян, если пользователь нажал кнопку в момент отсутствия сети (тоннель, слабый сигнал, офлайн).
|
||||||
|
|
||||||
|
Данный ADR фиксирует исходное архитектурное решение по паттерну офлайн-очереди. Актуальная реализация с деталями вариантов — в ADR-007.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Решение
|
||||||
|
|
||||||
|
**IndexedDB outbox + BackgroundSync + online event fallback**
|
||||||
|
|
||||||
|
1. Кнопка нажата → немедленная попытка `fetch('/api/signal')`
|
||||||
|
2. Ошибка или offline → запись в IndexedDB outbox
|
||||||
|
3. Trigger 1: `window.addEventListener('online', flushOutbox)` — main thread, все браузеры
|
||||||
|
4. Trigger 2: SW регистрирует `registration.sync.register('flush-outbox')` — Chromium только
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Обоснование
|
||||||
|
|
||||||
|
- **#1006:** IndexedDB — единственный вариант, доступный и в main thread, и в Service Worker. Общее хранилище исключает дублирование кода между `app.js` и `sw.js`.
|
||||||
|
- **#1003:** localStorage в iOS Safari приватном режиме бросает `SecurityError` → не подходит для надёжного офлайн-хранилища в приложении экстренного сигнала.
|
||||||
|
- **BackgroundSync (#1001):** браузер управляет повтором, flush возможен даже при закрытой вкладке (Chrome). Dual trigger (BackgroundSync + online event) страхует пользователей Safari/Firefox.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
**Вопрос о покрытии BackgroundSync:** Решение #1001 фиксирует охват BackgroundSync как ~85%. Актуальные данные caniuse (март 2026) показывают 78.75% — Safari и Firefox не поддерживают API. Это влияет на описание архитектурных гарантий: ручной online-fallback является **обязательным**, а не опциональным элементом.
|
||||||
|
|
||||||
|
ACTION: Обновить решение #1001 — изменить охват BackgroundSync с 85% до 78.75%, пометить ручной fallback как обязательный элемент архитектуры (конвенция #1049).
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
| Группа | 20 msg/минуту |
|
| Группа | 20 msg/минуту |
|
||||||
| Глобально | ~30 msg/сек |
|
| Глобально | ~30 msg/сек |
|
||||||
|
|
||||||
При превышении: HTTP 429 + `parameters.retry_after`.
|
При превышении: HTTP 429 + `parameters.retry_after`. При последовательных 429 рекомендуется exponential backoff согласно решению #1046.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,16 +28,12 @@ ADR_007 = ADR_DIR / "ADR-007-offline-queue-v2.md"
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def test_adr_002_offline_pattern_file_does_not_exist() -> None:
|
# Criterion 1 superseded by BATON-ARCH-014: ADR-002-offline-pattern.md now exists
|
||||||
"""Файл ADR-002-offline-pattern*.md не должен существовать в docs/adr/."""
|
# as a legitimate new ADR document.
|
||||||
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
|
# Criterion 2 — no stale 'ADR-002-offline-pattern' textual references in docs/
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -54,44 +50,28 @@ def test_no_adr_002_offline_pattern_in_docs() -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_no_adr_002_offline_pattern_in_architecture_md() -> None:
|
# test_no_adr_002_offline_pattern_in_architecture_md superseded by BATON-ARCH-014:
|
||||||
"""ARCHITECTURE.md не должен содержать строку 'ADR-002-offline-pattern'."""
|
# ARCHITECTURE.md now legitimately links to ADR-002-offline-pattern.md.
|
||||||
content = ARCHITECTURE_MD.read_text(encoding="utf-8")
|
# test_no_bare_adr_002_in_docs superseded: ADR-002-offline-pattern.md is a valid new ADR.
|
||||||
assert "ADR-002-offline-pattern" not in content, (
|
# test_no_bare_adr_002_in_architecture_md superseded: [ADR-002] is now a valid table row.
|
||||||
"Найдена устаревшая ссылка 'ADR-002-offline-pattern' в ARCHITECTURE.md"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Criterion 3 — no dangling bare ADR-002 references
|
# Criterion 3 — no dangling bare ADR-002 references in test files
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
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:
|
def test_no_bare_adr_002_in_tests() -> None:
|
||||||
"""Файлы тестов (кроме этого самого файла) не должны содержать голую метку 'ADR-002'."""
|
"""Файлы тестов (кроме легитимных исключений) не должны содержать голую метку 'ADR-002'."""
|
||||||
pattern = re.compile(r"\bADR-002\b")
|
pattern = re.compile(r"\bADR-002\b")
|
||||||
this_file = Path(__file__).resolve()
|
# Легитимные исключения: файлы, документирующие задачи, которые явно работают с 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"):
|
for path in (PROJECT_ROOT / "tests").glob("*.py"):
|
||||||
if path.resolve() == this_file:
|
if path.resolve() in _ALLOWED:
|
||||||
continue # этот файл документирует задачу и легитимно упоминает ADR-002
|
continue
|
||||||
content = path.read_text(encoding="utf-8")
|
content = path.read_text(encoding="utf-8")
|
||||||
assert not pattern.search(content), (
|
assert not pattern.search(content), (
|
||||||
f"Найдена висячая ссылка 'ADR-002' в {path.relative_to(PROJECT_ROOT)}"
|
f"Найдена висячая ссылка 'ADR-002' в {path.relative_to(PROJECT_ROOT)}"
|
||||||
|
|
|
||||||
222
tests/test_arch_014.py
Normal file
222
tests/test_arch_014.py
Normal 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»"
|
||||||
|
)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue