Files
enduro-trails/docs/work-items/ET-015/04-test-plan.yaml
claude-bot c2cf8280ca
All checks were successful
CI / lint (push) Successful in 4s
CI / test (push) Successful in 9s
CI / build (push) Successful in 3s
analyst(ET): auto-commit from analyst run_id=101
2026-06-05 15:11:28 +00:00

257 lines
11 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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."