Skip to main content

API Testing Guide

Complete guide for testing TVL Platform APIs using curl, automated scripts, and integration tools.

Table of Contents

  1. Quick Start
  2. Authentication Testing
  3. CRUD Operations Examples
  4. Advanced Testing Scenarios
  5. Testing Scripts
  6. Integration Testing
  7. Performance Testing
  8. Webhook Testing
  9. Common Testing Patterns
  10. CI/CD Integration

1. Quick Start

Prerequisites

Install required tools:

# curl (usually pre-installed)
curl --version

# jq for JSON parsing
# Ubuntu/Debian
sudo apt-get install jq

# macOS
brew install jq

# httpie (optional, user-friendly alternative)
pip install httpie

# newman (Postman CLI)
npm install -g newman

Setting Up Environment Variables

Create a .env.test file:

# API Configuration
export API_BASE_URL="https://api.tvl.com"
export API_VERSION="v1"
export API_TIMEOUT=30

# Authentication
export GOOGLE_CLIENT_ID="your-google-client-id"
export GOOGLE_CLIENT_SECRET="your-google-client-secret"
export TEST_USER_EMAIL="test@example.com"
export TEST_USER_PASSWORD="test-password"

# Testing Configuration
export TEST_PROPERTY_ID=""
export TEST_UNIT_ID=""
export TEST_BOOKING_ID=""
export IDEMPOTENCY_KEY=""

# Debug Mode
export DEBUG=false
export VERBOSE=false

Load environment variables:

source .env.test

Quick Test Script

#!/bin/bash
# quick-test.sh - Quick API health check

# Colors for output
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m' # No Color

echo "Testing TVL API..."

# Health check
response=$(curl -s -o /dev/null -w "%{http_code}" ${API_BASE_URL}/health)
if [ $response -eq 200 ]; then
echo -e "${GREEN}✓ Health check passed${NC}"
else
echo -e "${RED}✗ Health check failed (${response})${NC}"
exit 1
fi

# Version check
version=$(curl -s ${API_BASE_URL}/version | jq -r '.version')
echo -e "${GREEN}✓ API Version: ${version}${NC}"

echo "API is ready for testing!"

2. Authentication Testing

Getting JWT Token (Google OIDC Simulation)

#!/bin/bash
# get-token.sh - Obtain JWT token for testing

# Step 1: Initiate Google OAuth flow
AUTH_URL="${API_BASE_URL}/v1/auth/google/authorize"

echo "Initiating Google OAuth flow..."
auth_response=$(curl -s -X POST ${AUTH_URL} \
-H "Content-Type: application/json" \
-d '{
"client_id": "'${GOOGLE_CLIENT_ID}'",
"redirect_uri": "http://localhost:3000/callback",
"scope": "openid profile email",
"state": "random-state-string"
}')

echo "Auth Response: ${auth_response}"

# Step 2: Simulate OAuth callback with authorization code
# In real scenario, user would authenticate with Google
AUTH_CODE=$(echo $auth_response | jq -r '.authorization_code')

# Step 3: Exchange authorization code for tokens
TOKEN_URL="${API_BASE_URL}/v1/auth/google/token"

token_response=$(curl -s -X POST ${TOKEN_URL} \
-H "Content-Type: application/json" \
-d '{
"code": "'${AUTH_CODE}'",
"client_id": "'${GOOGLE_CLIENT_ID}'",
"client_secret": "'${GOOGLE_CLIENT_SECRET}'",
"redirect_uri": "http://localhost:3000/callback"
}')

# Extract tokens
export ACCESS_TOKEN=$(echo $token_response | jq -r '.access_token')
export REFRESH_TOKEN=$(echo $token_response | jq -r '.refresh_token')
export TOKEN_EXPIRES_IN=$(echo $token_response | jq -r '.expires_in')

echo "Access Token: ${ACCESS_TOKEN}"
echo "Refresh Token: ${REFRESH_TOKEN}"
echo "Expires In: ${TOKEN_EXPIRES_IN} seconds"

# Save tokens to file
cat > .tokens <<EOF
ACCESS_TOKEN=${ACCESS_TOKEN}
REFRESH_TOKEN=${REFRESH_TOKEN}
TOKEN_EXPIRES_IN=${TOKEN_EXPIRES_IN}
TOKEN_OBTAINED_AT=$(date +%s)
EOF

echo "Tokens saved to .tokens file"

Test Token Validity

# Test if token is valid
curl -X GET "${API_BASE_URL}/v1/auth/me" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
| jq '.'

# Expected response:
# {
# "id": "usr_123456",
# "email": "test@example.com",
# "name": "Test User",
# "role": "property_manager",
# "permissions": ["read:properties", "write:bookings"]
# }

Refresh Token Flow

#!/bin/bash
# refresh-token.sh - Refresh expired access token

source .tokens

REFRESH_URL="${API_BASE_URL}/v1/auth/refresh"

refresh_response=$(curl -s -X POST ${REFRESH_URL} \
-H "Content-Type: application/json" \
-d '{
"refresh_token": "'${REFRESH_TOKEN}'"
}')

# Update tokens
export ACCESS_TOKEN=$(echo $refresh_response | jq -r '.access_token')
NEW_REFRESH_TOKEN=$(echo $refresh_response | jq -r '.refresh_token')

# Update if new refresh token provided
if [ "$NEW_REFRESH_TOKEN" != "null" ]; then
export REFRESH_TOKEN=$NEW_REFRESH_TOKEN
fi

# Save updated tokens
cat > .tokens <<EOF
ACCESS_TOKEN=${ACCESS_TOKEN}
REFRESH_TOKEN=${REFRESH_TOKEN}
TOKEN_EXPIRES_IN=$(echo $refresh_response | jq -r '.expires_in')
TOKEN_OBTAINED_AT=$(date +%s)
EOF

echo "Token refreshed successfully"

3. CRUD Operations Examples

Properties API

CREATE Property

# Create a new property
curl -X POST "${API_BASE_URL}/v1/properties" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: prop-create-$(uuidgen)" \
-d '{
"name": "Sunset Villa",
"description": "Luxurious beachfront villa with stunning sunset views",
"property_type": "villa",
"address": {
"street": "123 Ocean Drive",
"city": "Malibu",
"state": "CA",
"postal_code": "90265",
"country": "US"
},
"coordinates": {
"latitude": 34.0259,
"longitude": -118.7798
},
"amenities": [
"wifi",
"pool",
"ocean_view",
"parking",
"air_conditioning"
],
"images": [
"https://storage.tvl.com/properties/sunset-villa-1.jpg",
"https://storage.tvl.com/properties/sunset-villa-2.jpg"
],
"metadata": {
"hostaway_property_id": "12345",
"check_in_time": "15:00",
"check_out_time": "11:00"
}
}' | jq '.'

# Save property ID for later use
export TEST_PROPERTY_ID=$(curl -s -X POST "${API_BASE_URL}/v1/properties" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: prop-create-$(uuidgen)" \
-d '{"name":"Test Property"}' | jq -r '.id')

READ Property

# Get single property by ID
curl -X GET "${API_BASE_URL}/v1/properties/${TEST_PROPERTY_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
| jq '.'

# List all properties with pagination
curl -X GET "${API_BASE_URL}/v1/properties?limit=20&cursor=eyJpZCI6MTIzfQ==" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
| jq '.'

# Filter properties by criteria
curl -X GET "${API_BASE_URL}/v1/properties" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-G \
--data-urlencode "property_type=villa" \
--data-urlencode "city=Malibu" \
--data-urlencode "amenities=pool,wifi" \
--data-urlencode "min_price=100" \
--data-urlencode "max_price=500" \
| jq '.'

UPDATE Property

# Full update (PUT)
curl -X PUT "${API_BASE_URL}/v1/properties/${TEST_PROPERTY_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Sunset Villa - Updated",
"description": "Updated description with new amenities",
"property_type": "villa",
"address": {
"street": "123 Ocean Drive",
"city": "Malibu",
"state": "CA",
"postal_code": "90265",
"country": "US"
}
}' | jq '.'

# Partial update (PATCH)
curl -X PATCH "${API_BASE_URL}/v1/properties/${TEST_PROPERTY_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Sunset Villa - Premium",
"amenities": ["wifi", "pool", "ocean_view", "parking", "air_conditioning", "hot_tub"]
}' | jq '.'

