From a8aaf6591889c521b01772582539e71feef84754 Mon Sep 17 00:00:00 2001 From: "a.tolmachev" Date: Tue, 7 Apr 2026 12:51:09 +0300 Subject: [PATCH] chore: add automated staging smoke helper --- README.md | 6 ++ TASKS.md | 10 ++-- docs/deploy-and-staging-smoke.md | 23 ++++++++ justfile | 3 + scripts/README.md | 11 ++++ scripts/staging-smoke.sh | 99 ++++++++++++++++++++++++++++++++ 6 files changed, 147 insertions(+), 5 deletions(-) create mode 100644 scripts/README.md create mode 100644 scripts/staging-smoke.sh diff --git a/README.md b/README.md index 35b5d7f..f1beafe 100644 --- a/README.md +++ b/README.md @@ -130,3 +130,9 @@ npm run e2e ```bash just ui-e2e ``` + +Для post-deploy smoke: + +```bash +just staging-smoke https:// +``` diff --git a/TASKS.md b/TASKS.md index 69c59d0..49d4642 100644 --- a/TASKS.md +++ b/TASKS.md @@ -2,18 +2,18 @@ ## Current -### `feat/staging-regression-notes` +### `feat/next-live-staging-pass` Status: completed DoD: -- staging regression log template exists -- deploy smoke runbook links to the regression log -- README references the regression log as the canonical post-deploy notes location +- automated staging smoke helper exists +- helper validates core public routes, auth session JSON and legacy redirects +- deploy smoke runbook and README reference the helper ## Next -- `feat/next-live-staging-pass` +- `feat/live-staging-run` ## Backlog diff --git a/docs/deploy-and-staging-smoke.md b/docs/deploy-and-staging-smoke.md index 24eeb33..e5cb054 100644 --- a/docs/deploy-and-staging-smoke.md +++ b/docs/deploy-and-staging-smoke.md @@ -33,6 +33,29 @@ - включенный `CRANK_DEMO_SEED=true`, если нужен предзаполненный smoke state; - bootstrap admin credentials для входа в UI. +## 3.1. Быстрый automated smoke + +Есть helper script: + +```bash +just staging-smoke https:// +``` + +или напрямую: + +```bash +bash scripts/staging-smoke.sh https:// +``` + +Он проверяет: + +- public routes; +- `/api/auth/session` JSON contract; +- `/mcp/health`; +- legacy `/html/...` redirects. + +Это не заменяет ручной smoke pass ниже, а только быстро отсеивает грубые deploy/routing поломки. + ## 4. Server-side smoke На сервере: diff --git a/justfile b/justfile index 0839910..9d1f709 100644 --- a/justfile +++ b/justfile @@ -26,3 +26,6 @@ verify-ui: ui-e2e: cd apps/ui && npm run e2e + +staging-smoke base_url: + bash scripts/staging-smoke.sh {{base_url}} diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..7eda1eb --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,11 @@ +# Scripts + +## staging-smoke.sh + +Post-deploy smoke helper for staging/production-like environments. + +Usage: + +```bash +./scripts/staging-smoke.sh https://rmcp.itexp.me +``` diff --git a/scripts/staging-smoke.sh b/scripts/staging-smoke.sh new file mode 100644 index 0000000..31dd808 --- /dev/null +++ b/scripts/staging-smoke.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -lt 1 ]]; then + echo "usage: $0 " >&2 + echo "example: $0 https://rmcp.itexp.me" >&2 + exit 1 +fi + +BASE_URL="${1%/}" +TIMEOUT_SECONDS="${SMOKE_TIMEOUT_SECONDS:-20}" + +check_head() { + local path="$1" + local expected_prefix="${2:-2}" + local url="${BASE_URL}${path}" + local code + + code="$(curl -k -sS -o /dev/null -I -L --max-time "${TIMEOUT_SECONDS}" -w '%{http_code}' "${url}")" + printf '%-36s %s\n' "${path}" "${code}" + + if [[ "${code}" != ${expected_prefix}* ]]; then + echo "unexpected status for ${url}: ${code}" >&2 + return 1 + fi +} + +check_json() { + local path="$1" + local expected_status_one="$2" + local expected_status_two="${3:-}" + local url="${BASE_URL}${path}" + local headers + local code + local content_type + + headers="$(mktemp)" + code="$(curl -k -sS -o /dev/null -D "${headers}" --max-time "${TIMEOUT_SECONDS}" -w '%{http_code}' "${url}")" + content_type="$(awk 'BEGIN{IGNORECASE=1} /^content-type:/ {print $2}' "${headers}" | tr -d '\r' | tail -n1)" + rm -f "${headers}" + + printf '%-36s %s (%s)\n' "${path}" "${code}" "${content_type:-unknown}" + + if [[ "${code}" != "${expected_status_one}" && ( -z "${expected_status_two}" || "${code}" != "${expected_status_two}" ) ]]; then + echo "unexpected status for ${url}: ${code}" >&2 + return 1 + fi + + if [[ "${content_type}" != application/json* ]]; then + echo "unexpected content-type for ${url}: ${content_type:-missing}" >&2 + return 1 + fi +} + +check_redirect() { + local path="$1" + local expected_location_suffix="$2" + local url="${BASE_URL}${path}" + local headers + local code + local location + + headers="$(mktemp)" + code="$(curl -k -sS -o /dev/null -D "${headers}" --max-time "${TIMEOUT_SECONDS}" -w '%{http_code}' "${url}")" + location="$(awk 'BEGIN{IGNORECASE=1} /^location:/ {print $2}' "${headers}" | tr -d '\r' | tail -n1)" + rm -f "${headers}" + + printf '%-36s %s -> %s\n' "${path}" "${code}" "${location:-missing}" + + if [[ "${code}" != 30* ]]; then + echo "expected redirect for ${url}, got ${code}" >&2 + return 1 + fi + + if [[ "${location}" != *"${expected_location_suffix}" ]]; then + echo "unexpected redirect location for ${url}: ${location:-missing}" >&2 + return 1 + fi +} + +echo "staging smoke: ${BASE_URL}" +echo +echo "public routes" +check_head "/" 2 +check_head "/login" 2 +check_head "/agents" 2 +check_head "/secrets" 2 +check_head "/wizard/" 2 +check_head "/mcp/health" 2 +check_json "/api/auth/session" 200 401 + +echo +echo "legacy redirects" +check_redirect "/html/login.html" "/login" +check_redirect "/html/agents.html" "/agents" +check_redirect "/html/wizard/" "/wizard/" + +echo +echo "smoke completed"