#!/usr/bin/env bash set -euo pipefail # Force GPG to use terminal-based pinentry (required for SSH sessions) export GPG_TTY=$(tty) # ---------------------------------------------------------------------------- # bin/test — verify a running Firecrawl deployment by hitting it from the host # (i.e. outside the Docker network), simulating an external client # such as an AI agent. # # Usage: # bin/test # local dev — hits http://127.0.0.1:31329 # bin/test https://my.host # remote — hits the given base URL # # The API key is read from gopass at projects/firecrawl-dokploy/dev/api_key # (override by exporting TEST_API_KEY before running). # ---------------------------------------------------------------------------- BASE_URL="${1:-http://127.0.0.1:31329}" BASE_URL="${BASE_URL%/}" if [ -z "${TEST_API_KEY:-}" ]; then TEST_API_KEY="$(gopass show -o projects/firecrawl-dokploy/dev/api_key)" fi if [ -z "${TEST_API_KEY:-}" ]; then echo >&2 "ERROR: TEST_API_KEY is empty (gopass returned nothing and env was unset)" exit 1 fi # ---------------------------------------------------------------------------- # Output capture: tee everything to tmp/test-.log AND the most recent # run to tmp/test-latest.log, while still printing to the terminal. # ---------------------------------------------------------------------------- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" TMP_DIR="$REPO_ROOT/tmp" mkdir -p "$TMP_DIR" LOG_FILE="$TMP_DIR/test-$(date +%Y%m%d-%H%M%S).log" LATEST_LOG="$TMP_DIR/test-latest.log" # Redirect all stdout+stderr through tee. Use a process substitution so the # script keeps its normal exit code. exec > >(tee "$LOG_FILE") 2>&1 # Mirror to test-latest.log on exit (atomic copy, not a symlink so it survives # `rm tmp/*.log`). trap 'cp -f "$LOG_FILE" "$LATEST_LOG" 2>/dev/null || true' EXIT # ---------------------------------------------------------------------------- # Pretty output helpers # ---------------------------------------------------------------------------- RED=$'\033[0;31m' GREEN=$'\033[0;32m' YELLOW=$'\033[0;33m' BLUE=$'\033[0;34m' BOLD=$'\033[1m' RESET=$'\033[0m' PASS=0 FAIL=0 section() { printf '\n%s== %s ==%s\n' "$BOLD$BLUE" "$1" "$RESET" } ok() { printf ' %s✓%s %s\n' "$GREEN" "$RESET" "$1" PASS=$((PASS + 1)) } bad() { printf ' %s✗%s %s\n' "$RED" "$RESET" "$1" FAIL=$((FAIL + 1)) } info() { printf ' %s…%s %s\n' "$YELLOW" "$RESET" "$1" } # ---------------------------------------------------------------------------- # Issue a JSON request, capture body + status # Args: METHOD PATH [JSON_BODY] # Sets: HTTP_STATUS, HTTP_BODY # ---------------------------------------------------------------------------- http_call() { local method=$1 local path=$2 local body=${3:-} local tmp tmp=$(mktemp) local args=( -sS -o "$tmp" -w '%{http_code}' -X "$method" -H "Authorization: Bearer ${TEST_API_KEY}" -H "Content-Type: application/json" --connect-timeout 10 --max-time 120 ) if [ -n "$body" ]; then args+=(-d "$body") fi HTTP_STATUS=$(curl "${args[@]}" "${BASE_URL}${path}" || echo "000") HTTP_BODY=$(cat "$tmp") rm -f "$tmp" } # ---------------------------------------------------------------------------- # Test runner # ---------------------------------------------------------------------------- section "Target" info "BASE_URL = ${BASE_URL}" info "TEST_API_KEY = ${TEST_API_KEY:0:8}…" # 1. Reachability ------------------------------------------------------------ section "1. Reachability" http_call GET "/" case "$HTTP_STATUS" in 200|404|401) ok "API is reachable (HTTP $HTTP_STATUS at /)" ;; 000) bad "Could not connect to ${BASE_URL} — is the stack running?" echo echo "Hint: run 'bin/up' first, or pass a different base URL." exit 1 ;; *) bad "Unexpected HTTP $HTTP_STATUS at /" ;; esac # 2. Auth posture (informational) ------------------------------------------ # Self-hosted Firecrawl with USE_DB_AUTHENTICATION=false has no built-in # bearer-token gate — any token (including a bogus one) is accepted by the # API. We probe with a bogus token just to surface this fact in the log. section "2. Auth posture (informational)" tmp=$(mktemp) status=$(curl -sS -o "$tmp" -w '%{http_code}' \ -X POST "${BASE_URL}/v1/scrape" \ -H "Authorization: Bearer fc-definitely-not-a-real-key" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com"}' \ --connect-timeout 10 --max-time 60 || echo "000") rm -f "$tmp" if [ "$status" = "401" ] || [ "$status" = "403" ]; then ok "bogus key rejected with HTTP $status (USE_DB_AUTHENTICATION on?)" else info "bogus key returned HTTP $status — self-hosted Firecrawl is open by design; restrict access at Traefik/firewall level" fi # 3. /v1/scrape -------------------------------------------------------------- section "3. Scrape — POST /v1/scrape https://example.com" http_call POST "/v1/scrape" '{"url":"https://example.com","formats":["markdown"]}' if [ "$HTTP_STATUS" = "200" ]; then if printf '%s' "$HTTP_BODY" | grep -qi "example domain"; then ok "scrape returned 200 and markdown contains 'Example Domain'" else bad "scrape returned 200 but markdown did not contain 'Example Domain'" echo "$HTTP_BODY" | head -c 400 echo fi else bad "scrape failed with HTTP $HTTP_STATUS" echo "$HTTP_BODY" | head -c 400 echo fi # 4. /v1/search (covers SearXNG + Firecrawl scrape pipeline) ----------------- section "4. Search — POST /v1/search 'firecrawl github'" http_call POST "/v1/search" '{"query":"firecrawl github","limit":3}' if [ "$HTTP_STATUS" = "200" ]; then if printf '%s' "$HTTP_BODY" | grep -q '"success":true'; then ok "search returned 200 with success:true (SearXNG + scrape pipeline OK)" else bad "search returned 200 but body lacks success:true" echo "$HTTP_BODY" | head -c 400 echo fi else bad "search failed with HTTP $HTTP_STATUS" echo "$HTTP_BODY" | head -c 400 echo fi # 5. /v1/map ----------------------------------------------------------------- section "5. Map — POST /v1/map https://example.com" http_call POST "/v1/map" '{"url":"https://example.com"}' if [ "$HTTP_STATUS" = "200" ]; then ok "map returned 200" else bad "map failed with HTTP $HTTP_STATUS" echo "$HTTP_BODY" | head -c 400 echo fi # ---------------------------------------------------------------------------- # Summary # ---------------------------------------------------------------------------- echo section "Summary" printf " %sPassed:%s %d\n" "$GREEN" "$RESET" "$PASS" printf " %sFailed:%s %d\n" "$RED" "$RESET" "$FAIL" echo if [ "$FAIL" -gt 0 ]; then echo "Log: $LOG_FILE" exit 1 fi echo "Log: $LOG_FILE"