DELETE Property

# Soft delete (default)
curl -X DELETE "${API_BASE_URL}/v1/properties/${TEST_PROPERTY_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
| jq '.'

# Hard delete (permanent)
curl -X DELETE "${API_BASE_URL}/v1/properties/${TEST_PROPERTY_ID}?permanent=true" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "X-Confirm-Delete: true" \
| jq '.'

Units API

CREATE Unit

# Create a new unit
curl -X POST "${API_BASE_URL}/v1/units" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: unit-create-$(uuidgen)" \
-d '{
"property_id": "'${TEST_PROPERTY_ID}'",
"name": "Master Suite",
"unit_type": "bedroom",
"floor": 2,
"max_occupancy": 2,
"bedrooms": 1,
"bathrooms": 1,
"size_sqft": 450,
"base_price": 250.00,
"amenities": ["king_bed", "private_bathroom", "balcony"],
"images": [
"https://storage.tvl.com/units/master-suite-1.jpg"
]
}' | jq '.'

# Save unit ID
export TEST_UNIT_ID=$(curl -s -X POST "${API_BASE_URL}/v1/units" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: unit-create-$(uuidgen)" \
-d '{"property_id":"'${TEST_PROPERTY_ID}'","name":"Test Unit"}' | jq -r '.id')

READ Unit

# Get single unit
curl -X GET "${API_BASE_URL}/v1/units/${TEST_UNIT_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
| jq '.'

# List units for a property
curl -X GET "${API_BASE_URL}/v1/properties/${TEST_PROPERTY_ID}/units" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
| jq '.'

UPDATE Unit

# Partial update
curl -X PATCH "${API_BASE_URL}/v1/units/${TEST_UNIT_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"base_price": 275.00,
"amenities": ["king_bed", "private_bathroom", "balcony", "ocean_view"]
}' | jq '.'

DELETE Unit

# Delete unit
curl -X DELETE "${API_BASE_URL}/v1/units/${TEST_UNIT_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
| jq '.'

Bookings API

CREATE Booking

# Create a new booking
curl -X POST "${API_BASE_URL}/v1/bookings" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: booking-create-$(uuidgen)" \
-d '{
"property_id": "'${TEST_PROPERTY_ID}'",
"unit_id": "'${TEST_UNIT_ID}'",
"check_in_date": "2025-11-01",
"check_out_date": "2025-11-05",
"guest": {
"first_name": "John",
"last_name": "Doe",
"email": "john.doe@example.com",
"phone": "+1-555-0123",
"adults": 2,
"children": 0
},
"pricing": {
"base_price": 250.00,
"nights": 4,
"subtotal": 1000.00,
"cleaning_fee": 75.00,
"service_fee": 50.00,
"taxes": 87.50,
"total": 1212.50
},
"payment_method": "credit_card",
"special_requests": "Late check-in around 8 PM",
"source": "direct",
"metadata": {
"hostaway_booking_id": "67890"
}
}' | jq '.'

# Save booking ID
export TEST_BOOKING_ID=$(curl -s -X POST "${API_BASE_URL}/v1/bookings" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: booking-create-$(uuidgen)" \
-d '{
"property_id": "'${TEST_PROPERTY_ID}'",
"unit_id": "'${TEST_UNIT_ID}'",
"check_in_date": "2025-11-01",
"check_out_date": "2025-11-05"
}' | jq -r '.id')

READ Booking

# Get single booking
curl -X GET "${API_BASE_URL}/v1/bookings/${TEST_BOOKING_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
| jq '.'

# List bookings with filters
curl -X GET "${API_BASE_URL}/v1/bookings" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-G \
--data-urlencode "property_id=${TEST_PROPERTY_ID}" \
--data-urlencode "status=confirmed" \
--data-urlencode "check_in_from=2025-11-01" \
--data-urlencode "check_in_to=2025-11-30" \
| jq '.'

UPDATE Booking

# Update booking status
curl -X PATCH "${API_BASE_URL}/v1/bookings/${TEST_BOOKING_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"status": "confirmed",
"confirmed_at": "2025-10-24T12:00:00Z"
}' | jq '.'

# Modify booking dates
curl -X PATCH "${API_BASE_URL}/v1/bookings/${TEST_BOOKING_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"check_in_date": "2025-11-02",
"check_out_date": "2025-11-06"
}' | jq '.'

DELETE Booking (Cancel)

# Cancel booking
curl -X DELETE "${API_BASE_URL}/v1/bookings/${TEST_BOOKING_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"cancellation_reason": "Guest requested cancellation",
"refund_amount": 1000.00
}' | jq '.'

Availability API

Check Availability

# Check unit availability
curl -X GET "${API_BASE_URL}/v1/availability" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-G \
--data-urlencode "property_id=${TEST_PROPERTY_ID}" \
--data-urlencode "check_in_date=2025-11-01" \
--data-urlencode "check_out_date=2025-11-05" \
--data-urlencode "adults=2" \
| jq '.'

Update Availability

# Block dates
curl -X POST "${API_BASE_URL}/v1/availability/block" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"unit_id": "'${TEST_UNIT_ID}'",
"start_date": "2025-12-24",
"end_date": "2025-12-26",
"reason": "maintenance"
}' | jq '.'

# Unblock dates
curl -X DELETE "${API_BASE_URL}/v1/availability/block/${BLOCK_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
| jq '.'

4. Advanced Testing Scenarios

Pagination Testing

#!/bin/bash
# test-pagination.sh - Test cursor-based pagination

echo "Testing pagination..."

# Get first page
page1=$(curl -s -X GET "${API_BASE_URL}/v1/properties?limit=10" \
-H "Authorization: Bearer ${ACCESS_TOKEN}")

echo "Page 1 items: $(echo $page1 | jq '.data | length')"

# Extract next cursor
next_cursor=$(echo $page1 | jq -r '.pagination.next_cursor')

if [ "$next_cursor" != "null" ]; then
# Get second page
page2=$(curl -s -X GET "${API_BASE_URL}/v1/properties?limit=10&cursor=${next_cursor}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}")

echo "Page 2 items: $(echo $page2 | jq '.data | length')"
fi

# Test edge cases
echo "Testing limit=0 (should use default)..."
curl -s -X GET "${API_BASE_URL}/v1/properties?limit=0" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" | jq '.pagination'

echo "Testing limit=1000 (should cap at max)..."
curl -s -X GET "${API_BASE_URL}/v1/properties?limit=1000" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" | jq '.pagination'

echo "Testing invalid cursor..."
curl -s -X GET "${API_BASE_URL}/v1/properties?cursor=invalid" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" | jq '.error'
#!/bin/bash
# test-filtering.sh - Test filtering capabilities

echo "Testing filters..."

# Multiple filters
curl -s -X GET "${API_BASE_URL}/v1/properties" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-G \
--data-urlencode "city=Malibu" \
--data-urlencode "property_type=villa" \
--data-urlencode "min_price=100" \
--data-urlencode "max_price=500" \
--data-urlencode "amenities=pool,wifi" \
| jq '.data | length'

# Full-text search
curl -s -X GET "${API_BASE_URL}/v1/properties/search" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-G \
--data-urlencode "q=beachfront villa sunset view" \
| jq '.'

# Date range filters
curl -s -X GET "${API_BASE_URL}/v1/bookings" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-G \
--data-urlencode "check_in_from=2025-11-01" \
--data-urlencode "check_in_to=2025-11-30" \
--data-urlencode "status=confirmed,pending" \
| jq '.'

Rate Limiting Testing

#!/bin/bash
# test-rate-limiting.sh - Test rate limits

echo "Testing rate limits..."

# Make rapid requests
for i in {1..100}; do
response=$(curl -s -w "\n%{http_code}" -X GET "${API_BASE_URL}/v1/properties" \
-H "Authorization: Bearer ${ACCESS_TOKEN}")

http_code=$(echo "$response" | tail -n1)

if [ "$http_code" == "429" ]; then
echo "Rate limit hit at request $i"

