#!/usr/bin/env bash # End-to-end smoke check. # # Mirrors the README quickstart: boots the compose stack, creates an # admin token, creates a reporter + consumer + their tokens, posts a # report, triggers the recompute job, pulls the blocklist, and asserts # the IP shows up. Tears down at the end. # # Run from the repo root: # ./tests/e2e/demo.sh # # Requirements: Docker. No PHP/Node/Composer needed on the host. # Expected runtime: ~90 seconds (most of it the FrankenPHP cold start). # # Exits non-zero on any step failure. Sets `set -e` so the first error # aborts; `trap` ensures the stack is torn down even on early exit. set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" cd "$REPO_ROOT" LOG_PREFIX="\033[1;36m[e2e]\033[0m" log() { printf "%b %s\n" "$LOG_PREFIX" "$*"; } fail() { printf "\033[1;31m[fail]\033[0m %s\n" "$*" >&2; exit 1; } cleanup() { log "tearing down compose stack" docker compose down -v >/dev/null 2>&1 || true } trap cleanup EXIT # ---- Step 1: prepare a clean .env --------------------------------- log "preparing .env from .env.example" cp .env.example .env # Generate a deterministic local-admin password hash so the test is # reproducible. The helper escape doubles every $ so docker-compose # variable substitution doesn't eat them. HASH=$(php -r "echo password_hash('e2e-demo-password', PASSWORD_ARGON2ID);" | sed 's/\$/$$/g') # Fill in the secrets needed for boot. Use a small awk to substitute # in place — POSIX sed's behaviour for "key=" lines varies across # distros for special chars in the value. awk -v hash="$HASH" ' BEGIN { ui_secret=ui_svc=int_tok=app_secret="" } /^UI_SECRET=/ { print "UI_SECRET=" "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; next } /^APP_SECRET=/ { print "APP_SECRET=" "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"; next } /^INTERNAL_JOB_TOKEN=/ { print "INTERNAL_JOB_TOKEN=" "abababababababababababababababababababababababababababababababab"; next } /^UI_SERVICE_TOKEN=/ { print "UI_SERVICE_TOKEN=" "irdb_svc_AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD"; next } /^OIDC_ENABLED=/ { print "OIDC_ENABLED=false"; next } /^LOCAL_ADMIN_PASSWORD_HASH=/ { print "LOCAL_ADMIN_PASSWORD_HASH=" hash; next } { print } ' .env > .env.tmp && mv .env.tmp .env # ---- Step 2: bring the stack up ---------------------------------- log "compose up" docker compose up -d --build >/dev/null log "waiting for api healthcheck (up to 60s)" for _ in $(seq 1 60); do if curl -sf http://localhost:8081/healthz >/dev/null 2>&1; then break fi sleep 1 done curl -sf http://localhost:8081/healthz >/dev/null || fail "api never became healthy" log "waiting for ui healthcheck (up to 30s)" for _ in $(seq 1 30); do if curl -sf http://localhost:8080/healthz >/dev/null 2>&1; then break fi sleep 1 done curl -sf http://localhost:8080/healthz >/dev/null || fail "ui never became healthy" # ---- Step 3: bootstrap the service token + admin token ----------- log "bootstrapping the UI service token row" docker compose exec -T api php bin/console auth:bootstrap-service-token >/dev/null log "creating an admin token via CLI" ADMIN_TOKEN=$(docker compose exec -T api php bin/console \ auth:create-token --kind=admin --role=admin --quiet | tr -d '\r') [ -n "$ADMIN_TOKEN" ] || fail "no admin token returned" # ---- Step 4: create a reporter + reporter token ------------------ log "creating reporter + token" RID=$(curl -sf -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"e2e-rep","trust_weight":1.0}' \ http://localhost:8081/api/v1/admin/reporters \ | php -r 'echo json_decode(stream_get_contents(STDIN),true)["id"];') [ -n "$RID" ] || fail "no reporter id returned" REP_TOKEN=$(curl -sf -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d "{\"kind\":\"reporter\",\"reporter_id\":$RID}" \ http://localhost:8081/api/v1/admin/tokens \ | php -r 'echo json_decode(stream_get_contents(STDIN),true)["raw_token"];') [ -n "$REP_TOKEN" ] || fail "no reporter token returned" # ---- Step 5: post some reports ---------------------------------- log "submitting reports for 203.0.113.10..12" for i in 10 11 12; do curl -sf -X POST -H "Authorization: Bearer $REP_TOKEN" \ -H "Content-Type: application/json" \ -d "{\"ip\":\"203.0.113.$i\",\"category\":\"brute_force\"}" \ http://localhost:8081/api/v1/report > /dev/null done # ---- Step 6: create a consumer + consumer token ----------------- log "creating consumer + token" # Pick policy id 1 (the seeded "moderate" policy). CID=$(curl -sf -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"e2e-con","policy_id":1}' \ http://localhost:8081/api/v1/admin/consumers \ | php -r 'echo json_decode(stream_get_contents(STDIN),true)["id"];') [ -n "$CID" ] || fail "no consumer id returned" CON_TOKEN=$(curl -sf -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d "{\"kind\":\"consumer\",\"consumer_id\":$CID}" \ http://localhost:8081/api/v1/admin/tokens \ | php -r 'echo json_decode(stream_get_contents(STDIN),true)["raw_token"];') [ -n "$CON_TOKEN" ] || fail "no consumer token returned" # ---- Step 7: trigger recompute via admin endpoint --------------- log "triggering recompute-scores" RESP=$(curl -sf -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" -d '{"full":true}' \ http://localhost:8081/api/v1/admin/jobs/trigger/recompute-scores) echo "$RESP" | grep -q '"status":"success"' || fail "recompute did not succeed: $RESP" # ---- Step 8: pull the blocklist; assert non-empty ---------------- log "pulling blocklist" LIST=$(curl -sf -H "Authorization: Bearer $CON_TOKEN" http://localhost:8081/api/v1/blocklist) COUNT=$(printf '%s\n' "$LIST" | grep -cE '^[0-9]' || true) [ "$COUNT" -ge 1 ] || fail "blocklist empty (count=$COUNT)" log "blocklist has $COUNT entries" # ---- Step 9: poke the OpenAPI viewer ----------------------------- log "fetching /api/v1/openapi.yaml" curl -sf http://localhost:8081/api/v1/openapi.yaml | grep -q '^openapi:' \ || fail "openapi.yaml missing or unexpected" log "fetching /api/docs" curl -sf http://localhost:8081/api/docs | grep -qi 'rapi-doc' \ || fail "/api/docs did not render the viewer" log "all checks passed"