#!/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"