# Extract retry-after header
retry_after=$(curl -s -I -X GET "${API_BASE_URL}/v1/properties" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" | grep -i "retry-after" | cut -d' ' -f2)

echo "Retry after: ${retry_after} seconds"
break
fi

echo "Request $i: $http_code"
sleep 0.1
done

# Test rate limit headers
curl -I -X GET "${API_BASE_URL}/v1/properties" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
| grep -E "(X-RateLimit-|Retry-After)"

Idempotency Testing

#!/bin/bash
# test-idempotency.sh - Test idempotency keys

echo "Testing idempotency..."

# Generate unique idempotency key
IDEMPOTENCY_KEY="test-$(uuidgen)"

# First request
response1=$(curl -s -X POST "${API_BASE_URL}/v1/properties" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: ${IDEMPOTENCY_KEY}" \
-d '{
"name": "Idempotency Test Property",
"property_type": "apartment"
}')

property_id1=$(echo $response1 | jq -r '.id')
echo "First request created property: ${property_id1}"

# Second request with same idempotency key (should return cached response)
response2=$(curl -s -X POST "${API_BASE_URL}/v1/properties" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: ${IDEMPOTENCY_KEY}" \
-d '{
"name": "Different Name",
"property_type": "villa"
}')

property_id2=$(echo $response2 | jq -r '.id')
echo "Second request returned property: ${property_id2}"

if [ "$property_id1" == "$property_id2" ]; then
echo "✓ Idempotency working correctly"
else
echo "✗ Idempotency failed - different IDs returned"
fi

# Clean up
curl -s -X DELETE "${API_BASE_URL}/v1/properties/${property_id1}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" > /dev/null

Concurrent Requests

#!/bin/bash
# test-concurrent.sh - Test concurrent request handling

echo "Testing concurrent requests..."

# Function to make a booking request
make_booking() {
local id=$1
curl -s -X POST "${API_BASE_URL}/v1/bookings" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: concurrent-${id}" \
-d '{
"property_id": "'${TEST_PROPERTY_ID}'",
"unit_id": "'${TEST_UNIT_ID}'",
"check_in_date": "2025-11-01",
"check_out_date": "2025-11-05"
}' -w "\nHTTP_CODE:%{http_code}" -o /tmp/booking_${id}.json
}

# Launch 10 concurrent requests for the same dates
for i in {1..10}; do
make_booking $i &
done

# Wait for all background jobs
wait

# Check results
successful=0
conflicts=0

for i in {1..10}; do
http_code=$(tail -n1 /tmp/booking_${i}.json | cut -d':' -f2)

if [ "$http_code" == "201" ]; then
((successful++))
elif [ "$http_code" == "409" ]; then
((conflicts++))
fi
done

echo "Successful bookings: ${successful}"
echo "Conflicts detected: ${conflicts}"

# Only one should succeed
if [ $successful -eq 1 ] && [ $conflicts -eq 9 ]; then
echo "✓ Concurrent booking handling working correctly"
else
echo "✗ Unexpected concurrent booking behavior"
fi

# Clean up
rm -f /tmp/booking_*.json

Error Handling

#!/bin/bash
# test-error-handling.sh - Test error responses

echo "Testing error handling..."

# 400 Bad Request - Invalid data
echo "Testing 400 Bad Request..."
curl -s -X POST "${API_BASE_URL}/v1/properties" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "",
"property_type": "invalid_type"
}' | jq '.'

# 401 Unauthorized - Missing token
echo "Testing 401 Unauthorized..."
curl -s -X GET "${API_BASE_URL}/v1/properties" \
| jq '.'

# 403 Forbidden - Insufficient permissions
echo "Testing 403 Forbidden..."
curl -s -X DELETE "${API_BASE_URL}/v1/users/admin_user_id" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
| jq '.'

# 404 Not Found - Resource doesn't exist
echo "Testing 404 Not Found..."
curl -s -X GET "${API_BASE_URL}/v1/properties/nonexistent_id" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
| jq '.'

# 409 Conflict - Duplicate booking
echo "Testing 409 Conflict..."
curl -s -X POST "${API_BASE_URL}/v1/bookings" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"property_id": "'${TEST_PROPERTY_ID}'",
"unit_id": "'${TEST_UNIT_ID}'",
"check_in_date": "2025-11-01",
"check_out_date": "2025-11-05"
}' | jq '.'

# 422 Unprocessable Entity - Validation error
echo "Testing 422 Unprocessable Entity..."
curl -s -X POST "${API_BASE_URL}/v1/bookings" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"property_id": "'${TEST_PROPERTY_ID}'",
"check_in_date": "2025-11-05",
"check_out_date": "2025-11-01"
}' | jq '.'

# 500 Internal Server Error (simulate)
echo "Testing 500 Internal Server Error..."
curl -s -X POST "${API_BASE_URL}/v1/internal/trigger-error" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
| jq '.'

5. Testing Scripts

test-auth.sh

#!/bin/bash
# test-auth.sh - Complete authentication flow testing

set -e

echo "=== Authentication Flow Test ==="

# Colors
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'

# Load configuration
source .env.test

# Test 1: Health check
echo -e "\n${YELLOW}Test 1: API Health Check${NC}"
health_response=$(curl -s -w "\n%{http_code}" ${API_BASE_URL}/health)
health_code=$(echo "$health_response" | tail -n1)

if [ "$health_code" == "200" ]; then
echo -e "${GREEN}✓ Health check passed${NC}"
else
echo -e "${RED}✗ Health check failed${NC}"
exit 1
fi

# Test 2: Get authorization URL
echo -e "\n${YELLOW}Test 2: Get Authorization URL${NC}"
auth_response=$(curl -s -X POST ${API_BASE_URL}/v1/auth/google/authorize \
-H "Content-Type: application/json" \
-d '{
"client_id": "'${GOOGLE_CLIENT_ID}'",
"redirect_uri": "http://localhost:3000/callback",
"scope": "openid profile email"
}')

auth_url=$(echo $auth_response | jq -r '.authorization_url')
echo "Authorization URL: ${auth_url}"

if [ "$auth_url" != "null" ]; then
echo -e "${GREEN}✓ Authorization URL obtained${NC}"
else
echo -e "${RED}✗ Failed to get authorization URL${NC}"
exit 1
fi

# Test 3: Simulate OAuth callback (in test environment)
echo -e "\n${YELLOW}Test 3: OAuth Callback Simulation${NC}"
# In production, this would come from Google
TEST_AUTH_CODE="test_auth_code_$(date +%s)"

token_response=$(curl -s -X POST ${API_BASE_URL}/v1/auth/google/token \
-H "Content-Type: application/json" \
-d '{
"code": "'${TEST_AUTH_CODE}'",
"client_id": "'${GOOGLE_CLIENT_ID}'",
"client_secret": "'${GOOGLE_CLIENT_SECRET}'",
"redirect_uri": "http://localhost:3000/callback"
}')

ACCESS_TOKEN=$(echo $token_response | jq -r '.access_token')
REFRESH_TOKEN=$(echo $token_response | jq -r '.refresh_token')

if [ "$ACCESS_TOKEN" != "null" ]; then
echo -e "${GREEN}✓ Access token obtained${NC}"
echo "Token preview: ${ACCESS_TOKEN:0:20}..."
else
echo -e "${RED}✗ Failed to obtain access token${NC}"
exit 1
fi

# Test 4: Validate token
echo -e "\n${YELLOW}Test 4: Token Validation${NC}"
me_response=$(curl -s -X GET ${API_BASE_URL}/v1/auth/me \
-H "Authorization: Bearer ${ACCESS_TOKEN}")

user_id=$(echo $me_response | jq -r '.id')

if [ "$user_id" != "null" ]; then
echo -e "${GREEN}✓ Token is valid${NC}"
echo "User ID: ${user_id}"
echo "Email: $(echo $me_response | jq -r '.email')"
else
echo -e "${RED}✗ Token validation failed${NC}"
exit 1
fi

# Test 5: Token refresh
echo -e "\n${YELLOW}Test 5: Token Refresh${NC}"
sleep 2

