#!/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