test(e2e): add the Compose smoke test of the running web process
Why: - ADR-0016 reserves Compose for a serialized smoke test of the running web process. Nothing else exercises the deployment steps, a real HTTP server, or the real logging configuration, which every in-process test no-ops. Changes: - scripts/smoke.sh brings up Compose, runs both bootstrap steps, provisions a throwaway tenant, starts uvicorn, and drives the test against it - the test skips unless SMOKE_BASE_URL is set, so `uv run pytest` never invokes Compose; it asserts the ADR-0011 JSON log sink Impact: - a pre-release gate, not a per-PR one Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
98
scripts/smoke.sh
Executable file
98
scripts/smoke.sh
Executable file
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env bash
|
||||
# Serialized operational smoke test of the running web process (ADR-0016, plan
|
||||
# 001 Phase 6).
|
||||
#
|
||||
# ./scripts/smoke.sh
|
||||
#
|
||||
# Brings up the Compose stack, runs both deployment steps for real, provisions
|
||||
# a tenant, starts uvicorn, and drives `tests/e2e/test_compose_smoke.py`
|
||||
# against it over a socket. This is the only Compose-based test: every other
|
||||
# test uses Testcontainers and an in-process ASGI transport, which is exactly
|
||||
# what makes this one worth having -- it is the only thing that exercises the
|
||||
# deployment steps, the real logging configuration, and a real HTTP server.
|
||||
#
|
||||
# Not part of `uv run pytest`: the smoke test skips itself unless SMOKE_BASE_URL
|
||||
# is set, so this script is the only way it runs. Run it before a release.
|
||||
#
|
||||
# Leaves the Compose stack running (it is the local dev stack); only the uvicorn
|
||||
# process and the temporary log file are cleaned up.
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")/.."
|
||||
|
||||
PORT="${SMOKE_PORT:-8021}"
|
||||
SLUG="smoke-$(date +%s)"
|
||||
DOMAIN="smoke"
|
||||
LOG_FILE="$(mktemp -t smoke-app-log.XXXXXX.jsonl)"
|
||||
CONSOLE_LOG="$(mktemp -t smoke-app-console.XXXXXX.log)"
|
||||
APP_PID=""
|
||||
|
||||
cleanup() {
|
||||
if [[ -n "${APP_PID}" ]] && kill -0 "${APP_PID}" 2>/dev/null; then
|
||||
kill "${APP_PID}" 2>/dev/null || true
|
||||
wait "${APP_PID}" 2>/dev/null || true
|
||||
fi
|
||||
rm -f "${LOG_FILE}" "${CONSOLE_LOG}"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
if [[ ! -f .env ]]; then
|
||||
echo "no .env found; copy .env.example first (see docs/runbook.md)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> starting Postgres, MinIO, Qdrant"
|
||||
docker compose up -d --wait
|
||||
|
||||
echo "==> applying deployment steps"
|
||||
uv run alembic upgrade head
|
||||
uv run python -m src.cli.qdrant_bootstrap
|
||||
|
||||
echo "==> provisioning tenant '${SLUG}'"
|
||||
PROVISION_OUTPUT="$(uv run python -m src.cli.provision_tenant \
|
||||
--slug "${SLUG}" --domain "${DOMAIN}" --scopes files:write 2>/dev/null)"
|
||||
API_KEY="$(printf '%s\n' "${PROVISION_OUTPUT}" | sed -n 's/^api_key=//p')"
|
||||
if [[ -z "${API_KEY}" ]]; then
|
||||
echo "provisioning did not return an api_key" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> starting the web process on port ${PORT}"
|
||||
# JSON to a file sink, because the smoke test asserts the real ADR-0011 log
|
||||
# output -- the one thing no in-process test can check.
|
||||
LOG_JSON_FORMAT=true LOG_FILE_PATH="${LOG_FILE}" \
|
||||
uv run python -m uvicorn src.main:app --host 127.0.0.1 --port "${PORT}" \
|
||||
>"${CONSOLE_LOG}" 2>&1 &
|
||||
APP_PID=$!
|
||||
|
||||
echo "==> waiting for /readyz"
|
||||
for _ in $(seq 1 60); do
|
||||
if curl -fsS "http://127.0.0.1:${PORT}/readyz" >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
if ! kill -0 "${APP_PID}" 2>/dev/null; then
|
||||
echo "the web process exited before becoming ready:" >&2
|
||||
tail -20 "${CONSOLE_LOG}" >&2
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if ! curl -fsS "http://127.0.0.1:${PORT}/readyz" >/dev/null 2>&1; then
|
||||
# Most often an unbootstrapped Qdrant or an unreachable embedder host; the
|
||||
# runbook's health/readiness section covers reading this.
|
||||
echo "the web process never became ready:" >&2
|
||||
tail -20 "${CONSOLE_LOG}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> running the smoke test"
|
||||
SMOKE_BASE_URL="http://127.0.0.1:${PORT}" \
|
||||
SMOKE_API_KEY="${API_KEY}" \
|
||||
SMOKE_DOMAIN="${DOMAIN}" \
|
||||
SMOKE_LOG_PATH="${LOG_FILE}" \
|
||||
SMOKE_QDRANT_URL="${QDRANT_URL:-http://127.0.0.1:6343}" \
|
||||
SMOKE_QDRANT_COLLECTION="${QDRANT_COLLECTION:-chunks}" \
|
||||
uv run python -m pytest tests/e2e/test_compose_smoke.py -q
|
||||
|
||||
echo "==> smoke test passed"
|
||||
Reference in New Issue
Block a user