refresh_response=$(curl -s -X POST ${API_BASE_URL}/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{
"refresh_token": "'${REFRESH_TOKEN}'"
}')

NEW_ACCESS_TOKEN=$(echo $refresh_response | jq -r '.access_token')

if [ "$NEW_ACCESS_TOKEN" != "null" ]; then
echo -e "${GREEN}✓ Token refreshed successfully${NC}"
else
echo -e "${RED}✗ Token refresh failed${NC}"
exit 1
fi

# Test 6: Revoke token
echo -e "\n${YELLOW}Test 6: Token Revocation${NC}"
revoke_response=$(curl -s -X POST ${API_BASE_URL}/v1/auth/revoke \
-H "Authorization: Bearer ${NEW_ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"token": "'${NEW_ACCESS_TOKEN}'"
}')

# Try to use revoked token
revoked_test=$(curl -s -w "\n%{http_code}" -X GET ${API_BASE_URL}/v1/auth/me \
-H "Authorization: Bearer ${NEW_ACCESS_TOKEN}")
revoked_code=$(echo "$revoked_test" | tail -n1)

if [ "$revoked_code" == "401" ]; then
echo -e "${GREEN}✓ Token successfully revoked${NC}"
else
echo -e "${RED}✗ Revoked token still works${NC}"
exit 1
fi

echo -e "\n${GREEN}=== All Authentication Tests Passed ===${NC}"

test-booking-flow.sh

#!/bin/bash
# test-booking-flow.sh - Complete booking workflow test

set -e

echo "=== Booking Flow Test ==="

GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'

source .env.test
source .tokens

# Step 1: Check availability
echo -e "\n${YELLOW}Step 1: Check Availability${NC}"
availability_response=$(curl -s -X GET "${API_BASE_URL}/v1/availability" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-G \
--data-urlencode "property_id=${TEST_PROPERTY_ID}" \
--data-urlencode "check_in_date=2025-11-01" \
--data-urlencode "check_out_date=2025-11-05" \
--data-urlencode "adults=2")

available_units=$(echo $availability_response | jq -r '.available_units | length')
echo "Available units: ${available_units}"

if [ "$available_units" -gt 0 ]; then
echo -e "${GREEN}✓ Units available${NC}"
AVAILABLE_UNIT_ID=$(echo $availability_response | jq -r '.available_units[0].id')
else
echo -e "${RED}✗ No units available${NC}"
exit 1
fi

# Step 2: Get pricing quote
echo -e "\n${YELLOW}Step 2: Get Pricing Quote${NC}"
quote_response=$(curl -s -X POST "${API_BASE_URL}/v1/bookings/quote" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"property_id": "'${TEST_PROPERTY_ID}'",
"unit_id": "'${AVAILABLE_UNIT_ID}'",
"check_in_date": "2025-11-01",
"check_out_date": "2025-11-05",
"adults": 2,
"children": 0
}')

total_price=$(echo $quote_response | jq -r '.total')
echo "Total price: \$${total_price}"

if [ "$total_price" != "null" ]; then
echo -e "${GREEN}✓ Quote generated${NC}"
else
echo -e "${RED}✗ Failed to get quote${NC}"
exit 1
fi

# Step 3: Create booking (with idempotency)
echo -e "\n${YELLOW}Step 3: Create Booking${NC}"
IDEMPOTENCY_KEY="booking-test-$(uuidgen)"

booking_response=$(curl -s -X POST "${API_BASE_URL}/v1/bookings" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: ${IDEMPOTENCY_KEY}" \
-d '{
"property_id": "'${TEST_PROPERTY_ID}'",
"unit_id": "'${AVAILABLE_UNIT_ID}'",
"check_in_date": "2025-11-01",
"check_out_date": "2025-11-05",
"guest": {
"first_name": "Test",
"last_name": "User",
"email": "test@example.com",
"phone": "+1-555-0123",
"adults": 2,
"children": 0
},
"pricing": '$quote_response',
"payment_method": "credit_card"
}')

BOOKING_ID=$(echo $booking_response | jq -r '.id')
booking_status=$(echo $booking_response | jq -r '.status')

echo "Booking ID: ${BOOKING_ID}"
echo "Status: ${booking_status}"

if [ "$BOOKING_ID" != "null" ]; then
echo -e "${GREEN}✓ Booking created${NC}"
else
echo -e "${RED}✗ Failed to create booking${NC}"
exit 1
fi

# Step 4: Verify idempotency (retry same request)
echo -e "\n${YELLOW}Step 4: Test Idempotency${NC}"
retry_response=$(curl -s -X POST "${API_BASE_URL}/v1/bookings" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: ${IDEMPOTENCY_KEY}" \
-d '{
"property_id": "'${TEST_PROPERTY_ID}'",
"unit_id": "'${AVAILABLE_UNIT_ID}'",
"check_in_date": "2025-11-01",
"check_out_date": "2025-11-05"
}')

RETRY_BOOKING_ID=$(echo $retry_response | jq -r '.id')

if [ "$BOOKING_ID" == "$RETRY_BOOKING_ID" ]; then
echo -e "${GREEN}✓ Idempotency working${NC}"
else
echo -e "${RED}✗ Idempotency failed${NC}"
fi

# Step 5: Confirm booking
echo -e "\n${YELLOW}Step 5: Confirm Booking${NC}"
confirm_response=$(curl -s -X PATCH "${API_BASE_URL}/v1/bookings/${BOOKING_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"status": "confirmed",
"payment_status": "paid"
}')

confirmed_status=$(echo $confirm_response | jq -r '.status')

if [ "$confirmed_status" == "confirmed" ]; then
echo -e "${GREEN}✓ Booking confirmed${NC}"
else
echo -e "${RED}✗ Failed to confirm booking${NC}"
fi

# Step 6: Retrieve booking details
echo -e "\n${YELLOW}Step 6: Retrieve Booking${NC}"
get_response=$(curl -s -X GET "${API_BASE_URL}/v1/bookings/${BOOKING_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}")

echo $get_response | jq '.'

# Step 7: Modify booking (change dates)
echo -e "\n${YELLOW}Step 7: Modify Booking Dates${NC}"
modify_response=$(curl -s -X PATCH "${API_BASE_URL}/v1/bookings/${BOOKING_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"check_out_date": "2025-11-06"
}')

new_checkout=$(echo $modify_response | jq -r '.check_out_date')
echo "New checkout date: ${new_checkout}"

if [ "$new_checkout" == "2025-11-06" ]; then
echo -e "${GREEN}✓ Booking modified${NC}"
else
echo -e "${RED}✗ Failed to modify booking${NC}"
fi

# Step 8: Cancel booking
echo -e "\n${YELLOW}Step 8: Cancel Booking${NC}"
cancel_response=$(curl -s -X DELETE "${API_BASE_URL}/v1/bookings/${BOOKING_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"cancellation_reason": "Test completed",
"refund_amount": '$total_price'
}')

cancelled_status=$(echo $cancel_response | jq -r '.status')

if [ "$cancelled_status" == "cancelled" ]; then
echo -e "${GREEN}✓ Booking cancelled${NC}"
else
echo -e "${RED}✗ Failed to cancel booking${NC}"
fi

echo -e "\n${GREEN}=== Booking Flow Test Complete ===${NC}"

test-property-crud.sh

#!/bin/bash
# test-property-crud.sh - Property CRUD operations test

set -e

echo "=== Property CRUD Test ==="

GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'

source .env.test
source .tokens

# CREATE
echo -e "\n${YELLOW}CREATE: Creating new property${NC}"
create_response=$(curl -s -X POST "${API_BASE_URL}/v1/properties" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: prop-crud-$(uuidgen)" \
-d '{
"name": "CRUD Test Property",
"description": "Property for testing CRUD operations",
"property_type": "apartment",
"address": {
"street": "456 Test Street",
"city": "Test City",
"state": "CA",
"postal_code": "90001",
"country": "US"
},
"amenities": ["wifi", "parking"]
}')

PROPERTY_ID=$(echo $create_response | jq -r '.id')
echo "Created property ID: ${PROPERTY_ID}"

if [ "$PROPERTY_ID" != "null" ]; then
echo -e "${GREEN}✓ CREATE successful${NC}"
else
echo -e "${RED}✗ CREATE failed${NC}"
exit 1
fi

# READ
echo -e "\n${YELLOW}READ: Fetching property${NC}"
read_response=$(curl -s -X GET "${API_BASE_URL}/v1/properties/${PROPERTY_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}")

property_name=$(echo $read_response | jq -r '.name')
echo "Property name: ${property_name}"

if [ "$property_name" == "CRUD Test Property" ]; then
echo -e "${GREEN}✓ READ successful${NC}"
else
echo -e "${RED}✗ READ failed${NC}"
fi

# UPDATE (PATCH)
echo -e "\n${YELLOW}UPDATE: Patching property${NC}"
update_response=$(curl -s -X PATCH "${API_BASE_URL}/v1/properties/${PROPERTY_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "CRUD Test Property - Updated",
"amenities": ["wifi", "parking", "pool"]
}')

updated_name=$(echo $update_response | jq -r '.name')
updated_amenities=$(echo $update_response | jq -r '.amenities | length')

echo "Updated name: ${updated_name}"
echo "Amenities count: ${updated_amenities}"

if [ "$updated_name" == "CRUD Test Property - Updated" ] && [ "$updated_amenities" == "3" ]; then
echo -e "${GREEN}✓ UPDATE successful${NC}"
else
echo -e "${RED}✗ UPDATE failed${NC}"
fi

# LIST
echo -e "\n${YELLOW}LIST: Fetching properties list${NC}"
list_response=$(curl -s -X GET "${API_BASE_URL}/v1/properties?limit=5" \
-H "Authorization: Bearer ${ACCESS_TOKEN}")

properties_count=$(echo $list_response | jq -r '.data | length')
echo "Properties returned: ${properties_count}"

if [ "$properties_count" -gt 0 ]; then
echo -e "${GREEN}✓ LIST successful${NC}"
else
echo -e "${RED}✗ LIST failed${NC}"
fi

# DELETE (Soft)
echo -e "\n${YELLOW}DELETE: Soft deleting property${NC}"
delete_response=$(curl -s -X DELETE "${API_BASE_URL}/v1/properties/${PROPERTY_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}")

deleted_status=$(echo $delete_response | jq -r '.deleted')

if [ "$deleted_status" == "true" ]; then
echo -e "${GREEN}✓ DELETE successful${NC}"
else
echo -e "${RED}✗ DELETE failed${NC}"
fi

# Verify soft delete
echo -e "\n${YELLOW}Verifying soft delete${NC}"
verify_response=$(curl -s -w "\n%{http_code}" -X GET "${API_BASE_URL}/v1/properties/${PROPERTY_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}")
verify_code=$(echo "$verify_response" | tail -n1)

if [ "$verify_code" == "404" ]; then
echo -e "${GREEN}✓ Property not accessible after deletion${NC}"
else
echo -e "${RED}✗ Property still accessible${NC}"
fi

echo -e "\n${GREEN}=== CRUD Test Complete ===${NC}"

load-test-availability.sh

#!/bin/bash
# load-test-availability.sh - Load test availability endpoint

echo "=== Availability Endpoint Load Test ==="

GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'

source .env.test
source .tokens

# Configuration
CONCURRENT_USERS=10
REQUESTS_PER_USER=50
TOTAL_REQUESTS=$((CONCURRENT_USERS * REQUESTS_PER_USER))

echo "Configuration:"
echo " Concurrent users: ${CONCURRENT_USERS}"
echo " Requests per user: ${REQUESTS_PER_USER}"
echo " Total requests: ${TOTAL_REQUESTS}"

# Prepare test data
PROPERTY_IDS=("prop_123" "prop_456" "prop_789")
CHECK_IN_DATES=("2025-11-01" "2025-11-15" "2025-12-01")

# Results file
RESULTS_FILE="/tmp/load_test_results.txt"
> $RESULTS_FILE

# Function to make availability request
test_availability() {
local user_id=$1
local request_num=$2

# Rotate through test data
local prop_idx=$((RANDOM % 3))
local date_idx=$((RANDOM % 3))

local property_id=${PROPERTY_IDS[$prop_idx]}
local check_in=${CHECK_IN_DATES[$date_idx]}

local start_time=$(date +%s%N)

response=$(curl -s -w "\n%{http_code}\n%{time_total}" \
-X GET "${API_BASE_URL}/v1/availability" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-G \
--data-urlencode "property_id=${property_id}" \
--data-urlencode "check_in_date=${check_in}" \
--data-urlencode "check_out_date=2025-11-05")

local http_code=$(echo "$response" | tail -n2 | head -n1)
local time_total=$(echo "$response" | tail -n1)

echo "${user_id},${request_num},${http_code},${time_total}" >> $RESULTS_FILE
}

# Run load test
echo -e "\n${YELLOW}Starting load test...${NC}"
start_time=$(date +%s)

for user in $(seq 1 $CONCURRENT_USERS); do
(
for req in $(seq 1 $REQUESTS_PER_USER); do
test_availability $user $req
done
) &
done

# Wait for all background jobs
wait

end_time=$(date +%s)
duration=$((end_time - start_time))

# Analyze results
echo -e "\n${YELLOW}Analyzing results...${NC}"

total_requests=$(wc -l < $RESULTS_FILE)
successful=$(awk -F',' '$3 == 200' $RESULTS_FILE | wc -l)
failed=$(awk -F',' '$3 != 200' $RESULTS_FILE | wc -l)

avg_response_time=$(awk -F',' '{sum+=$4; count++} END {print sum/count}' $RESULTS_FILE)
min_response_time=$(awk -F',' '{print $4}' $RESULTS_FILE | sort -n | head -n1)
max_response_time=$(awk -F',' '{print $4}' $RESULTS_FILE | sort -n | tail -n1)

# Calculate percentiles
p50=$(awk -F',' '{print $4}' $RESULTS_FILE | sort -n | awk '{a[NR]=$1} END {print a[int(NR*0.5)]}')
p95=$(awk -F',' '{print $4}' $RESULTS_FILE | sort -n | awk '{a[NR]=$1} END {print a[int(NR*0.95)]}')
p99=$(awk -F',' '{print $4}' $RESULTS_FILE | sort -n | awk '{a[NR]=$1} END {print a[int(NR*0.99)]}')

requests_per_second=$(echo "scale=2; $total_requests / $duration" | bc)

# Print results
echo -e "\n${GREEN}=== Load Test Results ===${NC}"
echo "Duration: ${duration} seconds"
echo "Total requests: ${total_requests}"
echo "Successful: ${successful}"
echo "Failed: ${failed}"
echo "Success rate: $(echo "scale=2; $successful * 100 / $total_requests" | bc)%"
echo ""
echo "Response Times (seconds):"
echo " Average: ${avg_response_time}"
echo " Min: ${min_response_time}"
echo " Max: ${max_response_time}"
echo " P50: ${p50}"
echo " P95: ${p95}"
echo " P99: ${p99}"
echo ""
echo "Throughput: ${requests_per_second} req/s"

# Check error codes
echo -e "\n${YELLOW}Error Distribution:${NC}"
awk -F',' '$3 != 200 {print $3}' $RESULTS_FILE | sort | uniq -c

# Clean up
rm $RESULTS_FILE

echo -e "\n${GREEN}=== Load Test Complete ===${NC}"

test-idempotency.sh

#!/bin/bash
# test-idempotency.sh - Comprehensive idempotency testing

set -e

echo "=== Idempotency Test Suite ==="

GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'

source .env.test
source .tokens

# Test 1: Same idempotency key returns same result
echo -e "\n${YELLOW}Test 1: Duplicate Request Detection${NC}"
IDEM_KEY="test-duplicate-$(uuidgen)"

response1=$(curl -s -X POST "${API_BASE_URL}/v1/properties" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: ${IDEM_KEY}" \
-d '{
"name": "Idempotency Test 1",
"property_type": "apartment"
}')

property_id1=$(echo $response1 | jq -r '.id')

# Wait a moment
sleep 1

response2=$(curl -s -X POST "${API_BASE_URL}/v1/properties" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: ${IDEM_KEY}" \
-d '{
"name": "Different Name",
"property_type": "villa"
}')

property_id2=$(echo $response2 | jq -r '.id')

if [ "$property_id1" == "$property_id2" ]; then
echo -e "${GREEN}✓ Duplicate request returned cached response${NC}"
else
echo -e "${RED}✗ Different IDs returned: ${property_id1} vs ${property_id2}${NC}"
fi

# Test 2: Different keys create different resources
echo -e "\n${YELLOW}Test 2: Different Keys Different Resources${NC}"
KEY1="test-key1-$(uuidgen)"
KEY2="test-key2-$(uuidgen)"

resp1=$(curl -s -X POST "${API_BASE_URL}/v1/properties" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: ${KEY1}" \
-d '{"name": "Property A", "property_type": "apartment"}')

resp2=$(curl -s -X POST "${API_BASE_URL}/v1/properties" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: ${KEY2}" \
-d '{"name": "Property B", "property_type": "apartment"}')

id1=$(echo $resp1 | jq -r '.id')
id2=$(echo $resp2 | jq -r '.id')

if [ "$id1" != "$id2" ]; then
echo -e "${GREEN}✓ Different keys created different resources${NC}"
else
echo -e "${RED}✗ Same ID returned for different keys${NC}"
fi

# Test 3: Idempotency key expiration
echo -e "\n${YELLOW}Test 3: Idempotency Key Expiration${NC}"
EXPIRE_KEY="test-expire-$(uuidgen)"

# First request
first=$(curl -s -X POST "${API_BASE_URL}/v1/properties" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: ${EXPIRE_KEY}" \
-d '{"name": "Expire Test", "property_type": "apartment"}')

first_id=$(echo $first | jq -r '.id')

# Wait for expiration (typically 24 hours, but may be shorter in test)
echo "Waiting for key expiration (5 seconds in test environment)..."
sleep 5

# Request with expired key should create new resource
expired=$(curl -s -X POST "${API_BASE_URL}/v1/properties" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: ${EXPIRE_KEY}" \
-d '{"name": "Expire Test 2", "property_type": "apartment"}')

expired_id=$(echo $expired | jq -r '.id')

if [ "$first_id" != "$expired_id" ]; then
echo -e "${GREEN}✓ Expired key allowed new resource creation${NC}"
else
echo -e "${YELLOW}⚠ Key may not have expired (or cache still valid)${NC}"
fi

# Test 4: Idempotency with different HTTP methods
echo -e "\n${YELLOW}Test 4: Idempotency with PATCH${NC}"
PATCH_KEY="test-patch-$(uuidgen)"

# Create a property first
create=$(curl -s -X POST "${API_BASE_URL}/v1/properties" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"name": "Patch Test", "property_type": "apartment"}')

patch_prop_id=$(echo $create | jq -r '.id')

# First PATCH
patch1=$(curl -s -X PATCH "${API_BASE_URL}/v1/properties/${patch_prop_id}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: ${PATCH_KEY}" \
-d '{"name": "Patched Name"}')

# Duplicate PATCH
patch2=$(curl -s -X PATCH "${API_BASE_URL}/v1/properties/${patch_prop_id}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: ${PATCH_KEY}" \
-d '{"name": "Different Patched Name"}')

name1=$(echo $patch1 | jq -r '.name')
name2=$(echo $patch2 | jq -r '.name')

if [ "$name1" == "$name2" ]; then
echo -e "${GREEN}✓ PATCH idempotency working${NC}"
else
echo -e "${RED}✗ PATCH not idempotent${NC}"
fi

# Test 5: No idempotency key behavior
echo -e "\n${YELLOW}Test 5: Missing Idempotency Key${NC}"
no_key1=$(curl -s -X POST "${API_BASE_URL}/v1/properties" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"name": "No Key Test", "property_type": "apartment"}')

no_key2=$(curl -s -X POST "${API_BASE_URL}/v1/properties" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"name": "No Key Test", "property_type": "apartment"}')

id_no1=$(echo $no_key1 | jq -r '.id')
id_no2=$(echo $no_key2 | jq -r '.id')

if [ "$id_no1" != "$id_no2" ]; then
echo -e "${GREEN}✓ Without key, duplicate resources created${NC}"
else
echo -e "${RED}✗ Same resource returned without key${NC}"
fi

echo -e "\n${GREEN}=== Idempotency Tests Complete ===${NC}"

# Cleanup
echo -e "\n${YELLOW}Cleaning up test resources...${NC}"
curl -s -X DELETE "${API_BASE_URL}/v1/properties/${property_id1}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" > /dev/null
curl -s -X DELETE "${API_BASE_URL}/v1/properties/${id1}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" > /dev/null
curl -s -X DELETE "${API_BASE_URL}/v1/properties/${id2}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" > /dev/null

echo -e "${GREEN}Cleanup complete${NC}"

6. Integration Testing

Newman (Postman CLI)

Export your Postman collection and run tests:

# Install Newman
npm install -g newman

# Run collection
newman run tvl-api-collection.json \
--environment tvl-test-env.json \
--reporters cli,json,html \
--reporter-html-export /tmp/newman-report.html

# Run with specific folder
newman run tvl-api-collection.json \
--folder "Booking Tests" \
--environment tvl-test-env.json

# Run with data file (data-driven testing)
newman run tvl-api-collection.json \
--iteration-data test-data.csv \
--environment tvl-test-env.json

# Example test data CSV
# property_id,check_in,check_out,adults
# prop_123,2025-11-01,2025-11-05,2
# prop_456,2025-11-15,2025-11-20,4

Sample Postman test script:

// Test script in Postman
pm.test("Status code is 200", function () {
pm.response.to.have.status(200);
});

pm.test("Response time is less than 500ms", function () {
pm.expect(pm.response.responseTime).to.be.below(500);
});

pm.test("Response has required fields", function () {
const jsonData = pm.response.json();
pm.expect(jsonData).to.have.property('id');
pm.expect(jsonData).to.have.property('name');
});

pm.test("Property ID is valid UUID", function () {
const jsonData = pm.response.json();
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
pm.expect(jsonData.id).to.match(uuidRegex);
});

// Save to environment
pm.environment.set("property_id", pm.response.json().id);

HTTPie Examples

HTTPie provides a more user-friendly alternative to curl:

# Install HTTPie
pip install httpie

# Basic GET request
http GET ${API_BASE_URL}/v1/properties \
"Authorization: Bearer ${ACCESS_TOKEN}"

# POST with JSON
http POST ${API_BASE_URL}/v1/properties \
"Authorization: Bearer ${ACCESS_TOKEN}" \
name="Test Property" \
property_type="apartment"

# POST with JSON file
http POST ${API_BASE_URL}/v1/properties \
"Authorization: Bearer ${ACCESS_TOKEN}" \
< property-data.json

# Query parameters
http GET ${API_BASE_URL}/v1/properties \
"Authorization: Bearer ${ACCESS_TOKEN}" \
city=="Malibu" \
property_type=="villa"

# Download response
http --download ${API_BASE_URL}/v1/properties/${PROPERTY_ID}/export \
"Authorization: Bearer ${ACCESS_TOKEN}"

# Session support (saves headers)
http --session=tvl POST ${API_BASE_URL}/v1/auth/login \
email=test@example.com password=test123

http --session=tvl GET ${API_BASE_URL}/v1/properties

# Pretty print JSON
http ${API_BASE_URL}/v1/properties | jq '.'

Insomnia Examples

Insomnia CLI for automation:

# Install Insomnia CLI
npm install -g insomnia-inso

# Run tests
inso run test "TVL API Tests" \
--env "Test Environment"

# Export collection
inso export spec "TVL API" \
--output tvl-api-spec.yaml

# Lint API spec
inso lint spec tvl-api-spec.yaml

# Run specific test suite
inso run test "Booking Flow" \
--env "Test Environment" \
--reporter json \
--reporter-file results.json

7. Performance Testing

Apache Bench (ab)

Simple load testing tool:

# Install Apache Bench
# Ubuntu/Debian
sudo apt-get install apache2-utils

# macOS (included with Apache)
# which ab

# Basic load test
ab -n 1000 -c 10 \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
"${API_BASE_URL}/v1/properties"

# With POST data
ab -n 100 -c 5 \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-p property-data.json \
"${API_BASE_URL}/v1/properties"

# Save results
ab -n 1000 -c 10 \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-g results.tsv \
"${API_BASE_URL}/v1/properties"

# Analyze with gnuplot
gnuplot <<EOF
set terminal png
set output "performance.png"
set title "API Performance"
set xlabel "Request"
set ylabel "Response Time (ms)"
plot "results.tsv" using 5 with lines title "Response Time"
EOF

k6 Load Testing

Modern load testing tool with JavaScript DSL:

// load-test.js - k6 load testing script
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';

// Custom metrics
const errorRate = new Rate('errors');

// Test configuration
export const options = {
stages: [
{ duration: '30s', target: 10 }, // Ramp up to 10 users
{ duration: '1m', target: 50 }, // Stay at 50 users
{ duration: '30s', target: 100 }, // Spike to 100 users
{ duration: '1m', target: 50 }, // Back to 50 users
{ duration: '30s', target: 0 }, // Ramp down
],
thresholds: {
http_req_duration: ['p(95)<500'], // 95% of requests under 500ms
http_req_failed: ['rate<0.01'], // Error rate under 1%
errors: ['rate<0.1'], // Custom error rate under 10%
},
};

// Test setup
export function setup() {
// Authenticate and get token
const loginRes = http.post(`${__ENV.API_BASE_URL}/v1/auth/login`, {
email: __ENV.TEST_USER_EMAIL,
password: __ENV.TEST_USER_PASSWORD,
});

return { token: loginRes.json('access_token') };
}

// Main test function
export default function(data) {
const headers = {
'Authorization': `Bearer ${data.token}`,
'Content-Type': 'application/json',
};

// Test 1: List properties
const listRes = http.get(
`${__ENV.API_BASE_URL}/v1/properties?limit=20`,
{ headers }
);

check(listRes, {
'list status is 200': (r) => r.status === 200,
'list has data': (r) => r.json('data').length > 0,
}) || errorRate.add(1);

sleep(1);

// Test 2: Get property details
const propertyId = listRes.json('data.0.id');
const detailRes = http.get(
`${__ENV.API_BASE_URL}/v1/properties/${propertyId}`,
{ headers }
);

check(detailRes, {
'detail status is 200': (r) => r.status === 200,
'detail has id': (r) => r.json('id') !== undefined,
}) || errorRate.add(1);

sleep(1);

// Test 3: Check availability
const availRes = http.get(
`${__ENV.API_BASE_URL}/v1/availability?property_id=${propertyId}&check_in_date=2025-11-01&check_out_date=2025-11-05`,
{ headers }
);

check(availRes, {
'availability status is 200': (r) => r.status === 200,
}) || errorRate.add(1);

sleep(2);
}

// Test teardown
export function teardown(data) {
// Cleanup if needed
}

Run k6 tests:

# Install k6
# Ubuntu/Debian
sudo gpg -k
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install k6

# macOS
brew install k6

# Run test
k6 run load-test.js

# With environment variables
k6 run -e API_BASE_URL=https://api.tvl.com \
-e TEST_USER_EMAIL=test@example.com \
load-test.js

# Output to InfluxDB
k6 run --out influxdb=http://localhost:8086/k6 load-test.js

# Cloud execution
k6 cloud load-test.js

Artillery Load Testing

Artillery configuration:

# artillery-config.yml
config:
target: "https://api.tvl.com"
phases:
- duration: 60
arrivalRate: 10
name: "Warm up"
- duration: 120
arrivalRate: 50
name: "Sustained load"
- duration: 60
arrivalRate: 100
name: "Spike test"
variables:
api_base_url: "https://api.tvl.com"
processor: "./auth-processor.js"

scenarios:
- name: "Property browsing flow"
weight: 70
flow:
- get:
url: "/v1/properties"
headers:
Authorization: "Bearer {{ $processEnvironment.ACCESS_TOKEN }}"
capture:
- json: "$.data[0].id"
as: "propertyId"
- get:
url: "/v1/properties/{{ propertyId }}"
headers:
Authorization: "Bearer {{ $processEnvironment.ACCESS_TOKEN }}"
- think: 2
- get:
url: "/v1/availability?property_id={{ propertyId }}&check_in_date=2025-11-01&check_out_date=2025-11-05"
headers:
Authorization: "Bearer {{ $processEnvironment.ACCESS_TOKEN }}"

- name: "Booking flow"
weight: 30
flow:
- post:
url: "/v1/bookings"
headers:
Authorization: "Bearer {{ $processEnvironment.ACCESS_TOKEN }}"
Content-Type: "application/json"
X-Idempotency-Key: "test-{{ $randomString() }}"
json:
property_id: "{{ propertyId }}"
check_in_date: "2025-11-01"
check_out_date: "2025-11-05"
capture:
- json: "$.id"
as: "bookingId"
- get:
url: "/v1/bookings/{{ bookingId }}"
headers:
Authorization: "Bearer {{ $processEnvironment.ACCESS_TOKEN }}"

Run Artillery:

# Install Artillery
npm install -g artillery

# Run test
artillery run artillery-config.yml

# Quick test
artillery quick --count 10 --num 50 \
https://api.tvl.com/v1/properties

# With report
artillery run artillery-config.yml \
--output report.json

artillery report report.json \
--output report.html

8. Webhook Testing

ngrok Setup for Local Testing

Test webhooks locally with ngrok:

# Install ngrok
# Download from https://ngrok.com/download

# Start local webhook server
python3 -m http.server 8080 &

# Create ngrok tunnel
ngrok http 8080

# Copy the forwarding URL (e.g., https://abc123.ngrok.io)

# Configure webhook in TVL
curl -X POST "${API_BASE_URL}/v1/webhooks" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"url": "https://abc123.ngrok.io/webhook",
"events": ["booking.created", "booking.updated", "booking.cancelled"],
"secret": "webhook_secret_key"
}'

# Simple webhook receiver (save as webhook-receiver.py)
# python3 webhook-receiver.py

Webhook receiver script:

#!/usr/bin/env python3
# webhook-receiver.py - Simple webhook receiver

from http.server import HTTPServer, BaseHTTPRequestHandler
import json
import hmac
import hashlib

WEBHOOK_SECRET = "webhook_secret_key"

class WebhookHandler(BaseHTTPRequestHandler):
def do_POST(self):
content_length = int(self.headers['Content-Length'])
body = self.rfile.read(content_length)

# Verify signature
signature = self.headers.get('X-TVL-Signature')
expected = hmac.new(
WEBHOOK_SECRET.encode(),
body,
hashlib.sha256
).hexdigest()

if signature != expected:
self.send_response(401)
self.end_headers()
return

# Process webhook
data = json.loads(body)
print(f"Received webhook: {data['event']}")
print(json.dumps(data, indent=2))

# Respond
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({"status": "received"}).encode())

if __name__ == '__main__':
server = HTTPServer(('localhost', 8080), WebhookHandler)
print("Webhook receiver running on http://localhost:8080")
server.serve_forever()

Webhook.site Testing

Use webhook.site for quick testing:

# Get a unique webhook URL from https://webhook.site
WEBHOOK_URL="https://webhook.site/unique-id"

# Register webhook
curl -X POST "${API_BASE_URL}/v1/webhooks" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"url": "'${WEBHOOK_URL}'",
"events": ["booking.created"],
"secret": "test_secret"
}'

# Trigger event
curl -X POST "${API_BASE_URL}/v1/bookings" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"property_id": "'${TEST_PROPERTY_ID}'",
"check_in_date": "2025-11-01",
"check_out_date": "2025-11-05"
}'

# Check webhook.site for received payload

Hostaway Webhook Simulation

Simulate incoming webhooks from Hostaway:

#!/bin/bash
# simulate-hostaway-webhook.sh

# Simulate booking created webhook
curl -X POST "http://localhost:3000/webhooks/hostaway" \
-H "Content-Type: application/json" \
-H "X-Hostaway-Signature: $(echo -n '{"event":"booking.created"}' | openssl dgst -sha256 -hmac 'hostaway_secret' | cut -d' ' -f2)" \
-d '{
"event": "booking.created",
"timestamp": "2025-10-24T12:00:00Z",
"data": {
"id": "12345",
"listingId": "67890",
"guestName": "John Doe",
"checkIn": "2025-11-01",
"checkOut": "2025-11-05",
"status": "confirmed",
"totalPrice": 1200.00
}
}'

