#!/usr/bin/env bash # ============================================================================= # Wild Dragon MAM — API Smoke Test # ============================================================================= # Hits every major endpoint and reports pass/fail. # # Usage: # MAM_API_URL=http://10.0.0.25:47432 ./deploy/test-api.sh # MAM_API_URL=http://10.0.0.25:47432 NODE_TOKEN=wd_xxxx ./deploy/test-api.sh # ============================================================================= set -euo pipefail BASE="${MAM_API_URL:-http://localhost:47432}" TOKEN="${NODE_TOKEN:-}" PASS=0; FAIL=0; SKIP=0 GRN='\033[0;32m'; RED='\033[0;31m'; YEL='\033[1;33m'; CYN='\033[0;36m'; BLD='\033[1m'; NC='\033[0m' pass() { PASS=$((PASS+1)); echo -e " ${GRN}PASS${NC} $1"; } fail() { FAIL=$((FAIL+1)); echo -e " ${RED}FAIL${NC} $1 ${RED}← $2${NC}"; } skip() { SKIP=$((SKIP+1)); echo -e " ${YEL}SKIP${NC} $1 ${YEL}($2)${NC}"; } header() { echo -e "\n${BLD}$1${NC}"; } AUTH_ARGS=() [[ -n "$TOKEN" ]] && AUTH_ARGS+=(-H "Authorization: Bearer $TOKEN") # GET — check HTTP status code (no -f so 4xx/5xx are visible as their real code) check_status() { local label="$1" path="$2" want="$3" local got got=$(curl -s -o /dev/null -w "%{http_code}" "${AUTH_ARGS[@]}" "$BASE$path" 2>/dev/null) [[ -z "$got" ]] && got="000" if [[ "$got" == "$want" ]]; then pass "$label [HTTP $got]" else fail "$label [HTTP $got]" "expected $want" fi } # GET — check response body contains literal string (fgrep avoids regex interpretation) check_body() { local label="$1" path="$2" needle="$3" local body body=$(curl -s "${AUTH_ARGS[@]}" "$BASE$path" 2>/dev/null) || { fail "$label" "request failed"; return; } if echo "$body" | grep -qF "$needle"; then pass "$label" else fail "$label" "'$needle' not in response" fi } # POST — check HTTP status code check_post() { local label="$1" path="$2" data="$3" want="$4" local got got=$(curl -s -o /dev/null -w "%{http_code}" \ "${AUTH_ARGS[@]}" \ -H "Content-Type: application/json" \ -X POST -d "$data" \ "$BASE$path" 2>/dev/null) [[ -z "$got" ]] && got="000" if [[ "$got" == "$want" ]]; then pass "$label [HTTP $got]" else fail "$label [HTTP $got]" "expected $want" fi } # ───────────────────────────────────────────────────────────────────────────── echo "" echo -e "${BLD}${CYN}Wild Dragon MAM — API Smoke Test${NC}" echo -e " Base URL : ${BLD}$BASE${NC}" [[ -n "$TOKEN" ]] && echo -e " Auth : Bearer token" || echo -e " Auth : none" echo "" # ── Connectivity ───────────────────────────────────────────────────────────── header "Connectivity" CONNECT=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/health" 2>/dev/null) if [[ "$CONNECT" == "200" ]]; then pass "API server reachable [/health → 200]" else fail "API server reachable [HTTP $CONNECT]" "cannot reach $BASE" echo -e "\n ${RED}Cannot reach the server — aborting.${NC}" exit 1 fi # ── Auth ───────────────────────────────────────────────────────────────────── header "Auth" check_status "GET /auth/me" "/api/v1/auth/me" 200 check_body "GET /auth/me returns username" "/api/v1/auth/me" '"username"' check_post "POST /auth/login (missing body → 400)" "/api/v1/auth/login" '{}' 400 # ── Assets ─────────────────────────────────────────────────────────────────── header "Assets" check_status "GET /assets" "/api/v1/assets" 200 check_body "GET /assets returns assets key" "/api/v1/assets" '"assets"' check_status "GET /assets bogus id → 404" "/api/v1/assets/00000000-0000-0000-0000-000000000000" 404 # ── Projects ───────────────────────────────────────────────────────────────── header "Projects" check_status "GET /projects" "/api/v1/projects" 200 check_body "GET /projects returns array" "/api/v1/projects" '[' # ── Jobs ───────────────────────────────────────────────────────────────────── header "Jobs" check_status "GET /jobs" "/api/v1/jobs" 200 check_body "GET /jobs returns array" "/api/v1/jobs" '[' # ── Recorders ──────────────────────────────────────────────────────────────── header "Recorders" check_status "GET /recorders" "/api/v1/recorders" 200 # ── Sequences (requires project_id param) ──────────────────────────────────── header "Sequences" check_status "GET /sequences (no project_id → 400)" "/api/v1/sequences" 400 check_status "GET /sequences bogus project_id → 200" "/api/v1/sequences?project_id=00000000-0000-0000-0000-000000000000" 200 # ── Settings ───────────────────────────────────────────────────────────────── header "Settings" check_status "GET /settings/ampp" "/api/v1/settings/ampp" 200 # ── Cluster ────────────────────────────────────────────────────────────────── header "Cluster" check_status "GET /cluster" "/api/v1/cluster" 200 check_body "GET /cluster returns array" "/api/v1/cluster" '[' # Heartbeat: register a temporary smoke-test node, verify it appears, remove it TEST_HOST="smoke-test-$(date +%s)" check_post "POST /cluster/heartbeat" "/api/v1/cluster/heartbeat" \ "{\"hostname\":\"$TEST_HOST\",\"role\":\"smoketest\",\"cpu_usage\":0,\"mem_used_mb\":512,\"mem_total_mb\":4096}" \ 200 NODE_ID=$(curl -s "${AUTH_ARGS[@]}" "$BASE/api/v1/cluster" 2>/dev/null \ | grep -o '"id":"[^"]*"' | head -1 | grep -o '[0-9a-f-]\{36\}' || true) if [[ -n "$NODE_ID" ]]; then pass "Cluster node visible in registry" DEL=$(curl -s -o /dev/null -w "%{http_code}" "${AUTH_ARGS[@]}" \ -X DELETE "$BASE/api/v1/cluster/$NODE_ID" 2>/dev/null) [[ "$DEL" == "200" ]] && pass "DELETE /cluster/:id (cleanup) [HTTP $DEL]" \ || fail "DELETE /cluster/:id (cleanup)" "HTTP $DEL" else skip "Cluster node visible in registry" "could not parse node id from response" fi # ── System / Containers ─────────────────────────────────────────────────────── header "System" check_status "GET /system/containers" "/api/v1/system/containers" 200 check_body "Containers returns array" "/api/v1/system/containers" '[' # ── Capture (proxies to capture service) ───────────────────────────────────── header "Capture" # 200 = capture active and responding # 404 = capture in sidecar/idle mode (no active recorder — expected in dev) # 5xx = capture container unreachable CAPTURE_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${AUTH_ARGS[@]}" \ "$BASE/api/v1/capture/status" 2>/dev/null) if [[ "$CAPTURE_CODE" == "200" ]]; then pass "GET /capture/status [HTTP 200 — capture active]" elif [[ "$CAPTURE_CODE" == "404" ]]; then skip "GET /capture/status [HTTP 404]" "capture in idle/sidecar mode (normal when not recording)" elif [[ "$CAPTURE_CODE" =~ ^5 ]]; then skip "GET /capture/status [HTTP $CAPTURE_CODE]" "capture container unreachable" else fail "GET /capture/status [HTTP $CAPTURE_CODE]" "unexpected status" fi # ── Users / Tokens ─────────────────────────────────────────────────────────── header "Users / Tokens" check_status "GET /users" "/api/v1/users" 200 check_status "GET /tokens" "/api/v1/tokens" 200 # ── Summary ─────────────────────────────────────────────────────────────────── TOTAL=$((PASS + FAIL + SKIP)) echo "" echo -e "${BLD}Results:${NC} ${GRN}${PASS} passed${NC} / ${RED}${FAIL} failed${NC} / ${YEL}${SKIP} skipped${NC} / $TOTAL total" echo "" if [[ $FAIL -gt 0 ]]; then echo -e "${RED}Some tests failed — check output above.${NC}" exit 1 else echo -e "${GRN}All tests passed.${NC}" fi