#!/bin/bash # Round 11 API Contract Test Script # Tests all API endpoints for correctness, error handling, and boundary conditions BASE="http://localhost:8080/api/v1" PASS=0 FAIL=0 RESULTS="" # Helper: run a test and record result test_case() { local name="$1" local expected="$2" local method="$3" local url="$4" local data="$5" local auth="$6" if [ -n "$data" ]; then resp=$(curl -s -w "\n%{http_code}" -X "$method" "$url" \ -H "Content-Type: application/json" \ ${auth:+-H "Authorization: Bearer $auth"} \ -d "$data" 2>&1) else resp=$(curl -s -w "\n%{http_code}" -X "$method" "$url" \ -H "Content-Type: application/json" \ ${auth:+-H "Authorization: Bearer $auth"} 2>&1) fi actual=$(echo "$resp" | tail -1) body=$(echo "$resp" | sed '$d') if [ "$actual" = "$expected" ]; then PASS=$((PASS + 1)) RESULTS+="PASS | $name | $expected | $actual\n" else FAIL=$((FAIL + 1)) RESULTS+="FAIL | $name | $expected | $actual | body=${body:0:120}\n" fi } echo "============================================" echo " Round 11 API Contract Test Suite" echo " Gateway: $BASE" echo " Started at $(date '+%Y-%m-%d %H:%M:%S')" echo "============================================" echo "" # ============================================================ # PART 1: Health + Public # ============================================================ echo "--- Part 1: Health & Public Endpoints ---" test_case "GET /health" "200" "GET" "$BASE/health" test_case "HEAD /health" "200" "HEAD" "$BASE/health" # ============================================================ # PART 2: Auth - Register # ============================================================ echo "--- Part 2: Auth Register ---" # Use unique username to avoid conflicts UN="testapi_$(date +%s)" PW="TestPass123!" test_case "Register: missing username" "400" "POST" "$BASE/auth/register" \ '{"password":"Test123!","email":"test@test.com","nickname":"Test","verify_code":"000000"}' test_case "Register: missing password" "400" "POST" "$BASE/auth/register" \ '{"username":"testuser","email":"test@test.com","nickname":"Test","verify_code":"000000"}' test_case "Register: username too short (<3)" "400" "POST" "$BASE/auth/register" \ '{"username":"ab","password":"Test123!","email":"test@test.com","nickname":"Test","verify_code":"000000"}' test_case "Register: username too long (>32)" "400" "POST" "$BASE/auth/register" \ '{"username":"abcdefghijklmnopqrstuvwxyz1234567890","password":"Test123!","email":"test@test.com","nickname":"Test","verify_code":"000000"}' test_case "Register: username with special chars" "400" "POST" "$BASE/auth/register" \ '{"username":"user@name!","password":"Test123!","email":"test@test.com","nickname":"Test","verify_code":"000000"}' test_case "Register: password too short (<6)" "400" "POST" "$BASE/auth/register" \ '{"username":"test_abc","password":"Ab1!","email":"test@test.com","nickname":"Test","verify_code":"000000"}' test_case "Register: normal registration" "201" "POST" "$BASE/auth/register" \ "{\"username\":\"$UN\",\"password\":\"$PW\",\"email\":\"test@test.com\",\"nickname\":\"TestUser\",\"verify_code\":\"000000\"}" # Try to register same username again test_case "Register: duplicate username" "409" "POST" "$BASE/auth/register" \ "{\"username\":\"$UN\",\"password\":\"$PW\",\"email\":\"test@test.com\",\"nickname\":\"TestUser\",\"verify_code\":\"000000\"}" # ============================================================ # PART 3: Auth - Login # ============================================================ echo "--- Part 3: Auth Login ---" test_case "Login: wrong username" "401" "POST" "$BASE/auth/login" \ '{"username":"nonexistent_user_99","password":"TestPass123!"}' test_case "Login: wrong password" "401" "POST" "$BASE/auth/login" \ "{\"username\":\"$UN\",\"password\":\"WrongPass999!\"}" # Correct login LOGIN_RESP=$(curl -s -w "\n%{http_code}" -X POST "$BASE/auth/login" \ -H "Content-Type: application/json" \ -d "{\"username\":\"$UN\",\"password\":\"$PW\"}") LOGIN_CODE=$(echo "$LOGIN_RESP" | tail -1) LOGIN_BODY=$(echo "$LOGIN_RESP" | sed '$d') if [ "$LOGIN_CODE" = "200" ]; then TOKEN=$(echo "$LOGIN_BODY" | python3 -c "import sys,json;print(json.load(sys.stdin).get('token',''))" 2>/dev/null) USER_ID=$(echo "$LOGIN_BODY" | python3 -c "import sys,json;print(json.load(sys.stdin).get('user_id',''))" 2>/dev/null) PASS=$((PASS + 1)) RESULTS+="PASS | Login: correct credentials | 200 | 200\n" echo " -> Got token OK, user_id=$USER_ID" else FAIL=$((FAIL + 1)) RESULTS+="FAIL | Login: correct credentials | 200 | $LOGIN_CODE | body=${LOGIN_BODY:0:120}\n" echo " -> Login FAILED: $LOGIN_CODE - $LOGIN_BODY" TOKEN="" fi # Admin login ADMIN_RESP=$(curl -s -w "\n%{http_code}" -X POST "$BASE/auth/login" \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"admin123"}') ADMIN_CODE=$(echo "$ADMIN_RESP" | tail -1) ADMIN_BODY=$(echo "$ADMIN_RESP" | sed '$d') if [ "$ADMIN_CODE" = "200" ]; then ADMIN_TOKEN=$(echo "$ADMIN_BODY" | python3 -c "import sys,json;print(json.load(sys.stdin).get('token',''))" 2>/dev/null) PASS=$((PASS + 1)) RESULTS+="PASS | Login: admin credentials | 200 | 200\n" echo " -> Admin token OK" else FAIL=$((FAIL + 1)) RESULTS+="FAIL | Login: admin credentials | 200 | $ADMIN_CODE | body=${ADMIN_BODY:0:120}\n" echo " -> Admin login FAILED: $ADMIN_CODE" ADMIN_TOKEN="" fi # Validate JWT token - try to use it if [ -n "$TOKEN" ]; then REFRESH_RESP=$(curl -s -w "\n%{http_code}" -X POST "$BASE/auth/refresh" \ -H "Authorization: Bearer $TOKEN") REFRESH_CODE=$(echo "$REFRESH_RESP" | tail -1) if [ "$REFRESH_CODE" = "200" ]; then PASS=$((PASS + 1)) RESULTS+="PASS | JWT token valid (refresh) | 200 | 200\n" else FAIL=$((FAIL + 1)) RESULTS+="FAIL | JWT token valid (refresh) | 200 | $REFRESH_CODE\n" fi fi # Login: missing fields test_case "Login: missing password" "400" "POST" "$BASE/auth/login" \ '{"username":"testuser"}' test_case "Login: empty body" "400" "POST" "$BASE/auth/login" '{}' test_case "Login: username format invalid" "400" "POST" "$BASE/auth/login" \ '{"username":"ab","password":"Test123!"}' # ============================================================ # PART 4: Session API # ============================================================ echo "--- Part 4: Session API ---" # Unauthenticated test_case "Sessions: list no auth" "401" "GET" "$BASE/sessions" test_case "Sessions: create no auth" "401" "POST" "$BASE/sessions" '{"title":"Test"}' if [ -n "$TOKEN" ]; then # Authenticated - List test_case "Sessions: list with auth" "200" "GET" "$BASE/sessions" "" "$TOKEN" # Create session SESS_RESP=$(curl -s -w "\n%{http_code}" -X POST "$BASE/sessions" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d '{"title":"Round 11 Test Session"}') SESS_CODE=$(echo "$SESS_RESP" | tail -1) SESS_BODY=$(echo "$SESS_RESP" | sed '$d') SESS_ID=$(echo "$SESS_BODY" | python3 -c "import sys,json;print(json.load(sys.stdin).get('id',''))" 2>/dev/null) if [ "$SESS_CODE" = "201" ]; then PASS=$((PASS + 1)) RESULTS+="PASS | Sessions: create | 201 | 201\n" echo " -> Created session: $SESS_ID" else FAIL=$((FAIL + 1)) RESULTS+="FAIL | Sessions: create | 201 | $SESS_CODE\n" fi # Get session if [ -n "$SESS_ID" ]; then test_case "Sessions: get existing" "200" "GET" "$BASE/sessions/$SESS_ID" "" "$TOKEN" fi # Get non-existent session test_case "Sessions: get non-existent" "404" "GET" "$BASE/sessions/session_nonexistent123" "" "$TOKEN" # Create with empty title (should default) test_case "Sessions: create empty title" "201" "POST" "$BASE/sessions" '{}' "$TOKEN" # Delete session if [ -n "$SESS_ID" ]; then test_case "Sessions: delete existing" "200" "DELETE" "$BASE/sessions/$SESS_ID" "" "$TOKEN" fi # Delete non-existent test_case "Sessions: delete non-existent" "200" "DELETE" "$BASE/sessions/session_nonexistent" "" "$TOKEN" # Get messages for non-existent session test_case "Sessions: messages non-existent" "200" "GET" "$BASE/sessions/session_nonexistent/messages" "" "$TOKEN" # Test cross-user access if [ -n "$ADMIN_TOKEN" ] && [ -n "$SESS_ID" ]; then echo " -> Testing cross-user access..." fi else FAIL=$((FAIL + 6)) echo " -> SKIPPED (no token)" fi # ============================================================ # PART 5: Files API # ============================================================ echo "--- Part 5: Files API ---" test_case "Files: list no auth" "401" "GET" "$BASE/files" if [ -n "$TOKEN" ]; then test_case "Files: list with auth" "200" "GET" "$BASE/files" "" "$TOKEN" test_case "Files: get non-existent" "404" "GET" "$BASE/files/file_nonexistent" "" "$TOKEN" else FAIL=$((FAIL + 2)) fi # ============================================================ # PART 6: Knowledge API # ============================================================ echo "--- Part 6: Knowledge API ---" test_case "Knowledge: list bases no auth" "401" "GET" "$BASE/knowledge/bases" if [ -n "$TOKEN" ]; then test_case "Knowledge: list bases with auth" "200" "GET" "$BASE/knowledge/bases" "" "$TOKEN" test_case "Knowledge: get non-existent" "404" "GET" "$BASE/knowledge/bases/kb_nonexistent" "" "$TOKEN" else FAIL=$((FAIL + 2)) fi # ============================================================ # PART 7: Automation API # ============================================================ echo "--- Part 7: Automation API ---" test_case "Automation: list rules no auth" "401" "GET" "$BASE/automation/rules" if [ -n "$TOKEN" ]; then test_case "Automation: list rules with auth" "200" "GET" "$BASE/automation/rules" "" "$TOKEN" test_case "Automation: list scenes with auth" "200" "GET" "$BASE/automation/scenes" "" "$TOKEN" test_case "Automation: get non-existent rule" "404" "GET" "$BASE/automation/rules/rule_nonexistent" "" "$TOKEN" else FAIL=$((FAIL + 3)) fi # ============================================================ # PART 8: Reminders API # ============================================================ echo "--- Part 8: Reminders API ---" test_case "Reminders: list no auth" "401" "GET" "$BASE/reminders" if [ -n "$TOKEN" ]; then test_case "Reminders: list with auth" "200" "GET" "$BASE/reminders" "" "$TOKEN" test_case "Reminders: create missing fields" "400" "POST" "$BASE/reminders" '{}' "$TOKEN" else FAIL=$((FAIL + 2)) fi # ============================================================ # PART 9: Briefings API # ============================================================ echo "--- Part 9: Briefings API ---" test_case "Briefings: get no auth" "401" "GET" "$BASE/briefings" if [ -n "$TOKEN" ]; then test_case "Briefings: get with auth" "200" "GET" "$BASE/briefings" "" "$TOKEN" test_case "Briefings: latest with auth" "200" "GET" "$BASE/briefings/latest" "" "$TOKEN" else FAIL=$((FAIL + 2)) fi # ============================================================ # PART 10: Notifications API # ============================================================ echo "--- Part 10: Notifications API ---" test_case "Notifications: push no auth" "401" "POST" "$BASE/notifications/push" '{"message":"test"}' if [ -n "$TOKEN" ]; then test_case "Notifications: push empty" "400" "POST" "$BASE/notifications/push" '{}' "$TOKEN" else FAIL=$((FAIL + 1)) fi # ============================================================ # PART 11: Memories API # ============================================================ echo "--- Part 11: Memories API ---" test_case "Memories: search no auth" "401" "GET" "$BASE/memory/search" if [ -n "$TOKEN" ]; then test_case "Memories: search with auth" "200" "GET" "$BASE/memory/search?q=test" "" "$TOKEN" test_case "Memories: list with auth" "200" "GET" "$BASE/memory" "" "$TOKEN" test_case "Memories: add missing fields" "400" "POST" "$BASE/memory" '{}' "$TOKEN" else FAIL=$((FAIL + 3)) fi # ============================================================ # PART 12: Voice API # ============================================================ echo "--- Part 12: Voice API ---" if [ -n "$TOKEN" ]; then test_case "Voice: status with auth" "200" "GET" "$BASE/voice/status" "" "$TOKEN" else FAIL=$((FAIL + 1)) fi # ============================================================ # PART 13: Admin endpoints # ============================================================ echo "--- Part 13: Admin endpoints ---" if [ -n "$ADMIN_TOKEN" ]; then test_case "Admin: sessions with admin" "200" "GET" "$BASE/admin/sessions" "" "$ADMIN_TOKEN" test_case "Admin: sessions/active with admin" "200" "GET" "$BASE/admin/sessions/active" "" "$ADMIN_TOKEN" else FAIL=$((FAIL + 2)) fi # Non-admin user trying admin if [ -n "$TOKEN" ]; then test_case "Admin: sessions without admin" "403" "GET" "$BASE/admin/sessions" "" "$TOKEN" else FAIL=$((FAIL + 1)) fi # ============================================================ # PART 14: Invalid token # ============================================================ echo "--- Part 14: Invalid Token ---" test_case "Sessions: invalid token" "401" "GET" "$BASE/sessions" "" "invalidtoken12345" test_case "Sessions: expired/malformed" "401" "GET" "$BASE/sessions" "" "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8k" # ============================================================ # PART 15: Token refresh # ============================================================ echo "--- Part 15: Token Refresh ---" test_case "Refresh: no auth" "401" "POST" "$BASE/auth/refresh" test_case "Refresh: invalid token" "401" "POST" "$BASE/auth/refresh" "" "invalidtoken" # ============================================================ # Summary # ============================================================ echo "" echo "============================================" echo " TEST SUMMARY" echo "============================================" TOTAL=$((PASS + FAIL)) echo "Total: $TOTAL | Passed: $PASS | Failed: $FAIL" echo "" echo -e "$RESULTS" | column -t -s '|' echo "" echo "============================================" echo " Test run completed at $(date '+%Y-%m-%d %H:%M:%S')" echo "============================================" # Save the token and user info for reference if [ -n "$TOKEN" ]; then echo "TOKEN=$TOKEN" > /tmp/round11_testenv echo "USER_ID=$USER_ID" >> /tmp/round11_testenv echo "ADMIN_TOKEN=$ADMIN_TOKEN" >> /tmp/round11_testenv echo "TEST_USER=$UN" >> /tmp/round11_testenv fi