# Simulate booking cancelled webhook
curl -X POST "http://localhost:3000/webhooks/hostaway" \
-H "Content-Type: application/json" \
-d '{
"event": "booking.cancelled",
"timestamp": "2025-10-24T13:00:00Z",
"data": {
"id": "12345",
"reason": "guest_request",
"cancelledAt": "2025-10-24T13:00:00Z"
}
}'

9. Common Testing Patterns

Testing with Fixtures

#!/bin/bash
# Create test fixtures

# fixtures/property.json
cat > /tmp/fixture-property.json <<'EOF'
{
"name": "Test Property",
"property_type": "villa",
"address": {
"street": "123 Test St",
"city": "Test City",
"state": "CA",
"postal_code": "90001",
"country": "US"
},
"amenities": ["wifi", "pool"]
}
EOF

# Use fixture
curl -X POST "${API_BASE_URL}/v1/properties" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d @/tmp/fixture-property.json

Cleaning Up Test Data

#!/bin/bash
# cleanup-test-data.sh - Clean up all test resources

echo "Cleaning up test data..."

source .env.test
source .tokens

# Delete test properties
test_properties=$(curl -s -X GET "${API_BASE_URL}/v1/properties?name_contains=Test" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" | jq -r '.data[].id')

for prop_id in $test_properties; do
echo "Deleting property: ${prop_id}"
curl -s -X DELETE "${API_BASE_URL}/v1/properties/${prop_id}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" > /dev/null
done

# Delete test bookings
test_bookings=$(curl -s -X GET "${API_BASE_URL}/v1/bookings?guest_email=test@example.com" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" | jq -r '.data[].id')

for booking_id in $test_bookings; do
echo "Cancelling booking: ${booking_id}"
curl -s -X DELETE "${API_BASE_URL}/v1/bookings/${booking_id}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" > /dev/null
done

echo "Cleanup complete"

Assertion Patterns

#!/bin/bash
# assert.sh - Assertion helper functions

assert_equals() {
if [ "$1" == "$2" ]; then
echo "✓ Assertion passed: $1 == $2"
return 0
else
echo "✗ Assertion failed: expected $2, got $1"
return 1
fi
}

assert_not_null() {
if [ "$1" != "null" ] && [ -n "$1" ]; then
echo "✓ Assertion passed: value is not null"
return 0
else
echo "✗ Assertion failed: value is null"
return 1
fi
}

assert_contains() {
if [[ "$1" == *"$2"* ]]; then
echo "✓ Assertion passed: '$1' contains '$2'"
return 0
else
echo "✗ Assertion failed: '$1' does not contain '$2'"
return 1
fi
}

assert_status() {
if [ "$1" == "$2" ]; then
echo "✓ HTTP status assertion passed: $1"
return 0
else
echo "✗ HTTP status assertion failed: expected $2, got $1"
return 1
fi
}

# Usage example
response=$(curl -s -w "\n%{http_code}" -X GET "${API_BASE_URL}/v1/properties/${PROPERTY_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}")

http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | head -n -1)
property_name=$(echo $body | jq -r '.name')

assert_status $http_code 200
assert_not_null "$property_name"
assert_contains "$property_name" "Villa"

Debugging Failed Requests

#!/bin/bash
# debug-request.sh - Detailed request debugging

DEBUG=true
VERBOSE=true

debug_request() {
local method=$1
local url=$2
shift 2

echo "=== REQUEST DEBUG ==="
echo "Method: ${method}"
echo "URL: ${url}"
echo "Headers:"

# Make request with verbose output
response=$(curl -v -X ${method} "${url}" "$@" 2>&1)

echo "$response"

echo "=== END DEBUG ==="
}

# Usage
debug_request POST "${API_BASE_URL}/v1/properties" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"name":"Debug Test"}'

