Files
datatools-dev/server/scripts/smoke.sh
Michael bab2c9468c feat(server): mint API + Postgres schema + manual adapter (PR 1)
Source-agnostic license issuance service. FastAPI app fronts a
Postgres `licenses` table; the only currently-wired source is
`manual` (operator mints via /internal/mint). Gumroad webhook
adapter lands in PR 2.

Key design points:

- Signing reuses src/license/crypto.py via a COPY into the image
  (single source of truth — blobs minted server-side verify against
  the same embedded pubkey on the buyer's machine).
- Source adapter Protocol (app/adapters/base.py) is the seam for
  Gumroad / Lemon Squeezy / Stripe in later PRs; Mint API speaks
  only SaleEvent / RefundEvent.
- (source, source_order_id) UNIQUE composite gives idempotent
  webhook retries without double-mint.
- JSONB type uses with_variant(JSON, 'sqlite') so the same models
  drive both Postgres prod and SQLite tests (no testcontainers dep).
- Bearer-token auth on /internal/*; the IP-loopback guard was
  removed after the docker bridge made it fight legitimate prod
  traffic (nginx defense + Bearer remain).
- Secrets resolved via *_FILE env vars pointing at
  /run/secrets/<name>, so passwords never appear in `docker inspect`.

21 unit tests (SQLite in-memory, StaticPool) plus a real-Postgres
docker-compose smoke test in server/scripts/smoke.sh that builds the
image, runs the alembic migration, mints a license, verifies the
signature against the host dev pubkey, and checks the DB row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 00:46:54 +00:00

85 lines
2.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# End-to-end smoke test for the license server.
#
# Builds the API image, brings up Postgres + API, runs the Alembic
# migration, mints a license through /internal/mint, verifies the
# resulting blob's Ed25519 signature against the dev pubkey, and
# confirms the row landed in the DB. Tears everything down at exit.
#
# Run from the server/ directory: ./scripts/smoke.sh
set -euo pipefail
cd "$(dirname "$0")/.."
PROJECT=dt-license-smoke
COMPOSE=(docker compose -p "$PROJECT" -f compose.test.yml)
cleanup() {
echo "--- Tearing down ---"
"${COMPOSE[@]}" down -v --remove-orphans >/dev/null 2>&1 || true
}
trap cleanup EXIT
echo "--- Building image ---"
"${COMPOSE[@]}" build
echo "--- Starting stack ---"
"${COMPOSE[@]}" up -d
echo "--- Waiting for API health (max 60s) ---"
for i in $(seq 1 60); do
if curl -sf http://127.0.0.1:18090/health 2>/dev/null | grep -q '"status":"ok"'; then
echo "API up after ${i}s"
break
fi
sleep 1
done
echo "--- Running migrations ---"
"${COMPOSE[@]}" exec -T api alembic upgrade head
echo "--- Re-checking health post-migration ---"
curl -sf http://127.0.0.1:18090/health | tee /dev/stderr | grep -q '"db":"ok"'
echo "--- POST /internal/mint ---"
RESP=$(curl -s -w "\nHTTP=%{http_code}" -X POST http://127.0.0.1:18090/internal/mint \
-H "Authorization: Bearer test-admin-token" \
-H "Content-Type: application/json" \
-d '{"name":"Smoke Test","email":"smoke@example.com","tier":"core","source":"manual"}')
echo "$RESP"
HTTP_CODE=$(echo "$RESP" | tail -n1 | sed 's/HTTP=//')
RESP=$(echo "$RESP" | sed '$d')
if [ "$HTTP_CODE" != "201" ]; then
echo "MINT FAILED (HTTP $HTTP_CODE)"
"${COMPOSE[@]}" logs --tail 50 api
exit 1
fi
echo "$RESP" | python3 -m json.tool | head -8
BLOB=$(echo "$RESP" | python3 -c 'import json,sys; print(json.load(sys.stdin)["blob"])')
echo "--- Verifying blob signature against host dev pubkey ---"
python3 - <<EOF
import sys
sys.path.insert(0, "..")
from src.license.crypto import decode_blob, verify
payload = decode_blob("$BLOB")
sig = payload.pop("signature")
assert verify(payload, sig), "signature must verify"
assert payload["name"] == "Smoke Test"
assert payload["email"] == "smoke@example.com"
assert payload["tier"] == "core"
print("OK: signature verifies, payload matches")
EOF
echo "--- Verifying DB row ---"
"${COMPOSE[@]}" exec -T postgres \
psql -U dt_test -d dt_test -t -c \
"SELECT license_key, email, tier, source FROM licenses;" \
| grep -q smoke@example.com
echo
echo "===================================="
echo " SMOKE TEST PASSED"
echo "===================================="