344 lines
18 KiB
Bash
344 lines
18 KiB
Bash
#!/usr/bin/env bash
|
|
# IT0 API Comprehensive Curl Test Suite
|
|
# Usage: bash scripts/api-test.sh [BASE_URL]
|
|
# Default BASE_URL: https://it0api.szaiai.com
|
|
|
|
set -uo pipefail
|
|
|
|
BASE="${1:-https://it0api.szaiai.com}"
|
|
PASS=0
|
|
FAIL=0
|
|
TOKEN=""
|
|
REFRESH_TOKEN=""
|
|
TENANT_ID=""
|
|
|
|
# ─── colors ───────────────────────────────────────────────────────────────────
|
|
GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
|
|
|
|
ok() { echo -e "${GREEN}[PASS]${NC} $1"; PASS=$((PASS + 1)); }
|
|
fail() { echo -e "${RED}[FAIL]${NC} $1"; FAIL=$((FAIL + 1)); }
|
|
info() { echo -e "${CYAN}[INFO]${NC} $1"; }
|
|
section() { echo -e "\n${YELLOW}══ $1 ══${NC}"; }
|
|
|
|
# ─── helpers ──────────────────────────────────────────────────────────────────
|
|
get_status() {
|
|
curl -s -o /dev/null -w "%{http_code}" "$@"
|
|
}
|
|
|
|
post_json() {
|
|
local url="$1"; shift
|
|
curl -s -w "\n__STATUS__%{http_code}" -X POST \
|
|
-H "Content-Type: application/json" \
|
|
"$@" "$url"
|
|
}
|
|
|
|
auth_header() {
|
|
echo "-H Authorization: Bearer ${TOKEN}"
|
|
}
|
|
|
|
# ─── Unique test values ────────────────────────────────────────────────────────
|
|
TS=$(date +%s)
|
|
TEST_EMAIL="apitest_${TS}@example.com"
|
|
TEST_PHONE="+861380${TS: -7}"
|
|
TEST_COMPANY="TestCo${TS}"
|
|
TEST_PASS="Test@12345"
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
section "1. Health Checks"
|
|
|
|
# Kong gateway is healthy if auth routes respond (no /health route on Kong)
|
|
STATUS=$(get_status "${BASE}/api/v1/auth/profile")
|
|
[[ "$STATUS" == "401" ]] && ok "Kong gateway reachable (auth/profile returns 401 without token)" \
|
|
|| fail "Kong gateway unreachable → ${STATUS}"
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
section "2. Auth — Register (Email)"
|
|
|
|
RESP=$(post_json "${BASE}/api/v1/auth/register" \
|
|
-d "{\"email\":\"${TEST_EMAIL}\",\"password\":\"${TEST_PASS}\",\"name\":\"Test User\",\"companyName\":\"${TEST_COMPANY}\"}")
|
|
STATUS=$(echo "$RESP" | grep -o '__STATUS__[0-9]*' | sed 's/__STATUS__//')
|
|
BODY=$(echo "$RESP" | sed 's/__STATUS__[0-9]*//')
|
|
|
|
if [[ "$STATUS" == "201" || "$STATUS" == "200" ]]; then
|
|
ok "POST /api/v1/auth/register (email) → ${STATUS}"
|
|
TOKEN=$(echo "$BODY" | grep -o '"accessToken":"[^"]*"' | cut -d'"' -f4)
|
|
REFRESH_TOKEN=$(echo "$BODY" | grep -o '"refreshToken":"[^"]*"' | cut -d'"' -f4)
|
|
TENANT_ID=$(echo "$BODY" | grep -o '"tenantId":"[^"]*"' | cut -d'"' -f4)
|
|
info "Tenant: ${TENANT_ID}"
|
|
else
|
|
fail "POST /api/v1/auth/register (email) → ${STATUS}: ${BODY}"
|
|
fi
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
section "3. Auth — Register (Phone)"
|
|
|
|
PHONE_COMPANY="PhoneCo${TS}"
|
|
RESP=$(post_json "${BASE}/api/v1/auth/register" \
|
|
-d "{\"phone\":\"${TEST_PHONE}\",\"password\":\"${TEST_PASS}\",\"name\":\"Phone User\",\"companyName\":\"${PHONE_COMPANY}\"}")
|
|
STATUS=$(echo "$RESP" | grep -o '__STATUS__[0-9]*' | sed 's/__STATUS__//')
|
|
BODY=$(echo "$RESP" | sed 's/__STATUS__[0-9]*//')
|
|
|
|
if [[ "$STATUS" == "201" || "$STATUS" == "200" ]]; then
|
|
ok "POST /api/v1/auth/register (phone) → ${STATUS}"
|
|
else
|
|
fail "POST /api/v1/auth/register (phone) → ${STATUS}: ${BODY}"
|
|
fi
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
section "4. Auth — Duplicate registration should fail"
|
|
|
|
# Same company name → 409 on slug conflict
|
|
RESP=$(post_json "${BASE}/api/v1/auth/register" \
|
|
-d "{\"email\":\"dup_${TS}@example.com\",\"password\":\"${TEST_PASS}\",\"name\":\"Dup\",\"companyName\":\"${TEST_COMPANY}\"}")
|
|
STATUS=$(echo "$RESP" | grep -o '__STATUS__[0-9]*' | sed 's/__STATUS__//')
|
|
[[ "$STATUS" == "409" ]] && ok "Duplicate company name → 409 Conflict" || fail "Duplicate company → expected 409, got ${STATUS}"
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
section "5. Auth — Login (email)"
|
|
|
|
RESP=$(post_json "${BASE}/api/v1/auth/login" \
|
|
-d "{\"email\":\"${TEST_EMAIL}\",\"password\":\"${TEST_PASS}\"}")
|
|
STATUS=$(echo "$RESP" | grep -o '__STATUS__[0-9]*' | sed 's/__STATUS__//')
|
|
BODY=$(echo "$RESP" | sed 's/__STATUS__[0-9]*//')
|
|
|
|
if [[ "$STATUS" == "200" || "$STATUS" == "201" ]]; then
|
|
ok "POST /api/v1/auth/login (email) → ${STATUS}"
|
|
TOKEN=$(echo "$BODY" | grep -o '"accessToken":"[^"]*"' | cut -d'"' -f4)
|
|
REFRESH_TOKEN=$(echo "$BODY" | grep -o '"refreshToken":"[^"]*"' | cut -d'"' -f4)
|
|
else
|
|
fail "POST /api/v1/auth/login (email) → ${STATUS}: ${BODY}"
|
|
fi
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
section "6. Auth — Login (phone)"
|
|
|
|
RESP=$(post_json "${BASE}/api/v1/auth/login" \
|
|
-d "{\"phone\":\"${TEST_PHONE}\",\"password\":\"${TEST_PASS}\"}")
|
|
STATUS=$(echo "$RESP" | grep -o '__STATUS__[0-9]*' | sed 's/__STATUS__//')
|
|
[[ "$STATUS" == "200" || "$STATUS" == "201" ]] && ok "POST /api/v1/auth/login (phone) → ${STATUS}" \
|
|
|| fail "POST /api/v1/auth/login (phone) → ${STATUS}"
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
section "7. Auth — Login (identifier field)"
|
|
|
|
RESP=$(post_json "${BASE}/api/v1/auth/login" \
|
|
-d "{\"identifier\":\"${TEST_EMAIL}\",\"password\":\"${TEST_PASS}\"}")
|
|
STATUS=$(echo "$RESP" | grep -o '__STATUS__[0-9]*' | sed 's/__STATUS__//')
|
|
[[ "$STATUS" == "200" || "$STATUS" == "201" ]] && ok "POST /api/v1/auth/login (identifier) → ${STATUS}" \
|
|
|| fail "POST /api/v1/auth/login (identifier) → ${STATUS}"
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
section "8. Auth — Wrong password should be 401"
|
|
|
|
RESP=$(post_json "${BASE}/api/v1/auth/login" \
|
|
-d "{\"email\":\"${TEST_EMAIL}\",\"password\":\"wrongpass\"}")
|
|
STATUS=$(echo "$RESP" | grep -o '__STATUS__[0-9]*' | sed 's/__STATUS__//')
|
|
[[ "$STATUS" == "401" ]] && ok "Wrong password → 401" || fail "Wrong password → expected 401, got ${STATUS}"
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
section "9. Auth — Get profile (JWT required)"
|
|
|
|
if [[ -n "$TOKEN" ]]; then
|
|
STATUS=$(get_status "${BASE}/api/v1/auth/profile" \
|
|
-H "Authorization: Bearer ${TOKEN}")
|
|
[[ "$STATUS" == "200" ]] && ok "GET /api/v1/auth/profile → 200" || fail "GET /api/v1/auth/profile → ${STATUS}"
|
|
else
|
|
fail "GET /api/v1/auth/profile — no token available"
|
|
fi
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
section "10. Auth — Profile without token should be 401"
|
|
|
|
STATUS=$(get_status "${BASE}/api/v1/auth/profile")
|
|
[[ "$STATUS" == "401" ]] && ok "GET /api/v1/auth/profile (no token) → 401" \
|
|
|| fail "GET /api/v1/auth/profile (no token) → expected 401, got ${STATUS}"
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
section "11. Auth — Refresh token"
|
|
|
|
if [[ -n "$REFRESH_TOKEN" ]]; then
|
|
RESP=$(post_json "${BASE}/api/v1/auth/refresh" \
|
|
-d "{\"refreshToken\":\"${REFRESH_TOKEN}\"}")
|
|
STATUS=$(echo "$RESP" | grep -o '__STATUS__[0-9]*' | sed 's/__STATUS__//')
|
|
[[ "$STATUS" == "200" || "$STATUS" == "201" ]] && ok "POST /api/v1/auth/refresh → ${STATUS}" \
|
|
|| fail "POST /api/v1/auth/refresh → ${STATUS}"
|
|
else
|
|
fail "POST /api/v1/auth/refresh — no refresh token"
|
|
fi
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
section "12. Auth — Platform admin login (ops@it0.com)"
|
|
|
|
RESP=$(post_json "${BASE}/api/v1/auth/login" \
|
|
-d '{"email":"ops@it0.com","password":"it0ops2024"}')
|
|
STATUS=$(echo "$RESP" | grep -o '__STATUS__[0-9]*' | sed 's/__STATUS__//')
|
|
BODY=$(echo "$RESP" | sed 's/__STATUS__[0-9]*//')
|
|
|
|
if [[ "$STATUS" == "200" || "$STATUS" == "201" ]]; then
|
|
ok "Platform admin login → ${STATUS}"
|
|
ADMIN_TOKEN=$(echo "$BODY" | grep -o '"accessToken":"[^"]*"' | cut -d'"' -f4)
|
|
info "Admin token: ${ADMIN_TOKEN:0:40}..."
|
|
else
|
|
fail "Platform admin login → ${STATUS}: ${BODY}"
|
|
ADMIN_TOKEN=""
|
|
fi
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
section "13. Tenants — List (platform admin via /api/v1/admin/tenants)"
|
|
|
|
if [[ -n "${ADMIN_TOKEN:-}" ]]; then
|
|
STATUS=$(get_status "${BASE}/api/v1/admin/tenants" \
|
|
-H "Authorization: Bearer ${ADMIN_TOKEN}")
|
|
[[ "$STATUS" == "200" ]] && ok "GET /api/v1/admin/tenants → 200" || fail "GET /api/v1/admin/tenants → ${STATUS}"
|
|
else
|
|
info "Skipped — no admin token"
|
|
fi
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
section "14. Users — List (platform admin via /api/v1/auth/users)"
|
|
|
|
if [[ -n "${ADMIN_TOKEN:-}" ]]; then
|
|
STATUS=$(get_status "${BASE}/api/v1/auth/users" \
|
|
-H "Authorization: Bearer ${ADMIN_TOKEN}")
|
|
[[ "$STATUS" == "200" ]] && ok "GET /api/v1/auth/users → 200" || fail "GET /api/v1/auth/users → ${STATUS}"
|
|
else
|
|
info "Skipped — no platform admin token"
|
|
fi
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
section "15. Billing — Plans (JWT required per Kong config)"
|
|
|
|
if [[ -n "$TOKEN" ]]; then
|
|
STATUS=$(get_status "${BASE}/api/v1/billing/plans" \
|
|
-H "Authorization: Bearer ${TOKEN}")
|
|
[[ "$STATUS" == "200" ]] && ok "GET /api/v1/billing/plans (JWT) → 200" \
|
|
|| fail "GET /api/v1/billing/plans (JWT) → ${STATUS}"
|
|
fi
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
section "16. Billing — Subscription (JWT required)"
|
|
|
|
if [[ -n "$TOKEN" ]]; then
|
|
STATUS=$(get_status "${BASE}/api/v1/billing/subscription" \
|
|
-H "Authorization: Bearer ${TOKEN}")
|
|
# 200 = has subscription, 404 = new tenant with no subscription yet (both acceptable)
|
|
[[ "$STATUS" == "200" || "$STATUS" == "404" ]] \
|
|
&& ok "GET /api/v1/billing/subscription → ${STATUS} (200=active, 404=no subscription)" \
|
|
|| fail "GET /api/v1/billing/subscription → ${STATUS}"
|
|
fi
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
section "17. Billing — Invoices (JWT required)"
|
|
|
|
if [[ -n "$TOKEN" ]]; then
|
|
STATUS=$(get_status "${BASE}/api/v1/billing/invoices" \
|
|
-H "Authorization: Bearer ${TOKEN}")
|
|
[[ "$STATUS" == "200" ]] && ok "GET /api/v1/billing/invoices → 200" \
|
|
|| fail "GET /api/v1/billing/invoices → ${STATUS}"
|
|
fi
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
section "18. App Version — Public check endpoint"
|
|
|
|
STATUS=$(get_status "${BASE}/api/app/version/check?platform=android¤t_version_code=0")
|
|
[[ "$STATUS" == "200" ]] && ok "GET /api/app/version/check → 200" \
|
|
|| fail "GET /api/app/version/check → ${STATUS}"
|
|
|
|
RESP=$(curl -s "${BASE}/api/app/version/check?platform=android¤t_version_code=0")
|
|
if echo "$RESP" | grep -q '"needUpdate"'; then
|
|
ok "Version check response has needUpdate field"
|
|
else
|
|
fail "Version check response missing needUpdate field: ${RESP}"
|
|
fi
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
section "19. App Versions — Admin list (JWT required)"
|
|
|
|
if [[ -n "${ADMIN_TOKEN:-}" ]]; then
|
|
STATUS=$(get_status "${BASE}/api/v1/versions" \
|
|
-H "Authorization: Bearer ${ADMIN_TOKEN}")
|
|
[[ "$STATUS" == "200" ]] && ok "GET /api/v1/versions → 200" || fail "GET /api/v1/versions → ${STATUS}"
|
|
elif [[ -n "$TOKEN" ]]; then
|
|
STATUS=$(get_status "${BASE}/api/v1/versions" \
|
|
-H "Authorization: Bearer ${TOKEN}")
|
|
[[ "$STATUS" == "200" ]] && ok "GET /api/v1/versions → 200" || fail "GET /api/v1/versions → ${STATUS}"
|
|
else
|
|
info "Skipped — no token"
|
|
fi
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
section "20. Inventory — Servers (JWT required)"
|
|
|
|
if [[ -n "$TOKEN" ]]; then
|
|
STATUS=$(get_status "${BASE}/api/v1/inventory/servers" \
|
|
-H "Authorization: Bearer ${TOKEN}")
|
|
[[ "$STATUS" == "200" ]] && ok "GET /api/v1/inventory/servers → 200" || fail "GET /api/v1/inventory/servers → ${STATUS}"
|
|
fi
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
section "21. Agent Sessions — List (JWT required)"
|
|
|
|
if [[ -n "$TOKEN" ]]; then
|
|
STATUS=$(get_status "${BASE}/api/v1/agent/sessions" \
|
|
-H "Authorization: Bearer ${TOKEN}")
|
|
[[ "$STATUS" == "200" ]] && ok "GET /api/v1/agent/sessions → 200" \
|
|
|| fail "GET /api/v1/agent/sessions → ${STATUS}"
|
|
fi
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
section "22. Roles — List (JWT required via /api/v1/auth/roles)"
|
|
|
|
if [[ -n "$TOKEN" ]]; then
|
|
STATUS=$(get_status "${BASE}/api/v1/auth/roles" \
|
|
-H "Authorization: Bearer ${TOKEN}")
|
|
[[ "$STATUS" == "200" ]] && ok "GET /api/v1/auth/roles → 200" || fail "GET /api/v1/auth/roles → ${STATUS}"
|
|
fi
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
section "23. API Keys — Create (JWT required)"
|
|
|
|
if [[ -n "$TOKEN" ]]; then
|
|
RESP=$(post_json "${BASE}/api/v1/auth/api-keys" \
|
|
-H "Authorization: Bearer ${TOKEN}" \
|
|
-d "{\"name\":\"test-key-${TS}\"}")
|
|
STATUS=$(echo "$RESP" | grep -o '__STATUS__[0-9]*' | sed 's/__STATUS__//')
|
|
[[ "$STATUS" == "200" || "$STATUS" == "201" ]] && ok "POST /api/v1/auth/api-keys → ${STATUS}" \
|
|
|| fail "POST /api/v1/auth/api-keys → ${STATUS}"
|
|
fi
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
section "24. Invite flow — Create + validate invite"
|
|
|
|
# Uses TOKEN (tenant admin, 'admin' role) to create invite for their own tenant
|
|
if [[ -n "${TOKEN:-}" && -n "${TENANT_ID:-}" ]]; then
|
|
INVITE_EMAIL="invite_${TS}@example.com"
|
|
RESP=$(post_json "${BASE}/api/v1/admin/tenants/${TENANT_ID}/invites" \
|
|
-H "Authorization: Bearer ${TOKEN}" \
|
|
-d "{\"email\":\"${INVITE_EMAIL}\",\"role\":\"viewer\"}")
|
|
STATUS=$(echo "$RESP" | grep -o '__STATUS__[0-9]*' | sed 's/__STATUS__//')
|
|
BODY=$(echo "$RESP" | sed 's/__STATUS__[0-9]*//')
|
|
if [[ "$STATUS" == "200" || "$STATUS" == "201" ]]; then
|
|
ok "POST /api/v1/admin/tenants/:id/invites → ${STATUS}"
|
|
INVITE_TOKEN=$(echo "$BODY" | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
|
|
if [[ -n "$INVITE_TOKEN" ]]; then
|
|
STATUS2=$(get_status "${BASE}/api/v1/auth/invite/${INVITE_TOKEN}")
|
|
[[ "$STATUS2" == "200" ]] && ok "GET /api/v1/auth/invite/:token → 200" \
|
|
|| fail "GET /api/v1/auth/invite/:token → ${STATUS2}"
|
|
fi
|
|
else
|
|
fail "POST /api/v1/admin/tenants/:id/invites → ${STATUS}: ${BODY}"
|
|
fi
|
|
else
|
|
info "Skipped invite flow — need token + tenantId"
|
|
fi
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
section "Summary"
|
|
|
|
TOTAL=$((PASS + FAIL))
|
|
echo -e "\nTotal: ${TOTAL} ${GREEN}Pass: ${PASS}${NC} ${RED}Fail: ${FAIL}${NC}"
|
|
|
|
if [[ $FAIL -gt 0 ]]; then
|
|
exit 1
|
|
fi
|