---
type: test-report
work_item_id: ET-015
verdict: READY_TO_DEPLOY
version: 1
---

# Test Report ET-015 — Healthcheck enduro-trails-app

**Branch:** `feature/ET-015-healthcheck-enduro-trails-app-`
**Base:** `main`
**Tester:** agent:tester
**Date:** 2026-06-05
**Test plan:** [04-test-plan.yaml](04-test-plan.yaml)
**Acceptance criteria:** [03-acceptance-criteria.md](03-acceptance-criteria.md)

## TL;DR

**16/16** ST + UT тестов пройдено. E2E-02 (`/api/health` снаружи на mva154)
возвращает `HTTP 200` за **0.111 s**. Эндпоинт `src/api/main.py::health()` не
изменён. Интеграционные IT-01..IT-04 и E2E-01 закрываются на этапе деплоя
(требуют live docker compose / ssh mva154) — это явно заложено в
`04-test-plan.yaml::done_when`.

**Вердикт: READY_TO_DEPLOY.**

## Окружение

| Параметр | Значение |
|----------|----------|
| Python | 3.12.13 |
| pytest | 8.3.3 |
| Repo HEAD | `d501bcb` (reviewer auto-commit) |
| Доступ к mva154 | через HTTPS (curl недоступен → проверка через python urllib) |
| Docker в окружении tester | **недоступен** (`docker: command not found`) |

Pre-flight: `GET https://openclaw.mva154.duckdns.org/enduro/api/health` →
`HTTP 200`, body `{"status":"ok","db_path":"/app/data/centralfederal.sqlite","db_exists":true}`,
time `0.111 s`. Тестовая среда жива.

## Результаты

### Static (ST-*) и Unit (UT-*)

Запуск:

```
python3 -m pytest tests/static/test_healthcheck_compose.py \
                  tests/unit/test_healthcheck_oneliner.py -v
```

Итог: **16 passed in 2.92s**.

| ID | Имя | AC | Результат |
|----|-----|----|-----------|
| ST-01 | `test_st01_healthcheck_does_not_use_curl` | AC-03 | PASS |
| ST-02 | `test_st02_dockerfile_does_not_apt_install_curl_or_wget` | AC-04 | PASS |
| ST-03 | `test_st03_healthcheck_uses_python_and_stdlib` | AC-06 | PASS |
| ST-04 | `test_st04_internal_timeout_less_than_external` (3 < 5) | AC-07 | PASS |
| ST-05 | `git diff main..HEAD -- src/api/main.py` (empty) | AC-08 | PASS |
| ST-06 | `test_st06_changelog_mentions_et015` | AC-09 | PASS |
| ST-07 | `test_st07_adr_exists` (ADR-020) | AC-10 | PASS |
| ST-reg | `test_app_healthcheck_target_is_local_api_health` | regression | PASS |
| ST-reg | `test_app_healthcheck_has_start_period` (20s) | regression | PASS |
| ST-reg | `test_app_healthcheck_preserves_baseline_params[interval-30]` | regression | PASS |
| ST-reg | `test_app_healthcheck_preserves_baseline_params[retries-3]` | regression | PASS |
| UT-01 | `test_ut01_returns_zero_on_http_200` | AC-06 | PASS |
| UT-02 | `test_ut02_returns_nonzero_when_port_unused` | AC-05, AC-06 | PASS |
| UT-03 | `test_ut03_returns_nonzero_on_non_2xx[301]` | AC-06 | PASS |
| UT-03 | `test_ut03_returns_nonzero_on_non_2xx[404]` | AC-06 | PASS |
| UT-03 | `test_ut03_returns_nonzero_on_non_2xx[500]` | AC-06 | PASS |
| UT-03 | `test_ut03_returns_nonzero_on_non_2xx[503]` | AC-06 | PASS |

Важная техническая деталь: unit-тесты one-liner'а **читают исходную
команду из `docker-compose.yml`** (а не дублируют её) — если в будущем
кто-то изменит one-liner в compose и сломает контракт exit-кода, UT-01/02/03
немедленно покраснеют.

### Integration (IT-*) — на стороне deployer

IT-01..IT-04 требуют локального `docker compose` и доступа к
`/home/slin/enduro-trails/data` — в среде tester'а Docker недоступен
(`docker: command not found`). Согласно `04-test-plan.yaml`
эти тесты автоматизируемые, но физически выполняются:

- IT-01 (healthy за ≤ 120s) — закрывается deployer'ом сразу после
  `make deploy-test` на mva154.
- IT-02 (стабилен 5 минут) — закрывается мониторингом после деплоя.
- IT-03 (переход в unhealthy при остановке uvicorn) — рекомендуется
  отдельным smoke-шагом в post-deploy чек-листе; **не блокирует deploy**,
  т.к. unit UT-02 уже доказал, что one-liner возвращает ненулевой exit-code
  при недоступном порту.
- IT-04 (не требует ребилда) — статически подтверждается тем, что
  `git diff main..HEAD -- Dockerfile` пуст, образ не меняется
  (что также проверяет ST-02).

