aurganize-backend/backend/tests/scripts/auth_test_api.sh

652 lines
20 KiB
Bash

#!/bin/bash
# ==============================================================================
# AURGANIZE V6.2 - API TESTING SCRIPT
# ==============================================================================
# Purpose: Comprehensive testing of all API endpoints
# Usage: ./test-api.sh
# Requires: curl, jq (for JSON parsing)
# ==============================================================================
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# API Base URL
BASE_URL="http://localhost:8080/api/v1"
# Test data
TEST_EMAIL="test-$(date +%s)@example.com" # Unique email with timestamp
TEST_PASSWORD="Test123!@#"
TEST_FIRST_NAME="Test"
TEST_LAST_NAME="User"
TEST_TENANT_NAME="Test Company $(date +%s)"
# Variables to store tokens
ACCESS_TOKEN=""
REFRESH_TOKEN=""
USER_ID=""
TENANT_ID=""
echo "=========================================="
echo "AURGANIZE V6.2 API TESTING"
echo "=========================================="
echo ""
echo "Base URL: $BASE_URL"
echo "Test Email: $TEST_EMAIL"
echo ""
# ==============================================================================
# Helper Functions
# ==============================================================================
# Function to print test header
print_test() {
echo ""
echo -e "${BLUE}=========================================="
echo "TEST: $1"
echo -e "==========================================${NC}"
echo ""
}
# Function to print success
print_success() {
echo -e "${GREEN}$1${NC}"
}
# Function to print error
print_error() {
echo -e "${RED}$1${NC}"
}
# Function to print warning
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
# Function to check if jq is installed
check_jq() {
if ! command -v jq &> /dev/null; then
print_warning "jq is not installed (JSON parsing will be limited)"
echo " Install: apt-get install jq or brew install jq"
return 1
fi
return 0
}
# Check for jq
HAS_JQ=false
if check_jq; then
HAS_JQ=true
fi
# ==============================================================================
# TEST 1: Health Check (Unprotected)
# ==============================================================================
print_test "1. Health Check (Unprotected Route)"
echo "Request: GET /health"
RESPONSE=$(curl -s -w "\n%{http_code}" http://localhost:8080/health)
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | head -n-1)
echo "HTTP Status: $HTTP_CODE"
echo "Response Body:"
echo "$BODY" | jq '.' 2>/dev/null || echo "$BODY"
if [ "$HTTP_CODE" = "200" ]; then
print_success "Health check passed"
else
print_error "Health check failed (expected 200, got $HTTP_CODE)"
exit 1
fi
# ==============================================================================
# TEST 2: User Registration
# ==============================================================================
print_test "2. User Registration"
echo "Creating new user account..."
echo " Email: $TEST_EMAIL"
echo " Password: $TEST_PASSWORD"
echo " Name: $TEST_FIRST_NAME $TEST_LAST_NAME"
echo " Tenant: $TEST_TENANT_NAME"
echo ""
REGISTER_PAYLOAD=$(cat <<EOF
{
"email": "$TEST_EMAIL",
"password": "$TEST_PASSWORD",
"first_name": "$TEST_FIRST_NAME",
"last_name": "$TEST_LAST_NAME",
"tenant_name": "$TEST_TENANT_NAME"
}
EOF
)
echo "Request: POST /api/v1/auth/register"
echo "Payload:"
echo "$REGISTER_PAYLOAD" | jq '.' 2>/dev/null || echo "$REGISTER_PAYLOAD"
echo ""
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/auth/register" \
-H "Content-Type: application/json" \
-d "$REGISTER_PAYLOAD")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | head -n-1)
echo "HTTP Status: $HTTP_CODE"
echo "Response Body:"
echo "$BODY" | jq '.' 2>/dev/null || echo "$BODY"
if [ "$HTTP_CODE" = "201" ]; then
print_success "User registered successfully"
# Extract tokens and IDs
if [ "$HAS_JQ" = true ]; then
ACCESS_TOKEN=$(echo "$BODY" | jq -r '.access_token')
REFRESH_TOKEN=$(echo "$BODY" | jq -r '.refresh_token')
USER_ID=$(echo "$BODY" | jq -r '.user.id')
TENANT_ID=$(echo "$BODY" | jq -r '.tenant.id')
echo ""
echo "Extracted data:"
echo " User ID: $USER_ID"
echo " Tenant ID: $TENANT_ID"
echo " Access Token: ${ACCESS_TOKEN:0:30}..."
echo " Refresh Token: ${REFRESH_TOKEN:0:30}..."
else
print_warning "Install jq to extract tokens automatically"
echo "Manually extract access_token and refresh_token from response above"
fi
else
print_error "Registration failed (expected 201, got $HTTP_CODE)"
echo ""
echo "Common issues:"
echo " 1. Validator not registered (check main.go line 128)"
echo " 2. Database user doesn't exist"
echo " 3. Transaction interface mismatch"
exit 1
fi
# ==============================================================================
# TEST 3: Duplicate Registration (Should Fail)
# ==============================================================================
print_test "3. Duplicate Registration (Should Fail with 409)"
echo "Attempting to register same email again..."
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/auth/register" \
-H "Content-Type: application/json" \
-d "$REGISTER_PAYLOAD")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | head -n-1)
echo "HTTP Status: $HTTP_CODE"
echo "Response Body:"
echo "$BODY" | jq '.' 2>/dev/null || echo "$BODY"
if [ "$HTTP_CODE" = "409" ]; then
print_success "Correctly rejected duplicate email"
else
print_warning "Expected 409 Conflict, got $HTTP_CODE (check uniqueness constraint)"
fi
# ==============================================================================
# TEST 4: Invalid Registration (Weak Password)
# ==============================================================================
print_test "4. Invalid Registration - Weak Password"
WEAK_PASSWORD_PAYLOAD=$(cat <<EOF
{
"email": "weak-$(date +%s)@example.com",
"password": "weak",
"first_name": "Test",
"last_name": "User",
"tenant_name": "Test Tenant"
}
EOF
)
echo "Request: POST /api/v1/auth/register"
echo "Password: 'weak' (should fail validation)"
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/auth/register" \
-H "Content-Type: application/json" \
-d "$WEAK_PASSWORD_PAYLOAD")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | head -n-1)
echo "HTTP Status: $HTTP_CODE"
echo "Response Body:"
echo "$BODY" | jq '.' 2>/dev/null || echo "$BODY"
if [ "$HTTP_CODE" = "400" ]; then
print_success "Correctly rejected weak password"
else
print_warning "Expected 400 Bad Request, got $HTTP_CODE (check validator)"
fi
# ==============================================================================
# TEST 5: Rate Limiting (Registration)
# ==============================================================================
print_test "5. Rate Limiting on Registration (5 requests/minute)"
echo "Sending 6 rapid registration requests..."
echo "(Rate limit: 5 requests/minute)"
echo ""
RATE_LIMITED=false
for i in {1..6}; do
UNIQUE_EMAIL="ratelimit-$i-$(date +%s)@example.com"
RATE_LIMIT_PAYLOAD=$(cat <<EOF
{
"email": "$UNIQUE_EMAIL",
"password": "$TEST_PASSWORD",
"first_name": "Rate",
"last_name": "Test$i",
"tenant_name": "Rate Test $i"
}
EOF
)
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/auth/register" \
-H "Content-Type: application/json" \
-d "$RATE_LIMIT_PAYLOAD")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
echo "Request $i: HTTP $HTTP_CODE"
if [ "$HTTP_CODE" = "429" ]; then
print_success "Rate limit triggered on request $i"
RATE_LIMITED=true
break
fi
sleep 0.5
done
if [ "$RATE_LIMITED" = false ]; then
print_warning "Rate limit not triggered (check rate limiter middleware)"
fi
echo ""
echo "Waiting 60 seconds for rate limit to reset..."
sleep 5 # Shortened for testing, normally would be 60
# ==============================================================================
# TEST 6: User Login
# ==============================================================================
print_test "6. User Login"
LOGIN_PAYLOAD=$(cat <<EOF
{
"email": "$TEST_EMAIL",
"password": "$TEST_PASSWORD"
}
EOF
)
echo "Request: POST /api/v1/auth/login"
echo "Credentials: $TEST_EMAIL / $TEST_PASSWORD"
echo ""
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/auth/login" \
-H "Content-Type: application/json" \
-d "$LOGIN_PAYLOAD")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | head -n-1)
echo "HTTP Status: $HTTP_CODE"
echo "Response Body:"
echo "$BODY" | jq '.' 2>/dev/null || echo "$BODY"
if [ "$HTTP_CODE" = "200" ]; then
print_success "Login successful"
# Update tokens (login gives new tokens)
if [ "$HAS_JQ" = true ]; then
ACCESS_TOKEN=$(echo "$BODY" | jq -r '.access_token')
REFRESH_TOKEN=$(echo "$BODY" | jq -r '.refresh_token')
echo ""
echo "New tokens received:"
echo " Access Token: ${ACCESS_TOKEN:0:30}..."
echo " Refresh Token: ${REFRESH_TOKEN:0:30}..."
fi
else
print_error "Login failed (expected 200, got $HTTP_CODE)"
exit 1
fi
# ==============================================================================
# TEST 7: Login with Wrong Password
# ==============================================================================
print_test "7. Login with Wrong Password (Should Fail)"
WRONG_PASSWORD_PAYLOAD=$(cat <<EOF
{
"email": "$TEST_EMAIL",
"password": "WrongPassword123!"
}
EOF
)
echo "Request: POST /api/v1/auth/login"
echo "Using wrong password..."
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/auth/login" \
-H "Content-Type: application/json" \
-d "$WRONG_PASSWORD_PAYLOAD")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | head -n-1)
echo "HTTP Status: $HTTP_CODE"
echo "Response Body:"
echo "$BODY" | jq '.' 2>/dev/null || echo "$BODY"
if [ "$HTTP_CODE" = "401" ]; then
print_success "Correctly rejected invalid credentials"
else
print_warning "Expected 401 Unauthorized, got $HTTP_CODE"
fi
# ==============================================================================
# TEST 8: Protected Route (Health with Auth)
# ==============================================================================
print_test "8. Protected Health Endpoint"
if [ -z "$ACCESS_TOKEN" ]; then
print_error "No access token available (login may have failed)"
exit 1
fi
echo "Request: GET /api/v1/health"
echo "Authorization: Bearer ${ACCESS_TOKEN:0:30}..."
echo ""
RESPONSE=$(curl -s -w "\n%{http_code}" "$BASE_URL/health" \
-H "Authorization: Bearer $ACCESS_TOKEN")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | head -n-1)
echo "HTTP Status: $HTTP_CODE"
echo "Response Body:"
echo "$BODY" | jq '.' 2>/dev/null || echo "$BODY"
if [ "$HTTP_CODE" = "200" ]; then
print_success "Authentication middleware working"
else
print_error "Protected route failed (expected 200, got $HTTP_CODE)"
fi
# ==============================================================================
# TEST 9: Protected Route WITHOUT Token (Should Fail)
# ==============================================================================
print_test "9. Protected Route Without Token (Should Fail)"
echo "Request: GET /api/v1/health"
echo "Authorization: (none)"
echo ""
RESPONSE=$(curl -s -w "\n%{http_code}" "$BASE_URL/health")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | head -n-1)
echo "HTTP Status: $HTTP_CODE"
echo "Response Body:"
echo "$BODY" | jq '.' 2>/dev/null || echo "$BODY"
if [ "$HTTP_CODE" = "401" ]; then
print_success "Correctly rejected request without token"
else
print_warning "Expected 401 Unauthorized, got $HTTP_CODE"
fi
# ==============================================================================
# TEST 10: Get My Tenant
# ==============================================================================
print_test "10. Get My Tenant (Row-Level Security Test)"
echo "Request: GET /api/v1/tenants/mine"
echo "This tests:"
echo " - Authentication middleware"
echo " - Row-Level Security (RLS)"
echo " - Tenant context extraction"
echo ""
RESPONSE=$(curl -s -w "\n%{http_code}" "$BASE_URL/tenants/mine" \
-H "Authorization: Bearer $ACCESS_TOKEN")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | head -n-1)
echo "HTTP Status: $HTTP_CODE"
echo "Response Body:"
echo "$BODY" | jq '.' 2>/dev/null || echo "$BODY"
if [ "$HTTP_CODE" = "200" ]; then
print_success "Tenant retrieval successful (RLS working)"
if [ "$HAS_JQ" = true ]; then
RETRIEVED_TENANT_ID=$(echo "$BODY" | jq -r '.id')
TENANT_NAME=$(echo "$BODY" | jq -r '.name')
echo ""
echo "Tenant details:"
echo " ID: $RETRIEVED_TENANT_ID"
echo " Name: $TENANT_NAME"
if [ "$RETRIEVED_TENANT_ID" = "$TENANT_ID" ]; then
print_success "Tenant ID matches registration"
else
print_warning "Tenant ID mismatch!"
fi
fi
else
print_error "Get tenant failed (expected 200, got $HTTP_CODE)"
fi
# ==============================================================================
# TEST 11: Get Specific Tenant by ID
# ==============================================================================
print_test "11. Get Specific Tenant by ID"
if [ -z "$TENANT_ID" ]; then
print_warning "Skipping: No tenant ID available"
else
echo "Request: GET /api/v1/tenants/$TENANT_ID"
echo ""
RESPONSE=$(curl -s -w "\n%{http_code}" "$BASE_URL/tenants/$TENANT_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | head -n-1)
echo "HTTP Status: $HTTP_CODE"
echo "Response Body:"
echo "$BODY" | jq '.' 2>/dev/null || echo "$BODY"
if [ "$HTTP_CODE" = "200" ]; then
print_success "Specific tenant retrieval working"
else
print_error "Get specific tenant failed (expected 200, got $HTTP_CODE)"
echo ""
echo "This might fail if route order is wrong:"
echo " /tenants/mine must come BEFORE /tenants/:id"
echo " Otherwise ':id' matches 'mine' as a UUID"
fi
fi
# ==============================================================================
# TEST 12: Token Refresh
# ==============================================================================
print_test "12. Token Refresh (Token Rotation)"
if [ -z "$REFRESH_TOKEN" ]; then
print_error "No refresh token available"
exit 1
fi
echo "Request: POST /api/v1/auth/refresh"
echo "Using refresh token: ${REFRESH_TOKEN:0:30}..."
echo ""
echo "This tests:"
echo " - Refresh token validation"
echo " - Token rotation (new access + refresh tokens)"
echo " - Session management"
echo ""
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/auth/refresh" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $REFRESH_TOKEN")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | head -n-1)
echo "HTTP Status: $HTTP_CODE"
echo "Response Body:"
echo "$BODY" | jq '.' 2>/dev/null || echo "$BODY"
if [ "$HTTP_CODE" = "200" ]; then
print_success "Token refresh successful"
if [ "$HAS_JQ" = true ]; then
NEW_ACCESS_TOKEN=$(echo "$BODY" | jq -r '.access_token')
NEW_REFRESH_TOKEN=$(echo "$BODY" | jq -r '.refresh_token')
echo ""
echo "New tokens received:"
echo " Access Token: ${NEW_ACCESS_TOKEN:0:30}..."
echo " Refresh Token: ${NEW_REFRESH_TOKEN:0:30}..."
# Verify tokens are different (rotation)
if [ "$NEW_ACCESS_TOKEN" != "$ACCESS_TOKEN" ]; then
print_success "Access token rotated (security best practice)"
else
print_warning "Access token not rotated"
fi
if [ "$NEW_REFRESH_TOKEN" != "$REFRESH_TOKEN" ]; then
print_success "Refresh token rotated (security best practice)"
else
print_warning "Refresh token not rotated"
fi
# Update tokens for logout test
ACCESS_TOKEN="$NEW_ACCESS_TOKEN"
REFRESH_TOKEN="$NEW_REFRESH_TOKEN"
fi
else
print_error "Token refresh failed (expected 200, got $HTTP_CODE)"
fi
# ==============================================================================
# TEST 13: Use Old Refresh Token (Should Fail)
# ==============================================================================
print_test "13. Use Old Refresh Token After Rotation (Should Fail)"
echo "After token rotation, old refresh token should be invalid..."
echo ""
# We'd need to save the old token before refresh to test this properly
print_warning "Skipping: Requires saving old token before rotation"
echo "Manual test: Try using old refresh token after successful refresh"
# ==============================================================================
# TEST 14: User Logout
# ==============================================================================
print_test "14. User Logout (Session Revocation)"
echo "Request: POST /api/v1/auth/logout"
echo "This should:"
echo " - Revoke current refresh token"
echo " - Mark session as revoked"
echo " - Old refresh token should no longer work"
echo ""
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/auth/logout" \
-H "Authorization: Bearer $ACCESS_TOKEN")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | head -n-1)
echo "HTTP Status: $HTTP_CODE"
echo "Response Body:"
echo "$BODY" | jq '.' 2>/dev/null || echo "$BODY"
if [ "$HTTP_CODE" = "200" ]; then
print_success "Logout successful"
else
print_warning "Logout returned $HTTP_CODE (expected 200)"
fi
# ==============================================================================
# TEST 15: Use Tokens After Logout (Should Fail)
# ==============================================================================
print_test "15. Use Tokens After Logout (Should Fail)"
echo "Attempting to refresh token after logout..."
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/auth/refresh" \
-H "Authorization: Bearer $REFRESH_TOKEN")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | head -n-1)
echo "HTTP Status: $HTTP_CODE"
echo "Response Body:"
echo "$BODY" | jq '.' 2>/dev/null || echo "$BODY"
if [ "$HTTP_CODE" = "401" ]; then
print_success "Refresh token correctly revoked after logout"
else
print_warning "Expected 401, got $HTTP_CODE (session may not be revoked)"
fi
# ==============================================================================
# FINAL SUMMARY
# ==============================================================================
echo ""
echo "=========================================="
echo "TEST SUMMARY"
echo "=========================================="
echo ""
print_success "All critical tests completed!"
echo ""
echo "Tested functionality:"
echo " ✅ Health check (public route)"
echo " ✅ User registration with transaction"
echo " ✅ Duplicate email detection"
echo " ✅ Password validation"
echo " ✅ Rate limiting"
echo " ✅ User login"
echo " ✅ Invalid credential handling"
echo " ✅ Authentication middleware"
echo " ✅ Row-Level Security (RLS)"
echo " ✅ Tenant context management"
echo " ✅ Token refresh & rotation"
echo " ✅ Session revocation (logout)"
echo ""
echo "Database verification:"
echo " Check sessions table:"
echo " docker exec -it aurganize-postgres psql -U postgres -d aurganize_dev -c \"SELECT id, user_id, is_revoked, device_type, created_at FROM sessions;\""
echo ""
echo " Check users table:"
echo " docker exec -it aurganize-postgres psql -U postgres -d aurganize_dev -c \"SELECT id, email, role, created_at FROM users;\""
echo ""
echo " Check tenants table:"
echo " docker exec -it aurganize-postgres psql -U postgres -d aurganize_dev -c \"SELECT id, name, type, created_at FROM tenants;\""
echo ""
echo "=========================================="
echo "TESTING COMPLETE"
echo "=========================================="