2026-05-20 13:49:35 -04:00
|
|
|
#!/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")
|
|
|
|
|
|
2026-05-20 13:52:59 -04:00
|
|
|
# GET — check HTTP status code (no -f so 4xx/5xx are visible)
|
2026-05-20 13:49:35 -04:00
|
|
|
check_status() {
|
|
|
|
|
local label="$1" path="$2" want="$3"
|
|
|
|
|
local got
|
2026-05-20 13:52:59 -04:00
|
|
|
got=$(curl -s -o /dev/null -w "%{http_code}" "${AUTH_ARGS[@]}" "$BASE$path" 2>/dev/null)
|
|
|
|
|
[[ -z "$got" ]] && got="000"
|
2026-05-20 13:49:35 -04:00
|
|
|
if [[ "$got" == "$want" ]]; then
|
|
|
|
|
pass "$label [HTTP $got]"
|
|
|
|
|
else
|
|
|
|
|
fail "$label [HTTP $got]" "expected $want"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-20 13:52:59 -04:00
|
|
|
# GET — check response body contains literal string (fgrep avoids regex interpretation)
|
2026-05-20 13:49:35 -04:00
|
|
|
check_body() {
|
|
|
|
|
local label="$1" path="$2" needle="$3"
|
|
|
|
|
local body
|
2026-05-20 13:52:59 -04:00
|
|
|
body=$(curl -s "${AUTH_ARGS[@]}" "$BASE$path" 2>/dev/null) || { fail "$label" "request failed"; return; }
|
|
|
|
|
if echo "$body" | grep -qF "$needle"; then
|
2026-05-20 13:49:35 -04:00
|
|
|
pass "$label"
|
|
|
|
|
else
|
|
|
|
|
fail "$label" "'$needle' not in response"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-20 13:52:59 -04:00
|
|
|
# POST — check HTTP status code
|
2026-05-20 13:49:35 -04:00
|
|
|
check_post() {
|
|
|
|
|
local label="$1" path="$2" data="$3" want="$4"
|
|
|
|
|
local got
|
2026-05-20 13:52:59 -04:00
|
|
|
got=$(curl -s -o /dev/null -w "%{http_code}" \
|
2026-05-20 13:49:35 -04:00
|
|
|
"${AUTH_ARGS[@]}" \
|
|
|
|
|
-H "Content-Type: application/json" \
|
|
|
|
|
-X POST -d "$data" \
|
2026-05-20 13:52:59 -04:00
|
|
|
"$BASE$path" 2>/dev/null)
|
|
|
|
|
[[ -z "$got" ]] && got="000"
|
2026-05-20 13:49:35 -04:00
|
|
|
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"
|
2026-05-20 13:52:59 -04:00
|
|
|
CONNECT=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/health" 2>/dev/null)
|
|
|
|
|
if [[ "$CONNECT" == "200" ]]; then
|
|
|
|
|
pass "API server reachable [/health → 200]"
|
2026-05-20 13:49:35 -04:00
|
|
|
else
|
2026-05-20 13:52:59 -04:00
|
|
|
fail "API server reachable [HTTP $CONNECT]" "cannot reach $BASE"
|
2026-05-20 13:49:35 -04:00
|
|
|
echo -e "\n ${RED}Cannot reach the server — aborting.${NC}"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ── Auth ─────────────────────────────────────────────────────────────────────
|
|
|
|
|
header "Auth"
|
2026-05-20 13:52:59 -04:00
|
|
|
check_status "GET /auth/me" "/api/v1/auth/me" 200
|
|
|
|
|
check_body "GET /auth/me returns username" "/api/v1/auth/me" '"username"'
|
|
|
|
|
check_status "POST /auth/login (bad creds → 400)" "/api/v1/auth/login" 400
|
2026-05-20 13:49:35 -04:00
|
|
|
|
|
|
|
|
# ── Assets ───────────────────────────────────────────────────────────────────
|
|
|
|
|
header "Assets"
|
2026-05-20 13:52:59 -04:00
|
|
|
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
|
2026-05-20 13:49:35 -04:00
|
|
|
|
|
|
|
|
# ── Projects ─────────────────────────────────────────────────────────────────
|
|
|
|
|
header "Projects"
|
2026-05-20 13:52:59 -04:00
|
|
|
check_status "GET /projects" "/api/v1/projects" 200
|
|
|
|
|
check_body "GET /projects returns array" "/api/v1/projects" '['
|
2026-05-20 13:49:35 -04:00
|
|
|
|
|
|
|
|
# ── Jobs ─────────────────────────────────────────────────────────────────────
|
|
|
|
|
header "Jobs"
|
2026-05-20 13:52:59 -04:00
|
|
|
check_status "GET /jobs" "/api/v1/jobs" 200
|
|
|
|
|
check_body "GET /jobs returns array" "/api/v1/jobs" '['
|
2026-05-20 13:49:35 -04:00
|
|
|
|
|
|
|
|
# ── Recorders ────────────────────────────────────────────────────────────────
|
|
|
|
|
header "Recorders"
|
2026-05-20 13:52:59 -04:00
|
|
|
check_status "GET /recorders" "/api/v1/recorders" 200
|
2026-05-20 13:49:35 -04:00
|
|
|
|
|
|
|
|
# ── Sequences (Editor) ───────────────────────────────────────────────────────
|
|
|
|
|
header "Sequences"
|
2026-05-20 13:52:59 -04:00
|
|
|
check_status "GET /sequences" "/api/v1/sequences" 200
|
2026-05-20 13:49:35 -04:00
|
|
|
|
|
|
|
|
# ── Cluster ──────────────────────────────────────────────────────────────────
|
|
|
|
|
header "Cluster"
|
2026-05-20 13:52:59 -04:00
|
|
|
check_status "GET /cluster" "/api/v1/cluster" 200
|
|
|
|
|
check_body "GET /cluster returns array" "/api/v1/cluster" '['
|
2026-05-20 13:49:35 -04:00
|
|
|
|
2026-05-20 13:52:59 -04:00
|
|
|
# Heartbeat: register a temporary smoke-test node, verify it appears, remove it
|
2026-05-20 13:49:35 -04:00
|
|
|
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
|
|
|
|
|
|
2026-05-20 13:52:59 -04:00
|
|
|
# Find the node we just created (match by hostname)
|
|
|
|
|
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)
|
2026-05-20 13:49:35 -04:00
|
|
|
|
|
|
|
|
if [[ -n "$NODE_ID" ]]; then
|
|
|
|
|
pass "Cluster node visible in registry"
|
2026-05-20 13:52:59 -04:00
|
|
|
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"
|
2026-05-20 13:49:35 -04:00
|
|
|
else
|
2026-05-20 13:52:59 -04:00
|
|
|
skip "Cluster node visible in registry" "could not parse node id from response"
|
2026-05-20 13:49:35 -04:00
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ── System / Containers ───────────────────────────────────────────────────────
|
|
|
|
|
header "System"
|
2026-05-20 13:52:59 -04:00
|
|
|
check_status "GET /system/containers" "/api/v1/system/containers" 200
|
|
|
|
|
check_body "Containers returns array" "/api/v1/system/containers" '['
|
2026-05-20 13:49:35 -04:00
|
|
|
|
2026-05-20 13:52:59 -04:00
|
|
|
# ── Capture (proxies to capture service) ─────────────────────────────────────
|
2026-05-20 13:49:35 -04:00
|
|
|
header "Capture"
|
2026-05-20 13:52:59 -04:00
|
|
|
# /capture/status proxies to the capture container; 200 = service up, 5xx = capture 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]"
|
|
|
|
|
elif [[ "$CAPTURE_CODE" == "500" || "$CAPTURE_CODE" == "502" || "$CAPTURE_CODE" == "503" ]]; then
|
|
|
|
|
skip "GET /capture/status [HTTP $CAPTURE_CODE]" "capture service unreachable (container down?)"
|
|
|
|
|
else
|
|
|
|
|
fail "GET /capture/status [HTTP $CAPTURE_CODE]" "expected 200 or 5xx proxy error"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ── Settings ─────────────────────────────────────────────────────────────────
|
|
|
|
|
header "Settings"
|
|
|
|
|
check_status "GET /settings" "/api/v1/settings" 200
|
2026-05-20 13:49:35 -04:00
|
|
|
|
2026-05-20 13:52:59 -04:00
|
|
|
# ── Users / Tokens ───────────────────────────────────────────────────────────
|
2026-05-20 13:49:35 -04:00
|
|
|
header "Users / Tokens"
|
2026-05-20 13:52:59 -04:00
|
|
|
check_status "GET /users" "/api/v1/users" 200
|
|
|
|
|
check_status "GET /tokens" "/api/v1/tokens" 200
|
2026-05-20 13:49:35 -04:00
|
|
|
|
|
|
|
|
# ── 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
|
2026-05-20 13:52:59 -04:00
|
|
|
echo -e "${RED}Some tests failed — check output above.${NC}"
|
2026-05-20 13:49:35 -04:00
|
|
|
exit 1
|
|
|
|
|
else
|
|
|
|
|
echo -e "${GRN}All tests passed.${NC}"
|
|
|
|
|
fi
|