# Or save to file
curl -v -X GET "${API_BASE_URL}/v1/properties" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
2>&1 | tee debug.log

10. CI/CD Integration

GitHub Actions Example

# .github/workflows/api-tests.yml
name: API Tests

on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 */6 * * *' # Every 6 hours

jobs:
api-tests:
runs-on: ubuntu-latest

env:
API_BASE_URL: ${{ secrets.API_BASE_URL }}
ACCESS_TOKEN: ${{ secrets.API_ACCESS_TOKEN }}

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'

- name: Install dependencies
run: |
npm install -g newman
sudo apt-get install -y jq curl

- name: Run authentication tests
run: |
chmod +x ./scripts/test-auth.sh
./scripts/test-auth.sh

- name: Run CRUD tests
run: |
chmod +x ./scripts/test-property-crud.sh
./scripts/test-property-crud.sh

- name: Run booking flow tests
run: |
chmod +x ./scripts/test-booking-flow.sh
./scripts/test-booking-flow.sh

- name: Run Newman tests
run: |
newman run postman-collection.json \
--environment postman-environment.json \
--reporters cli,json \
--reporter-json-export results.json

- name: Upload test results
if: always()
uses: actions/upload-artifact@v3
with:
name: test-results
path: results.json

- name: Cleanup test data
if: always()
run: |
chmod +x ./scripts/cleanup-test-data.sh
./scripts/cleanup-test-data.sh

