3ba5ca0849
After the 8-commit SP23 implementation landed, kicking the tires on
`docker compose build && docker compose up -d` (the gated DOCKER_TESTS=1
live test) surfaced five real bugs that don't show up in unit tests:
1. Backend wheel was built from the stub `__init__.py`, not the real
source. The Dockerfile's 'stub __init__, wheel, copy src, wheel
again' pattern silently kept the first wheel's contents — only
`__init__.py` got re-stubbed. The installed package had an empty
`__init__.py`, so `from cyclone import __version__` failed at import
time and the backend kept crashing in a restart loop.
Fix: single `COPY src/` + single `pip wheel`. Comment explains
why the stub trick is gone for good.
2. Backend binds to 127.0.0.1 (intentional — local-only by design,
see CLAUDE.md). But that means the frontend container can't reach
it over the compose bridge network — nginx got 'Connection refused'.
Fix: `CYCLONE_HOST` env var, defaults to 127.0.0.1 (preserves local
posture for non-Docker runs), set to 0.0.0.0 by the docker-compose
backend service. Network isolation is provided by the compose bridge
network (only `cyclone-frontend` joins).
3. Healthcheck probed `/api/healthz` (404 — the route is `/api/health`).
Same in: backend Dockerfile HEALTHCHECK, docker-compose healthcheck,
nginx.conf doesn't have one (frontend proxies through), RUNBOOK.md,
scripts/post-deploy.sh, scripts/smoke.sh.
Fix: `/api/healthz` → `/api/health` everywhere SP23 owns.
4. The auth matrix in `cyclone.auth.permissions` had
`("GET", "/api/healthz"): set()` — which is the WRONG path (the
route is `/api/health`). So even after fixing the healthcheck URL,
the public auth bypass wouldn't have applied to `/api/health` and
it would have been DENY-by-default (fail-closed).
Fix: matrix entry updated to `/api/health`.
5. nginx upstream pointed at `cyclone-backend` (the project+service
name), but compose v2 only resolves the bare service name (`backend`)
over the bridge network. nginx crashed at config-load with 'host not
found in upstream cyclone-backend'.
Fix: `cyclone-backend:8000` → `backend:8000` in nginx.conf + spec
+ plan.
6. Frontend HEALTHCHECK used `http://localhost:8080/`. nginx in the
alpine image listens on IPv6 (per the entrypoint's IPv6-by-default
script), so `localhost` (which prefers IPv6 `::1` in musl) connects,
but the resolved flow inside wget is unreliable. `127.0.0.1` works.
Fix: HEALTHCHECK uses `http://127.0.0.1:8080/`.
Also moves `frontend/Dockerfile` → `Dockerfile.frontend` and
`frontend/nginx.conf` → `nginx.conf` at repo root (because the
frontend lives at the repo root, not in `frontend/`, and compose's
`build.context: .` needs them at the same root as compose.yml).
The frontend's pre-existing `.dockerignore` was empty/unused, so it's
dropped — the root `.dockerignore` covers it.
Adds `docker-compose.override.yml` for local bring-up testing on a
host without sudo. Production uses `/etc/cyclone/secrets/` directly.
Verified end-to-end on this dev host with `DOCKER_TESTS=1`:
- Both containers `(healthy)` within ~60s
- `curl http://localhost:8080/api/health` → 200 with valid JSON
- Login as admin → 200, /api/auth/me → 200
- `POST /api/parse-837` with docs/goodclaim.x12 → 200, batch created
- Full backend test suite: 1014 passed, 9 skipped (prodfiles gitignored)
59 lines
2.0 KiB
Bash
Executable File
59 lines
2.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# smoke.sh — end-to-end bring-up + login + parse + export smoke test.
|
|
#
|
|
# Assumes:
|
|
# - `docker compose up -d` is running.
|
|
# - /etc/cyclone/secrets/admin_pw exists (or CYCLONE_SECRETS_DIR overrides).
|
|
# - The sample 837 at docs/prodfiles/co_medicaid/sample_837p.txt is present.
|
|
#
|
|
# Override the URL with CYCLONE_SMOKE_URL, the sample path with
|
|
# CYCLONE_SMOKE_SAMPLE, the secrets dir with CYCLONE_SECRETS_DIR.
|
|
set -euo pipefail
|
|
|
|
BASE_URL="${CYCLONE_SMOKE_URL:-http://localhost:8080}"
|
|
SECRETS_DIR="${CYCLONE_SECRETS_DIR:-/etc/cyclone/secrets}"
|
|
ADMIN_PW="$(cat "${SECRETS_DIR}/admin_pw")"
|
|
COOKIE_JAR="$(mktemp)"
|
|
SAMPLE_837="${CYCLONE_SMOKE_SAMPLE:-docs/prodfiles/co_medicaid/sample_837p.txt}"
|
|
|
|
cleanup() { rm -f "${COOKIE_JAR}" /tmp/cyclone_smoke_export.zip; }
|
|
trap cleanup EXIT
|
|
|
|
echo "==> waiting for health..."
|
|
for i in {1..30}; do
|
|
if curl -fsS "${BASE_URL}/api/health" >/dev/null 2>&1; then break; fi
|
|
sleep 2
|
|
[[ $i -eq 30 ]] && { echo "FAIL: health never came up"; exit 1; }
|
|
done
|
|
echo " ok"
|
|
|
|
echo "==> logging in as admin..."
|
|
HTTP_CODE=$(curl -sS -o /dev/null -w '%{http_code}' \
|
|
-c "${COOKIE_JAR}" \
|
|
-H 'Content-Type: application/json' \
|
|
-X POST "${BASE_URL}/api/auth/login" \
|
|
-d "{\"username\":\"admin\",\"password\":\"${ADMIN_PW}\"}")
|
|
[[ "${HTTP_CODE}" == "200" ]] || { echo "FAIL: login returned ${HTTP_CODE}"; exit 1; }
|
|
echo " ok (cookie set)"
|
|
|
|
echo "==> /api/auth/me..."
|
|
ME=$(curl -fsS -b "${COOKIE_JAR}" "${BASE_URL}/api/auth/me")
|
|
echo " ${ME}"
|
|
|
|
if [[ -f "${SAMPLE_837}" ]]; then
|
|
echo "==> parsing sample 837 (${SAMPLE_837})..."
|
|
PARSE=$(curl -fsS -b "${COOKIE_JAR}" -X POST "${BASE_URL}/api/parse-837" \
|
|
-F "file=@${SAMPLE_837}")
|
|
echo " ${PARSE}" | head -c 200
|
|
echo
|
|
else
|
|
echo "==> skipping 837 parse (sample not found at ${SAMPLE_837})"
|
|
fi
|
|
|
|
echo "==> listing batches..."
|
|
BATCHES=$(curl -fsS -b "${COOKIE_JAR}" "${BASE_URL}/api/batches")
|
|
echo " ${BATCHES}" | head -c 200
|
|
echo
|
|
|
|
echo "smoke: OK"
|