**Передача:** IT-01/IT-02/IT-03 → deployer (см. ниже секцию «Pending»).

### E2E

| ID | Имя | Результат |
|----|-----|-----------|
| E2E-01 | После `make deploy-test` контейнер healthy на mva154 (3 замера) | **Pending** — закрывается deployer'ом |
| E2E-02 | Приложение продолжает отвечать снаружи | **PASS** — `HTTP 200`, `0.111 s` (см. pre-flight) |

### Полный pytest-набор репозитория

`python3 -m pytest tests/` не собирается из-за пред-существующих
проблем окружения: отсутствуют `shapely`, `defusedxml`,
`mapbox_vector_tile` (15 collection errors). Это **не связано с ET-015**
(изменение чисто инфраструктурное — `docker-compose.yml`, CHANGELOG,
docs/tests; `src/api/` не трогается). Зафиксировано как наблюдение,
не блокирующее этот work item.

## Visual / UI тесты

Файл `docs/work-items/ET-015/04b-ui-test-cases.md` **отсутствует**
(инфраструктурная задача, UI не задействован). Шаг 4 теста-плана
пропущен согласно инструкции tester'а.

## Покрытие Acceptance Criteria

| AC | Тесты | Статус |
|----|-------|--------|
| AC-01 | IT-01, E2E-01 | Pending (deployer) |
| AC-02 | IT-02, E2E-01 | Pending (deployer) |
| AC-03 | ST-01 | **PASS** |
| AC-04 | ST-02, IT-04 | **PASS (static)** |
| AC-05 | UT-02, IT-03 | **PASS (unit)** + Pending (IT-03 на deployer) |
| AC-06 | ST-03, UT-01, UT-03 (4 кейса) | **PASS** |
| AC-07 | ST-04 (3 < 5) | **PASS** |
| AC-08 | ST-05, E2E-02 | **PASS** |
| AC-09 | ST-06 | **PASS** |
| AC-10 | ST-07 | **PASS** |

## Findings

### P0 (blocker)
Нет.

### P1 (must-fix)
Нет.

### P2 (should-fix)
Нет.

### P3 (nice-to-have)

- **P3-T1.** Pre-существующие сбои окружения при сборе общего
  pytest-набора (`shapely`, `defusedxml`, `mapbox_vector_tile`
  отсутствуют). Не относится к ET-015, но мешает запускать общий
  smoke за один проход. Рекомендуется отдельной задачей привести
  test-окружение в порядок (CI-образ или `requirements-test.txt`).
- **P3-T2.** В среде tester'а отсутствует `curl` — пришлось делать
  E2E-02 через `python -m urllib.request`. Результат идентичен
  (HTTP 200, ~111 ms), но в чек-листе деплоя стоит оставить
  команду `curl -sS` именно как написана в плане.

(Все три P3 из review (`12-review.md`) перенесены как известные
вопросы документации/стиля, не блокирующие.)

## Pending (передаётся deployer'у)

Эти проверки **обязательны** до закрытия задачи, но физически
выполняются на mva154 после `make deploy-test`:

1. **IT-01 / E2E-01 — healthy за ≤ 120 s после деплоя.**
   ```
   ssh mva154 'docker inspect enduro-trails-app-1 \
     --format "{{.State.Health.Status}} (streak {{.State.Health.FailingStreak}})"'
   # ожидается: healthy (streak 0)
   ```
2. **IT-02 / E2E-01 — стабилен через 5 и 10 минут.**
   Повторить команду выше через 5 и 10 минут после деплоя.
3. **IT-03 — переход в unhealthy при отказе.** *(опционально, smoke)*
   ```
   ssh mva154 'docker exec enduro-trails-app-1 sh -c "pkill -STOP -f uvicorn"'
   # подождать ≤ 120s
   ssh mva154 'docker inspect enduro-trails-app-1 --format "{{.State.Health.Status}}"'
   # ожидается: unhealthy
   ssh mva154 'docker compose restart app'   # вернуть в строй
   ```

## Команды воспроизведения

```bash
# ST + UT
python3 -m pytest tests/static/test_healthcheck_compose.py \
                  tests/unit/test_healthcheck_oneliner.py -v
# E2E-02
python3 -c "import urllib.request,time; t=time.time(); \
  r=urllib.request.urlopen('https://openclaw.mva154.duckdns.org/enduro/api/health', timeout=10); \
  print(r.status, f'{time.time()-t:.3f}s', r.read().decode())"
```

## Вердикт

**READY_TO_DEPLOY (stage:ready-to-deploy).**

Все автоматизируемые статические и unit-проверки пройдены (16/16).
Эндпоинт `/api/health` на test-среде жив, отдаёт 200 за ~111 ms.
`src/api/main.py` и `Dockerfile` не изменены — поведение приложения
гарантированно сохранено. P0/P1/P2 пусты. Передаю deployer'у;
финальные AC-01/AC-02 закрываются после `make deploy-test` по чек-листу
в секции «Pending».
