demo.sh 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. #!/usr/bin/env bash
  2. # End-to-end smoke check.
  3. #
  4. # Mirrors the README quickstart: boots the compose stack, creates an
  5. # admin token, creates a reporter + consumer + their tokens, posts a
  6. # report, triggers the recompute job, pulls the blocklist, and asserts
  7. # the IP shows up. Tears down at the end.
  8. #
  9. # Run from the repo root:
  10. # ./tests/e2e/demo.sh
  11. #
  12. # Requirements: Docker. No PHP/Node/Composer needed on the host.
  13. # Expected runtime: ~90 seconds (most of it the FrankenPHP cold start).
  14. #
  15. # Exits non-zero on any step failure. Sets `set -e` so the first error
  16. # aborts; `trap` ensures the stack is torn down even on early exit.
  17. set -euo pipefail
  18. REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
  19. cd "$REPO_ROOT"
  20. LOG_PREFIX="\033[1;36m[e2e]\033[0m"
  21. log() { printf "%b %s\n" "$LOG_PREFIX" "$*"; }
  22. fail() { printf "\033[1;31m[fail]\033[0m %s\n" "$*" >&2; exit 1; }
  23. cleanup() {
  24. log "tearing down compose stack"
  25. docker compose down -v >/dev/null 2>&1 || true
  26. }
  27. trap cleanup EXIT
  28. # ---- Step 1: prepare a clean .env ---------------------------------
  29. log "preparing .env from .env.example"
  30. cp .env.example .env
  31. # Generate a deterministic local-admin password hash so the test is
  32. # reproducible. The helper escape doubles every $ so docker-compose
  33. # variable substitution doesn't eat them.
  34. HASH=$(php -r "echo password_hash('e2e-demo-password', PASSWORD_ARGON2ID);" | sed 's/\$/$$/g')
  35. # Fill in the secrets needed for boot. Use a small awk to substitute
  36. # in place — POSIX sed's behaviour for "key=" lines varies across
  37. # distros for special chars in the value.
  38. awk -v hash="$HASH" '
  39. BEGIN { ui_secret=ui_svc=int_tok=app_secret="" }
  40. /^UI_SECRET=/ { print "UI_SECRET=" "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; next }
  41. /^APP_SECRET=/ { print "APP_SECRET=" "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"; next }
  42. /^INTERNAL_JOB_TOKEN=/ { print "INTERNAL_JOB_TOKEN=" "abababababababababababababababababababababababababababababababab"; next }
  43. /^UI_SERVICE_TOKEN=/ { print "UI_SERVICE_TOKEN=" "irdb_svc_AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD"; next }
  44. /^OIDC_ENABLED=/ { print "OIDC_ENABLED=false"; next }
  45. /^LOCAL_ADMIN_PASSWORD_HASH=/ { print "LOCAL_ADMIN_PASSWORD_HASH=" hash; next }
  46. { print }
  47. ' .env > .env.tmp && mv .env.tmp .env
  48. # ---- Step 2: bring the stack up ----------------------------------
  49. log "compose up"
  50. docker compose up -d --build >/dev/null
  51. log "waiting for api healthcheck (up to 60s)"
  52. for _ in $(seq 1 60); do
  53. if curl -sf http://localhost:8081/healthz >/dev/null 2>&1; then
  54. break
  55. fi
  56. sleep 1
  57. done
  58. curl -sf http://localhost:8081/healthz >/dev/null || fail "api never became healthy"
  59. log "waiting for ui healthcheck (up to 30s)"
  60. for _ in $(seq 1 30); do
  61. if curl -sf http://localhost:8080/healthz >/dev/null 2>&1; then
  62. break
  63. fi
  64. sleep 1
  65. done
  66. curl -sf http://localhost:8080/healthz >/dev/null || fail "ui never became healthy"
  67. # ---- Step 3: bootstrap the service token + admin token -----------
  68. log "bootstrapping the UI service token row"
  69. docker compose exec -T api php bin/console auth:bootstrap-service-token >/dev/null
  70. log "creating an admin token via CLI"
  71. ADMIN_TOKEN=$(docker compose exec -T api php bin/console \
  72. auth:create-token --kind=admin --role=admin --quiet | tr -d '\r')
  73. [ -n "$ADMIN_TOKEN" ] || fail "no admin token returned"
  74. # ---- Step 4: create a reporter + reporter token ------------------
  75. log "creating reporter + token"
  76. RID=$(curl -sf -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
  77. -H "Content-Type: application/json" \
  78. -d '{"name":"e2e-rep","trust_weight":1.0}' \
  79. http://localhost:8081/api/v1/admin/reporters \
  80. | php -r 'echo json_decode(stream_get_contents(STDIN),true)["id"];')
  81. [ -n "$RID" ] || fail "no reporter id returned"
  82. REP_TOKEN=$(curl -sf -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
  83. -H "Content-Type: application/json" \
  84. -d "{\"kind\":\"reporter\",\"reporter_id\":$RID}" \
  85. http://localhost:8081/api/v1/admin/tokens \
  86. | php -r 'echo json_decode(stream_get_contents(STDIN),true)["raw_token"];')
  87. [ -n "$REP_TOKEN" ] || fail "no reporter token returned"
  88. # ---- Step 5: post some reports ----------------------------------
  89. log "submitting reports for 203.0.113.10..12"
  90. for i in 10 11 12; do
  91. curl -sf -X POST -H "Authorization: Bearer $REP_TOKEN" \
  92. -H "Content-Type: application/json" \
  93. -d "{\"ip\":\"203.0.113.$i\",\"category\":\"brute_force\"}" \
  94. http://localhost:8081/api/v1/report > /dev/null
  95. done
  96. # ---- Step 6: create a consumer + consumer token -----------------
  97. log "creating consumer + token"
  98. # Pick policy id 1 (the seeded "moderate" policy).
  99. CID=$(curl -sf -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
  100. -H "Content-Type: application/json" \
  101. -d '{"name":"e2e-con","policy_id":1}' \
  102. http://localhost:8081/api/v1/admin/consumers \
  103. | php -r 'echo json_decode(stream_get_contents(STDIN),true)["id"];')
  104. [ -n "$CID" ] || fail "no consumer id returned"
  105. CON_TOKEN=$(curl -sf -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
  106. -H "Content-Type: application/json" \
  107. -d "{\"kind\":\"consumer\",\"consumer_id\":$CID}" \
  108. http://localhost:8081/api/v1/admin/tokens \
  109. | php -r 'echo json_decode(stream_get_contents(STDIN),true)["raw_token"];')
  110. [ -n "$CON_TOKEN" ] || fail "no consumer token returned"
  111. # ---- Step 7: trigger recompute via admin endpoint ---------------
  112. log "triggering recompute-scores"
  113. RESP=$(curl -sf -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
  114. -H "Content-Type: application/json" -d '{"full":true}' \
  115. http://localhost:8081/api/v1/admin/jobs/trigger/recompute-scores)
  116. echo "$RESP" | grep -q '"status":"success"' || fail "recompute did not succeed: $RESP"
  117. # ---- Step 8: pull the blocklist; assert non-empty ----------------
  118. log "pulling blocklist"
  119. LIST=$(curl -sf -H "Authorization: Bearer $CON_TOKEN" http://localhost:8081/api/v1/blocklist)
  120. COUNT=$(printf '%s\n' "$LIST" | grep -cE '^[0-9]' || true)
  121. [ "$COUNT" -ge 1 ] || fail "blocklist empty (count=$COUNT)"
  122. log "blocklist has $COUNT entries"
  123. # ---- Step 9: poke the OpenAPI viewer -----------------------------
  124. log "fetching /api/v1/openapi.yaml"
  125. curl -sf http://localhost:8081/api/v1/openapi.yaml | grep -q '^openapi:' \
  126. || fail "openapi.yaml missing or unexpected"
  127. log "fetching /api/docs"
  128. curl -sf http://localhost:8081/api/docs | grep -qi 'rapi-doc' \
  129. || fail "/api/docs did not render the viewer"
  130. log "all checks passed"