153 lines
10 KiB
Markdown
153 lines
10 KiB
Markdown
---
|
||
type: review
|
||
work_item_id: ET-015
|
||
verdict: APPROVED
|
||
version: 1
|
||
---
|
||
|
||
# Review ET-015 — Healthcheck enduro-trails-app (python urllib one-liner)
|
||
|
||
**Branch:** `feature/ET-015-healthcheck-enduro-trails-app-`
|
||
**Base:** `main`
|
||
**Reviewer:** agent:reviewer
|
||
**Date:** 2026-06-05
|
||
|
||
## Что проверял
|
||
|
||
- TRZ: `docs/work-items/ET-015/02-trz.md` (особенно §3.1, §3.2, §3.3, R-1..R-5)
|
||
- AC: `docs/work-items/ET-015/03-acceptance-criteria.md` (AC-01..AC-10)
|
||
- ADR: `docs/work-items/ET-015/06-adr/ADR-020-healthcheck-via-python-urllib.md`
|
||
- Глобальный ADR-индекс: `docs/architecture/adr/README.md`
|
||
- PR diff (`git diff main..HEAD`): `docker-compose.yml`, `CHANGELOG.md`,
|
||
`docs/architecture/adr/README.md`, артефакты `docs/work-items/ET-015/**`,
|
||
`tests/static/test_healthcheck_compose.py`,
|
||
`tests/unit/test_healthcheck_oneliner.py`.
|
||
- Запуск тестов: `pytest tests/static/test_healthcheck_compose.py
|
||
tests/unit/test_healthcheck_oneliner.py -v` → **16 passed**.
|
||
|
||
## Соответствие ТЗ
|
||
|
||
| Пункт TRZ | Ожидание | Факт в diff | Статус |
|
||
|-----------|----------|-------------|--------|
|
||
| §3.1 | YAML-массив `["CMD","python","-c", "<one-liner>"]` + `start_period: 20s`, `interval/timeout/retries` сохранены | `docker-compose.yml` lines 22–31 — байт-в-байт совпадает с §3.1 ADR-020 | ✓ |
|
||
| §3.2 | Dockerfile НЕ меняется | `git diff main..HEAD -- Dockerfile` пуст | ✓ |
|
||
| §3.3 | `src/api/main.py` НЕ меняется | `git diff main..HEAD -- src/api/main.py` пуст | ✓ |
|
||
| R-1 | Затронут только `app.healthcheck`, прочие поля сервиса не тронуты | Подтверждено diff'ом — ports/volumes/environment не сдвинуты | ✓ |
|
||
| R-2 | Изменение не требует `docker compose build` | Образ не меняется, команда исполняется существующим `python` интерпретатором | ✓ |
|
||
| R-3 | Никаких ENV для пути healthcheck | URL зашит литералом | ✓ |
|
||
| R-4 | ADR в `06-adr/` + запись в CHANGELOG | `ADR-020-healthcheck-via-python-urllib.md` + Unreleased/Fixed в `CHANGELOG.md` | ✓ |
|
||
| R-5 | YAML валидный | `yaml.safe_load(open("docker-compose.yml"))` парсит без ошибок (проверено) | ✓ |
|
||
|
||
## Соответствие Acceptance Criteria
|
||
|
||
| AC | Тест | Результат |
|
||
|----|------|-----------|
|
||
| AC-03 «нет curl в healthcheck» | ST-01 (`test_st01_healthcheck_does_not_use_curl`) | PASS |
|
||
| AC-04 «Dockerfile не ставит curl/wget» | ST-02 (`test_st02_dockerfile_does_not_apt_install_curl_or_wget`) + IT-04 (manual) | PASS (static) |
|
||
| AC-05 «честно фиксирует unhealthy» | UT-02 (`test_ut02_returns_nonzero_when_port_unused`) + IT-03 (manual) | PASS (unit) |
|
||
| AC-06 «stdlib python one-liner» | ST-03, UT-01, UT-03 (4 параметризации: 301/404/500/503) | PASS |
|
||
| AC-07 «внутренний timeout < внешнего» | ST-04 (`test_st04_internal_timeout_less_than_external`) — `3 < 5` | PASS |
|
||
| AC-08 «/api/health не сломан» | `git diff main..HEAD -- src/api/main.py` пуст; E2E-02 (manual) | PASS (static) |
|
||
| AC-09 «CHANGELOG обновлён» | ST-06 (`test_st06_changelog_mentions_et015`) | PASS |
|
||
| AC-10 «ADR зафиксирован» | ST-07 (`test_st07_adr_exists`) + ручная проверка содержимого ADR-020 | PASS |
|
||
| AC-01, AC-02 «healthy после деплоя / стабилен 10 минут» | IT-01/IT-02/E2E-01 — оператор/deployer | Pending (вне review) |
|
||
|
||
Замечание: AC-01/AC-02 закрываются только на live-среде (deployer/ops после
|
||
`make deploy-test`); это явно зафиксировано в плане тестов (`done_when`).
|
||
Review не блокирует — статические + unit-проверки полностью покрывают всё,
|
||
что можно проверить из ветки.
|
||
|
||
## Соответствие ADR-020
|
||
|
||
- §«Решение» п.1 — YAML-блок 1:1 совпадает с фактическим `docker-compose.yml`.
|
||
- §«Решение» п.2 — Dockerfile не тронут ✓.
|
||
- §«Решение» п.3 — `main.py` не тронут ✓.
|
||
- §«Решение» п.4 — `gps-collector` healthcheck не получает (в diff'е сервис
|
||
не меняется) ✓.
|
||
- §«Решение» п.5 — `CHANGELOG.md` Unreleased/Fixed содержит ET-015 + строку
|
||
`fix(infra): use python urllib for container healthcheck (ET-015)` ✓.
|
||
- Глобальный индекс ADR (`docs/architecture/adr/README.md`) пополнен строкой
|
||
ADR-020 ✓ (соблюдено процессное требование).
|
||
- Альтернативы B/C/D/E явно отклонены и не «протекли» в реализацию (curl/wget
|
||
не появились, отдельный `scripts/healthcheck.py` не создан, `HEALTHCHECK`
|
||
директива в Dockerfile не добавлена) ✓.
|
||
|
||
## Качество кода
|
||
|
||
- **YAML.** Используется `CMD` (массив), а не `CMD-SHELL`. Корректно: Docker
|
||
выполняет `exec`-ом без shell-парсинга, экранирование не нужно.
|
||
- **One-liner.** `import urllib.request, sys; sys.exit(0 if
|
||
urllib.request.urlopen(URL, timeout=3).status == 200 else 1)` —
|
||
компактно, без побочных эффектов, исключения корректно превращаются в
|
||
ненулевой exit code, что и нужно Docker'у.
|
||
- **`start_period: 20s`** добавлен — оправдан в ADR/TRZ, защищает от ложных
|
||
фейлов в первые секунды старта uvicorn.
|
||
- **Diff минимален и хирургичен.** Затронут ровно один логический блок —
|
||
это и есть «minor-change» по классификации ADR-020 §«Классификация».
|
||
|
||
## Качество тестов
|
||
|
||
- **`tests/static/test_healthcheck_compose.py`** (10 тестов):
|
||
- 6 первичных (ST-01..ST-04, ST-06, ST-07) с явной привязкой к AC и
|
||
источникам правды в docstring.
|
||
- 3 регрессивных: проверка локального URL, наличие `start_period`,
|
||
параметризованная проверка инвариантов `interval ≥ 30`, `retries ≥ 3`
|
||
(защита ADR-020 «инвариант: параметры не уменьшаются»).
|
||
- Чёрный список сторонних пакетов (`requests/httpx/aiohttp/urllib3`)
|
||
через `\b<pkg>\b` — корректный приём против ложных совпадений
|
||
подстроками.
|
||
- **`tests/unit/test_healthcheck_oneliner.py`** (6 тестов):
|
||
- Ключевая фишка: код one-liner'а **читается из `docker-compose.yml`**
|
||
и URL подменяется через `_retarget()` — под тест уходит ровно та же
|
||
логика, что и в проде. Если в compose кто-то поменяет one-liner и
|
||
сломает контракт exit-code, эти тесты упадут.
|
||
- UT-01 проверяет `exit 0` на HTTP 200, UT-02 — `exit ≠ 0` при пустом
|
||
порту, UT-03 параметризован по 301/404/500/503 (защита от подмены
|
||
`== 200` на `< 400` или подобное).
|
||
- Мок-сервер на `http.server` — без внешних зависимостей, без флакки.
|
||
- Тесты **запущены локально** (`pytest -v`): **16 passed** за 2.89s.
|
||
|
||
## Findings
|
||
|
||
### P0 (blocker)
|
||
|
||
Нет.
|
||
|
||
### P1 (must-fix)
|
||
|
||
Нет.
|
||
|
||
### P2 (should-fix)
|
||
|
||
Нет.
|
||
|
||
### P3 (nice-to-have)
|
||
|
||
- **P3-1.** `CHANGELOG.md` исторически содержит **два** `## [Unreleased]`
|
||
заголовка (строки 6 и 151) — это унаследованная проблема репозитория,
|
||
PR ET-015 её не вносит и не усугубляет. Просто фиксирую — стоит когда-нибудь
|
||
устранить в отдельной задаче `docs:`. ST-06 на этом не ломается, потому что
|
||
ищет ET-015 в любой части файла, а не «строго в верхней Unreleased».
|
||
- **P3-2.** В TRZ §1 формулировка цели говорит про «HTTP-код **2xx** как
|
||
healthy», но §3.1 (и реализация) проверяют именно `status == 200`. На
|
||
практике `/api/health` отдаёт 200, и UT-03 явно фиксирует поведение
|
||
для 301/404/500/503 → unhealthy, что согласуется с ADR-020. Это
|
||
ожидаемое сужение, зафиксированное в ADR-020 §«Решение»; стоит лишь
|
||
иметь в виду как документационную «шероховатость» в TRZ. Менять
|
||
поведение **не нужно**: иначе сломается часть UT-03 (301).
|
||
- **P3-3.** `urllib.request.urlopen(...).status` — объект ответа не
|
||
закрывается (нет `with`). Для одноразового процесса healthcheck это
|
||
безопасно (process завершится через `sys.exit`), но из стилистических
|
||
соображений можно когда-нибудь обернуть в `with`. Не блокирует
|
||
и не входит в скоуп ET-015.
|
||
|
||
## Вердикт
|
||
|
||
**APPROVED.** Все P0/P1/P2 пусты. Реализация 1-в-1 соответствует TRZ §3.1
|
||
и ADR-020 §«Решение»; не выходит за рамки BRD §6/§7; тесты адекватно
|
||
покрывают статические инварианты и поведение exit-кода one-liner'а.
|
||
Финальные AC-01/AC-02 закрываются на этапе deploy на mva154 — это
|
||
ожидаемо и зафиксировано в `04-test-plan.yaml::done_when`.
|
||
|
||
Можно передавать дальше (tester → deployer).
|