- name: Notify on failure
if: failure()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: 'API tests failed!'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}

GitLab CI Example

# .gitlab-ci.yml
stages:
- test
- report

variables:
API_BASE_URL: $API_BASE_URL
ACCESS_TOKEN: $API_ACCESS_TOKEN

before_script:
- apt-get update -qq
- apt-get install -y curl jq

api_tests:
stage: test
image: node:18
script:
- npm install -g newman
- chmod +x ./scripts/*.sh
- ./scripts/test-auth.sh
- ./scripts/test-property-crud.sh
- ./scripts/test-booking-flow.sh
- newman run postman-collection.json --environment postman-environment.json
after_script:
- ./scripts/cleanup-test-data.sh
artifacts:
when: always
paths:
- results.json
- logs/
expire_in: 1 week
only:
- main
- develop
- merge_requests

load_tests:
stage: test
image: loadimpact/k6:latest
script:
- k6 run load-test.js
only:
- schedules
artifacts:
paths:
- load-test-results.json

generate_report:
stage: report
image: python:3.9
script:
- pip install pytest-html
- python generate-report.py
artifacts:
paths:
- report.html
only:
- main

Running Tests in Docker

# Dockerfile.test
FROM node:18-alpine

# Install dependencies
RUN apk add --no-cache \
curl \
jq \
bash \
openssl

# Install testing tools
RUN npm install -g \
newman \
artillery

# Copy test scripts
WORKDIR /tests
COPY ./scripts ./scripts
COPY ./postman ./postman
COPY .env.test .env.test

# Make scripts executable
RUN chmod +x ./scripts/*.sh

# Default command
CMD ["./scripts/test-all.sh"]

Build and run:

# Build test image
docker build -f Dockerfile.test -t tvl-api-tests .

# Run tests
docker run --rm \
-e API_BASE_URL=${API_BASE_URL} \
-e ACCESS_TOKEN=${ACCESS_TOKEN} \
tvl-api-tests

# Run specific test
docker run --rm \
-e API_BASE_URL=${API_BASE_URL} \
-e ACCESS_TOKEN=${ACCESS_TOKEN} \
tvl-api-tests \
./scripts/test-booking-flow.sh

# Run with volume for results
docker run --rm \
-v $(pwd)/results:/tests/results \
-e API_BASE_URL=${API_BASE_URL} \
-e ACCESS_TOKEN=${ACCESS_TOKEN} \
tvl-api-tests

Docker Compose for test environment:

# docker-compose.test.yml
version: '3.8'

services:
api-tests:
build:
context: .
dockerfile: Dockerfile.test
environment:
- API_BASE_URL=${API_BASE_URL}
- ACCESS_TOKEN=${ACCESS_TOKEN}
volumes:
- ./results:/tests/results
- ./logs:/tests/logs
command: ./scripts/test-all.sh

load-tests:
image: loadimpact/k6:latest
volumes:
- ./load-test.js:/load-test.js
environment:
- API_BASE_URL=${API_BASE_URL}
- ACCESS_TOKEN=${ACCESS_TOKEN}
command: run /load-test.js

Run with Docker Compose:

# Run all tests
docker-compose -f docker-compose.test.yml up --abort-on-container-exit

# Run specific service
docker-compose -f docker-compose.test.yml up api-tests

# Clean up
docker-compose -f docker-compose.test.yml down -v

Validation Checklist

Before deploying to production, verify:

  • All authentication flows tested
  • CRUD operations work for all entities
  • Pagination tested with various limits
  • Rate limiting properly enforced
  • Idempotency keys working correctly
  • Error responses match specification
  • Webhook signatures validated
  • Performance metrics within acceptable ranges
  • Concurrent requests handled properly
  • CI/CD pipeline runs tests successfully


Last Updated: 2025-10-24 Maintained By: API Testing Team Version: 1.0.0