257 lines
11 KiB
YAML
257 lines
11 KiB
YAML
# Test Plan: ET-015 — Healthcheck enduro-trails-app
|
||
#
|
||
# Базовые документы:
|
||
# - 01-brd.md
|
||
# - 02-trz.md
|
||
# - 03-acceptance-criteria.md
|
||
#
|
||
# Категории тестов:
|
||
# unit — изолированный, без Docker
|
||
# integration — с реальным docker compose
|
||
# e2e — на тестовой среде mva154
|
||
# static — статический анализ файлов в репо
|
||
|
||
work_item: ET-015
|
||
version: "1.0"
|
||
date: "2026-06-05"
|
||
|
||
tests:
|
||
|
||
# ─────────────────────────────────────────────────────────────────
|
||
# STATIC
|
||
# ─────────────────────────────────────────────────────────────────
|
||
|
||
- id: ST-01
|
||
name: "docker-compose.yml не содержит curl в healthcheck"
|
||
type: static
|
||
covers: [AC-03]
|
||
given: "Ветка feature/ET-015-... в рабочем дереве"
|
||
steps:
|
||
- "grep -nE '\\bcurl\\b' docker-compose.yml || true"
|
||
expected:
|
||
- "В выводе нет строк, попадающих в секцию app.healthcheck."
|
||
automatable: true
|
||
tooling: "make lint / отдельная проверка в scripts/"
|
||
|
||
- id: ST-02
|
||
name: "Dockerfile не устанавливает curl/wget"
|
||
type: static
|
||
covers: [AC-04]
|
||
given: "Текущий Dockerfile"
|
||
steps:
|
||
- "grep -nE 'apt-get +install.*\\b(curl|wget)\\b' Dockerfile || true"
|
||
expected:
|
||
- "Совпадений нет."
|
||
automatable: true
|
||
|
||
- id: ST-03
|
||
name: "Healthcheck использует python и stdlib"
|
||
type: static
|
||
covers: [AC-06]
|
||
given: "YAML docker-compose.yml"
|
||
steps:
|
||
- "Распарсить YAML (python -c 'import yaml,sys; print(yaml.safe_load(open(\"docker-compose.yml\"))[\"services\"][\"app\"][\"healthcheck\"][\"test\"])')"
|
||
expected:
|
||
- "Массив начинается с ['CMD', 'python', '-c', ...]."
|
||
- "Четвёртый элемент содержит 'urllib.request' и 'sys.exit'."
|
||
- "Не импортируются сторонние пакеты (нет 'requests', 'httpx', и т.п.)."
|
||
automatable: true
|
||
|
||
- id: ST-04
|
||
name: "Внутренний timeout urlopen меньше внешнего timeout healthcheck"
|
||
type: static
|
||
covers: [AC-07]
|
||
given: "Парсенный healthcheck"
|
||
steps:
|
||
- "Извлечь N из 'timeout=N' внутри строки python -c."
|
||
- "Извлечь M из YAML-поля healthcheck.timeout (например, '5s' → 5)."
|
||
expected:
|
||
- "N < M (по умолчанию 3 < 5)."
|
||
automatable: true
|
||
|
||
- id: ST-05
|
||
name: "Эндпоинт /api/health не сломан изменениями"
|
||
type: static
|
||
covers: [AC-08]
|
||
given: "PR с фиксом против main"
|
||
steps:
|
||
- "git diff main -- src/api/main.py | grep -E '^[+-].*(api/health|async def health)' || true"
|
||
expected:
|
||
- "Либо нет изменений, либо рефакторинг без слома контракта."
|
||
automatable: true
|
||
|
||
- id: ST-06
|
||
name: "CHANGELOG обновлён"
|
||
type: static
|
||
covers: [AC-09]
|
||
given: "CHANGELOG.md"
|
||
steps:
|
||
- "grep -nE 'ET-015' CHANGELOG.md"
|
||
expected:
|
||
- "Минимум одна строка с упоминанием ET-015 в Unreleased/ближайшей версии."
|
||
automatable: true
|
||
|
||
- id: ST-07
|
||
name: "ADR существует"
|
||
type: static
|
||
covers: [AC-10]
|
||
given: "docs/work-items/ET-015/06-adr/"
|
||
steps:
|
||
- "ls docs/work-items/ET-015/06-adr/*.md"
|
||
expected:
|
||
- "Минимум один .md-файл с описанием решения healthcheck-via-python."
|
||
automatable: true
|
||
|
||
# ─────────────────────────────────────────────────────────────────
|
||
# UNIT
|
||
# ─────────────────────────────────────────────────────────────────
|
||
|
||
- id: UT-01
|
||
name: "Python one-liner возвращает 0 при HTTP 200"
|
||
type: unit
|
||
covers: [AC-06]
|
||
given: "Запущен локально `make dev` (uvicorn на :5556) или мок-сервер"
|
||
steps:
|
||
- "Скопировать строку python -c '...' из healthcheck."
|
||
- "Запустить `python -c '...'` на хосте против http://localhost:5556/api/health."
|
||
- "Проверить $? == 0."
|
||
expected:
|
||
- "exit code = 0."
|
||
automatable: true
|
||
|
||
- id: UT-02
|
||
name: "Python one-liner возвращает не 0 при недоступном порту"
|
||
type: unit
|
||
covers: [AC-05, AC-06]
|
||
given: "Никто не слушает :5556 (uvicorn остановлен)"
|
||
steps:
|
||
- "Запустить ту же команду python -c '...'"
|
||
- "Проверить exit code."
|
||
expected:
|
||
- "exit code != 0 (URLError → ненулевой код)."
|
||
automatable: true
|
||
|
||
- id: UT-03
|
||
name: "Python one-liner возвращает не 0 при HTTP 500"
|
||
type: unit
|
||
covers: [AC-06]
|
||
given: "Мок-сервер на :5556, отдающий 500 на /api/health"
|
||
steps:
|
||
- "Запустить python one-liner."
|
||
expected:
|
||
- "exit code != 0 (HTTPError или sys.exit(1))."
|
||
automatable: true
|
||
|
||
# ─────────────────────────────────────────────────────────────────
|
||
# INTEGRATION
|
||
# ─────────────────────────────────────────────────────────────────
|
||
|
||
- id: IT-01
|
||
name: "docker compose up: контейнер становится healthy за ≤ 120s"
|
||
type: integration
|
||
covers: [AC-01]
|
||
given: "Чистая локальная машина с Docker и доступом к данным /home/slin/enduro-trails/data"
|
||
steps:
|
||
- "docker compose down -v"
|
||
- "docker compose up -d app"
|
||
- "Запустить цикл: `while true; do status=$(docker inspect $(docker compose ps -q app) --format '{{.State.Health.Status}}'); echo $status; [ \"$status\" = \"healthy\" ] && break; sleep 5; done` с таймаутом 120s"
|
||
expected:
|
||
- "Статус становится healthy в течение 120 секунд."
|
||
- "FailingStreak == 0 после перехода."
|
||
automatable: true
|
||
|
||
- id: IT-02
|
||
name: "Healthy остаётся стабильным 5 минут"
|
||
type: integration
|
||
covers: [AC-02]
|
||
given: "Контейнер в статусе healthy"
|
||
steps:
|
||
- "Подождать 5 минут (10 циклов healthcheck при interval=30s)."
|
||
- "docker inspect ... --format '{{.State.Health.Status}}'"
|
||
- "docker inspect ... --format '{{json .State.Health.Log}}' | jq '.[-5:] | map(.ExitCode)'"
|
||
expected:
|
||
- "Статус == healthy."
|
||
- "Все 5 последних ExitCode == 0."
|
||
|
||
- id: IT-03
|
||
name: "Переход в unhealthy при остановке приложения"
|
||
type: integration
|
||
covers: [AC-05]
|
||
given: "Контейнер healthy"
|
||
steps:
|
||
- "docker exec <container> sh -c 'pkill -STOP -f uvicorn' (или эквивалент: остановить главный процесс)"
|
||
- "Ждать до 120 секунд."
|
||
- "docker inspect ... --format '{{.State.Health.Status}}'"
|
||
expected:
|
||
- "Статус становится unhealthy в течение 120 секунд."
|
||
- "FailingStreak >= retries (>= 3)."
|
||
teardown:
|
||
- "docker compose restart app — вернуть в рабочее состояние."
|
||
|
||
- id: IT-04
|
||
name: "Healthcheck не требует ребилда образа"
|
||
type: integration
|
||
covers: [AC-04]
|
||
given: "Старый образ (с поломанным curl-healthcheck) уже собран локально"
|
||
steps:
|
||
- "Применить новый docker-compose.yml без `docker compose build`."
|
||
- "docker compose up -d app (только пересоздание контейнера)."
|
||
- "Подождать до 120 секунд."
|
||
expected:
|
||
- "Контейнер healthy без пересборки образа."
|
||
|
||
# ─────────────────────────────────────────────────────────────────
|
||
# E2E (на mva154)
|
||
# ─────────────────────────────────────────────────────────────────
|
||
|
||
- id: E2E-01
|
||
name: "После make deploy-test контейнер healthy на mva154"
|
||
type: e2e
|
||
covers: [AC-01, AC-02]
|
||
given: "Ветка смерджена в main, CI прошёл, выполнен make deploy-test"
|
||
steps:
|
||
- "ssh mva154 'docker inspect enduro-trails-app-1 --format \"{{.State.Health.Status}}\"'"
|
||
- "Повторить через 5 и 10 минут."
|
||
expected:
|
||
- "Все три замера: healthy."
|
||
- "FailingStreak == 0."
|
||
automatable: false
|
||
owner: "ops"
|
||
|
||
- id: E2E-02
|
||
name: "Приложение продолжает отвечать снаружи"
|
||
type: e2e
|
||
covers: [AC-08]
|
||
given: "Контейнер healthy на mva154"
|
||
steps:
|
||
- "curl -sS -o /dev/null -w '%{http_code} %{time_total}\\n' https://openclaw.mva154.duckdns.org/enduro/api/health"
|
||
expected:
|
||
- "HTTP 200, time_total < 1s."
|
||
automatable: true
|
||
|
||
# ─────────────────────────────────────────────────────────────────
|
||
# Покрытие критериев приёмки
|
||
# ─────────────────────────────────────────────────────────────────
|
||
|
||
coverage_matrix:
|
||
AC-01: [IT-01, E2E-01]
|
||
AC-02: [IT-02, E2E-01]
|
||
AC-03: [ST-01]
|
||
AC-04: [ST-02, IT-04]
|
||
AC-05: [UT-02, IT-03]
|
||
AC-06: [ST-03, UT-01, UT-03]
|
||
AC-07: [ST-04]
|
||
AC-08: [ST-05, E2E-02]
|
||
AC-09: [ST-06]
|
||
AC-10: [ST-07]
|
||
|
||
# ─────────────────────────────────────────────────────────────────
|
||
# Definition of Done для тестирования
|
||
# ─────────────────────────────────────────────────────────────────
|
||
|
||
done_when:
|
||
- "Все ST-* и UT-* проходят в make test / CI."
|
||
- "IT-01, IT-02, IT-03, IT-04 пройдены вручную или в integration-CI."
|
||
- "E2E-01 подтверждён ops после deploy-test."
|
||
- "E2E-02 возвращает HTTP 200."
|