Add comprehensive test scripts for various models and services

- Implement tests for RideLocation and CompanyHeadquarters models to verify functionality and data integrity.
- Create a manual trigger test script for trending content calculation endpoint, including authentication and unauthorized access tests.
- Develop a manufacturer sync test to ensure ride manufacturers are correctly associated with ride models.
- Add tests for ParkLocation model, including coordinate setting and distance calculations between parks.
- Implement a RoadTripService test suite covering geocoding, route calculation, park discovery, and error handling.
- Create a unified map service test script to validate map functionality, API endpoints, and performance metrics.
This commit is contained in:
pacnpal
2025-09-27 22:26:40 -04:00
parent fdbbca2add
commit 1b246eeaa4
34 changed files with 2628 additions and 0 deletions

43
tests/test-args.sh Executable file
View File

@@ -0,0 +1,43 @@
#!/bin/bash
# Parse options FIRST
echo "DEBUG: Arguments received: $@"
echo "DEBUG: Number of arguments: $#"
while [[ $# -gt 0 ]]; do
echo "DEBUG: Processing argument: $1"
case "$1" in
--non-interactive)
export NON_INTERACTIVE="true"
echo "DEBUG: NON_INTERACTIVE set to $NON_INTERACTIVE"
shift
;;
--debug)
export CONFIG_DEBUG="true"
echo "DEBUG: CONFIG_DEBUG set to $CONFIG_DEBUG"
shift
;;
-*)
echo "Unknown option: $1"
exit 1
;;
*)
# This is the command - stop parsing options
echo "DEBUG: Found command argument: $1, breaking"
break
;;
esac
done
# NOW set the command after options are parsed
local_command="${1:-setup}"
echo "DEBUG: Command is: $local_command"
echo "DEBUG: NON_INTERACTIVE is: ${NON_INTERACTIVE:-unset}"
echo "DEBUG: CONFIG_DEBUG is: ${CONFIG_DEBUG:-unset}"
# Test the conditional logic
if [[ "$NON_INTERACTIVE" != "true" ]]; then
echo "WOULD SHOW: Interactive banner and prompt"
else
echo "WOULD SKIP: Interactive banner and prompt (non-interactive mode)"
fi

1
tests/test-auto-pull.md Normal file
View File

@@ -0,0 +1 @@
# Auto-pull test - Sun Aug 17 11:29:56 EDT 2025

408
tests/test_hybrid_endpoints.sh Executable file
View File

@@ -0,0 +1,408 @@
#!/bin/bash
# ThrillWiki Hybrid Filtering Endpoints Test Script
# Tests the newly synchronized Parks and Rides hybrid filtering endpoints
#
# Usage: ./test_hybrid_endpoints.sh [BASE_URL]
# Default BASE_URL: http://localhost:8000
set -e
# Configuration
BASE_URL="${1:-http://localhost:8000}"
VERBOSE=true
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Helper functions
print_header() {
local title="$1"
local level="${2:-1}"
case $level in
1) echo -e "\n${BLUE}================================================================================${NC}"
echo -e "${BLUE}$title${NC}"
echo -e "${BLUE}================================================================================${NC}" ;;
2) echo -e "\n${CYAN}--------------------------------------------------------------------------------${NC}"
echo -e "${CYAN}$title${NC}"
echo -e "${CYAN}--------------------------------------------------------------------------------${NC}" ;;
3) echo -e "\n${YELLOW}$title${NC}"
echo -e "${YELLOW}$(echo "$title" | sed 's/./~/g')${NC}" ;;
esac
}
print_endpoint() {
local method="$1"
local url="$2"
echo -e "\n${PURPLE}🔗 ENDPOINT:${NC} ${GREEN}$method${NC} $url"
}
print_description() {
local desc="$1"
echo -e "${CYAN}📋 DESCRIPTION:${NC} $desc"
}
make_request() {
local method="$1"
local url="$2"
local description="$3"
print_endpoint "$method" "$url"
print_description "$description"
echo -e "\n${YELLOW}📤 REQUEST:${NC}"
echo "curl -X $method \\"
echo " -H 'Accept: application/json' \\"
echo " -H 'Content-Type: application/json' \\"
echo " '$url'"
echo -e "\n${YELLOW}📥 RESPONSE:${NC}"
# Make the actual request
response=$(curl -s -w "\n%{http_code}" -X "$method" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
"$url")
# Extract status code and body
status_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
# Print status
if [[ "$status_code" -ge 200 && "$status_code" -lt 300 ]]; then
echo -e "${GREEN}✅ SUCCESS: HTTP $status_code${NC}"
else
echo -e "${RED}❌ ERROR: HTTP $status_code${NC}"
fi
# Pretty print JSON response
if command -v jq >/dev/null 2>&1; then
echo "$body" | jq '.'
else
echo "$body"
fi
# Extract and display key metrics
if command -v jq >/dev/null 2>&1 && [[ "$status_code" -ge 200 && "$status_code" -lt 300 ]]; then
echo -e "\n${CYAN}📊 RESPONSE SUMMARY:${NC}"
# Total count
total_count=$(echo "$body" | jq -r '.total_count // empty')
if [[ -n "$total_count" ]]; then
echo -e " • Total Count: ${GREEN}$total_count${NC}"
fi
# Strategy
strategy=$(echo "$body" | jq -r '.strategy // empty')
if [[ -n "$strategy" ]]; then
if [[ "$strategy" == "client_side" ]]; then
echo -e " • Strategy: ${GREEN}🖥️ $strategy${NC}"
else
echo -e " • Strategy: ${BLUE}🌐 $strategy${NC}"
fi
fi
# Has more
has_more=$(echo "$body" | jq -r '.has_more // empty')
if [[ -n "$has_more" ]]; then
if [[ "$has_more" == "true" ]]; then
echo -e " • Has More: ${YELLOW}➡️ $has_more${NC}"
else
echo -e " • Has More: ${GREEN}🏁 $has_more${NC}"
fi
fi
# Next offset
next_offset=$(echo "$body" | jq -r '.next_offset // empty')
if [[ -n "$next_offset" && "$next_offset" != "null" ]]; then
echo -e " • Next Offset: ${CYAN}$next_offset${NC}"
fi
# Data counts
parks_count=$(echo "$body" | jq -r '.parks | length // empty' 2>/dev/null)
if [[ -n "$parks_count" ]]; then
echo -e " • Parks Returned: ${GREEN}$parks_count${NC}"
fi
rides_count=$(echo "$body" | jq -r '.rides | length // empty' 2>/dev/null)
if [[ -n "$rides_count" ]]; then
echo -e " • Rides Returned: ${GREEN}$rides_count${NC}"
fi
# Filter metadata summary
if echo "$body" | jq -e '.filter_metadata' >/dev/null 2>&1; then
echo -e " • Filter Metadata: ${GREEN}✅ Available${NC}"
# Count categorical options
countries=$(echo "$body" | jq -r '.filter_metadata.categorical.countries | length // 0' 2>/dev/null)
states=$(echo "$body" | jq -r '.filter_metadata.categorical.states | length // 0' 2>/dev/null)
categories=$(echo "$body" | jq -r '.filter_metadata.categorical.categories | length // 0' 2>/dev/null)
if [[ "$countries" -gt 0 ]]; then
echo -e " - Countries: ${CYAN}$countries${NC}"
fi
if [[ "$states" -gt 0 ]]; then
echo -e " - States: ${CYAN}$states${NC}"
fi
if [[ "$categories" -gt 0 ]]; then
echo -e " - Categories: ${CYAN}$categories${NC}"
fi
fi
fi
echo -e "\n${PURPLE}$(printf '%.0s-' {1..80})${NC}"
}
# Main test execution
main() {
print_header "THRILLWIKI HYBRID FILTERING ENDPOINTS TEST SUITE" 1
echo -e "Testing endpoints at: ${GREEN}$BASE_URL${NC}"
echo -e "Timestamp: ${CYAN}$(date)${NC}"
# Check if server is running
echo -e "\n${YELLOW}🔍 Checking server availability...${NC}"
if ! curl -s --connect-timeout 5 "$BASE_URL" >/dev/null; then
echo -e "${RED}❌ Server not available at $BASE_URL${NC}"
echo -e "${YELLOW}💡 Make sure to start the Django server first:${NC}"
echo -e " cd backend && uv run manage.py runserver_plus"
exit 1
fi
echo -e "${GREEN}✅ Server is running${NC}"
# ========================================================================
# PARKS HYBRID FILTERING TESTS
# ========================================================================
print_header "PARKS HYBRID FILTERING TESTS" 1
# Test 1: Basic Parks Hybrid Filtering
make_request "GET" \
"$BASE_URL/api/v1/parks/hybrid/" \
"Basic hybrid filtering with no parameters - demonstrates automatic strategy selection"
# Test 2: Parks with Search Filter
make_request "GET" \
"$BASE_URL/api/v1/parks/hybrid/?search=disney" \
"Search for parks containing 'disney' - tests full-text search functionality"
# Test 3: Parks with Status Filter
make_request "GET" \
"$BASE_URL/api/v1/parks/hybrid/?status=OPERATING,CLOSED_TEMP" \
"Filter parks by multiple statuses - tests comma-separated list parameters"
# Test 4: Parks with Geographic Filters
make_request "GET" \
"$BASE_URL/api/v1/parks/hybrid/?country=United%20States&state=Florida,California" \
"Filter parks by country and multiple states - tests geographic filtering"
# Test 5: Parks with Numeric Range Filters
make_request "GET" \
"$BASE_URL/api/v1/parks/hybrid/?opening_year_min=1990&opening_year_max=2020&rating_min=4.0" \
"Filter parks by opening year range and minimum rating - tests numeric range filtering"
# Test 6: Parks with Size and Ride Count Filters
make_request "GET" \
"$BASE_URL/api/v1/parks/hybrid/?size_min=100&ride_count_min=10&coaster_count_min=5" \
"Filter parks by minimum size, ride count, and coaster count - tests park statistics filtering"
# Test 7: Parks with Operator Filter
make_request "GET" \
"$BASE_URL/api/v1/parks/hybrid/?operator=disney,universal" \
"Filter parks by operator slugs - tests company relationship filtering"
# Test 8: Parks Progressive Loading (with offset)
make_request "GET" \
"$BASE_URL/api/v1/parks/hybrid/?offset=50" \
"Progressive loading starting at offset 50 - tests server-side pagination"
# Test 9: Parks Filter Metadata
make_request "GET" \
"$BASE_URL/api/v1/parks/hybrid/filter-metadata/" \
"Get comprehensive filter metadata for parks - provides all available filter options and ranges"
# Test 10: Parks Scoped Filter Metadata
make_request "GET" \
"$BASE_URL/api/v1/parks/hybrid/filter-metadata/?scoped=true&country=United%20States" \
"Get filter metadata scoped to US parks - demonstrates dynamic metadata based on current filters"
# ========================================================================
# RIDES HYBRID FILTERING TESTS
# ========================================================================
print_header "RIDES HYBRID FILTERING TESTS" 1
# Test 1: Basic Rides Hybrid Filtering
make_request "GET" \
"$BASE_URL/api/v1/rides/hybrid/" \
"Basic hybrid filtering with no parameters - demonstrates automatic strategy selection for rides"
# Test 2: Rides with Search Filter
make_request "GET" \
"$BASE_URL/api/v1/rides/hybrid/?search=coaster" \
"Search for rides containing 'coaster' - tests full-text search across ride names and descriptions"
# Test 3: Rides with Category Filter
make_request "GET" \
"$BASE_URL/api/v1/rides/hybrid/?category=RC,DR" \
"Filter rides by categories (Roller Coaster, Dark Ride) - tests ride category filtering"
# Test 4: Rides with Status and Park Filters
make_request "GET" \
"$BASE_URL/api/v1/rides/hybrid/?status=OPERATING&park_slug=cedar-point" \
"Filter operating rides at Cedar Point - tests status and park-specific filtering"
# Test 5: Rides with Manufacturer and Designer Filters
make_request "GET" \
"$BASE_URL/api/v1/rides/hybrid/?manufacturer=bolliger-mabillard&designer=bolliger-mabillard" \
"Filter rides by B&M as manufacturer and designer - tests company relationship filtering"
# Test 6: Rides with Roller Coaster Specific Filters
make_request "GET" \
"$BASE_URL/api/v1/rides/hybrid/?roller_coaster_type=INVERTED,FLYING&track_material=STEEL&has_inversions=true" \
"Filter inverted/flying steel coasters with inversions - tests roller coaster specific attributes"
# Test 7: Rides with Height and Speed Filters
make_request "GET" \
"$BASE_URL/api/v1/rides/hybrid/?height_ft_min=200&speed_mph_min=70&inversions_min=4" \
"Filter tall, fast coasters with multiple inversions - tests numeric performance filtering"
# Test 8: Rides with Rating and Capacity Filters
make_request "GET" \
"$BASE_URL/api/v1/rides/hybrid/?rating_min=4.5&capacity_min=1000&opening_year_min=2000" \
"Filter highly-rated, high-capacity modern rides - tests quality and operational metrics"
# Test 9: Rides with Height Requirement Filters
make_request "GET" \
"$BASE_URL/api/v1/rides/hybrid/?height_requirement_min=48&height_requirement_max=54" \
"Filter rides by height requirements (48-54 inches) - tests accessibility filtering"
# Test 10: Rides Progressive Loading
make_request "GET" \
"$BASE_URL/api/v1/rides/hybrid/?offset=25&category=RC" \
"Progressive loading of roller coasters starting at offset 25 - tests server-side pagination with filters"
# Test 11: Rides Filter Metadata
make_request "GET" \
"$BASE_URL/api/v1/rides/hybrid/filter-metadata/" \
"Get comprehensive filter metadata for rides - provides all available filter options and ranges"
# Test 12: Rides Scoped Filter Metadata
make_request "GET" \
"$BASE_URL/api/v1/rides/hybrid/filter-metadata/?scoped=true&category=RC" \
"Get filter metadata scoped to roller coasters - demonstrates dynamic metadata for specific categories"
# ========================================================================
# COMPLEX COMBINATION TESTS
# ========================================================================
print_header "COMPLEX COMBINATION TESTS" 1
# Test 1: Parks with All Filter Types
make_request "GET" \
"$BASE_URL/api/v1/parks/hybrid/?search=theme&status=OPERATING&country=United%20States&opening_year_min=1980&rating_min=4.0&size_min=50&ride_count_min=20" \
"Complex parks query combining search, status, geographic, temporal, rating, and size filters"
# Test 2: Rides with All Filter Types
make_request "GET" \
"$BASE_URL/api/v1/rides/hybrid/?search=steel&category=RC&status=OPERATING&roller_coaster_type=SITDOWN&track_material=STEEL&height_ft_min=100&speed_mph_min=50&rating_min=4.0&has_inversions=false" \
"Complex rides query combining search, category, status, coaster type, materials, performance, and rating filters"
# ========================================================================
# EDGE CASE TESTS
# ========================================================================
print_header "EDGE CASE TESTS" 1
# Test 1: Empty Results
make_request "GET" \
"$BASE_URL/api/v1/parks/hybrid/?search=nonexistentpark12345" \
"Search for non-existent park - tests empty result handling"
# Test 2: Invalid Parameters
make_request "GET" \
"$BASE_URL/api/v1/rides/hybrid/?height_ft_min=invalid&rating_min=15" \
"Invalid numeric parameters - tests parameter validation and error handling"
# Test 3: Large Offset
make_request "GET" \
"$BASE_URL/api/v1/parks/hybrid/?offset=99999" \
"Very large offset value - tests pagination boundary handling"
# ========================================================================
# PERFORMANCE COMPARISON TESTS
# ========================================================================
print_header "PERFORMANCE COMPARISON TESTS" 1
echo -e "\n${CYAN}📊 Testing response times for different strategies...${NC}"
# Time the requests
echo -e "\n${YELLOW}⏱️ Timing Parks Hybrid Endpoint:${NC}"
time curl -s -o /dev/null "$BASE_URL/api/v1/parks/hybrid/"
echo -e "\n${YELLOW}⏱️ Timing Rides Hybrid Endpoint:${NC}"
time curl -s -o /dev/null "$BASE_URL/api/v1/rides/hybrid/"
echo -e "\n${YELLOW}⏱️ Timing Parks Filter Metadata:${NC}"
time curl -s -o /dev/null "$BASE_URL/api/v1/parks/hybrid/filter-metadata/"
echo -e "\n${YELLOW}⏱️ Timing Rides Filter Metadata:${NC}"
time curl -s -o /dev/null "$BASE_URL/api/v1/rides/hybrid/filter-metadata/"
# ========================================================================
# SUMMARY
# ========================================================================
print_header "TEST SUITE SUMMARY" 1
echo -e "${GREEN}✅ Test suite completed successfully!${NC}"
echo -e "\n${CYAN}📋 ENDPOINTS TESTED:${NC}"
echo -e " • Parks Hybrid Filtering: ${GREEN}/api/v1/parks/hybrid/${NC}"
echo -e " • Parks Filter Metadata: ${GREEN}/api/v1/parks/hybrid/filter-metadata/${NC}"
echo -e " • Rides Hybrid Filtering: ${GREEN}/api/v1/rides/hybrid/${NC}"
echo -e " • Rides Filter Metadata: ${GREEN}/api/v1/rides/hybrid/filter-metadata/${NC}"
echo -e "\n${CYAN}🔍 KEY FEATURES DEMONSTRATED:${NC}"
echo -e " • Automatic strategy selection (client-side vs server-side)"
echo -e " • Progressive loading for large datasets"
echo -e " • Comprehensive filter options (17+ parameters per domain)"
echo -e " • Dynamic filter metadata generation"
echo -e " • Consistent response formats across domains"
echo -e " • Full-text search capabilities"
echo -e " • Numeric range filtering"
echo -e " • Multi-value parameter support"
echo -e " • Geographic and temporal filtering"
echo -e " • Roller coaster specific filtering"
echo -e " • Error handling and validation"
echo -e "\n${YELLOW}💡 NEXT STEPS:${NC}"
echo -e " • Integrate these endpoints into your frontend application"
echo -e " • Use filter metadata to build dynamic filter interfaces"
echo -e " • Implement progressive loading for better user experience"
echo -e " • Monitor performance and adjust thresholds as needed"
echo -e "\n${PURPLE}🎉 Happy filtering! 🎢${NC}"
}
# Check dependencies
check_dependencies() {
if ! command -v curl >/dev/null 2>&1; then
echo -e "${RED}❌ curl is required but not installed${NC}"
exit 1
fi
if ! command -v jq >/dev/null 2>&1; then
echo -e "${YELLOW}⚠️ jq not found - JSON responses will not be pretty-printed${NC}"
fi
}
# Script execution
check_dependencies
main "$@"

View File

@@ -0,0 +1,136 @@
#!/usr/bin/env python3
"""
Basic test script to verify RideLocation and CompanyHeadquarters models work correctly.
"""
from rides.models import Ride, RideLocation
from parks.models import Company, CompanyHeadquarters
import os
import sys
import django
# Setup Django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "thrillwiki.settings")
django.setup()
def test_company_headquarters():
"""Test CompanyHeadquarters model functionality"""
print("Testing CompanyHeadquarters...")
# Try to use an existing company or skip this test if none exist
existing_company = Company.objects.first()
if not existing_company:
print("⚠️ No existing companies found, skipping CompanyHeadquarters test")
return None, None
# Check if headquarters already exist
try:
headquarters = existing_company.headquarters
print(f"✓ Found existing headquarters: {headquarters}")
except CompanyHeadquarters.DoesNotExist:
# Create headquarters for existing company
headquarters = CompanyHeadquarters.objects.create(
company=existing_company,
city="Orlando",
state_province="Florida",
country="USA",
street_address="123 Theme Park Blvd",
postal_code="32801",
)
print(f"✓ Created new headquarters: {headquarters}")
print(f"✓ Created headquarters: {headquarters}")
print(f"✓ Location display: {headquarters.location_display}")
print(f"✓ Formatted location: {headquarters.formatted_location}")
return existing_company, headquarters
def test_ride_location():
"""Test RideLocation model functionality"""
print("\nTesting RideLocation...")
# First, we need a ride - let's check if any exist
if Ride.objects.exists():
ride = Ride.objects.first()
print(f"✓ Using existing ride: {ride.name}")
else:
print("! No rides found in database - skipping RideLocation test")
return None, None
# Create ride location
ride_location = RideLocation.objects.create(
ride=ride,
park_area="Fantasyland",
notes="General location information",
entrance_notes="Queue entrance is to the left of the main attraction sign",
accessibility_notes="Wheelchair accessible entrance available via side path",
)
print(f"✓ Created ride location: {ride_location}")
print(f"✓ Has coordinates: {ride_location.has_coordinates}")
# Test setting coordinates
ride_location.set_coordinates(28.3772, -81.5707) # Disney World coordinates
ride_location.save()
print(f"✓ Set coordinates: {ride_location.coordinates}")
print(f"✓ Latitude: {ride_location.latitude}")
print(f"✓ Longitude: {ride_location.longitude}")
return ride, ride_location
def cleanup_test_data(company=None, headquarters=None, ride_location=None):
"""Clean up test data"""
print("\nCleaning up test data...")
if ride_location:
ride_location.delete()
print("✓ Deleted test ride location")
if headquarters:
headquarters.delete()
print("✓ Deleted test headquarters")
if company:
company.delete()
print("✓ Deleted test company")
def main():
"""Run all tests"""
print("=" * 50)
print("Testing Location Models Implementation")
print("=" * 50)
try:
# Test CompanyHeadquarters
company, headquarters = test_company_headquarters()
# Test RideLocation
ride, ride_location = test_ride_location()
print("\n" + "=" * 50)
print("✅ ALL TESTS PASSED!")
print(
"✅ Both RideLocation and CompanyHeadquarters models are working correctly"
)
print("=" * 50)
# Clean up
cleanup_test_data(company, headquarters, ride_location)
except Exception as e:
print(f"\n❌ TEST FAILED: {e}")
import traceback
traceback.print_exc()
return False
return True
if __name__ == "__main__":
success = main()
sys.exit(0 if success else 1)

View File

@@ -0,0 +1,182 @@
#!/usr/bin/env python3
"""
Test script for the manual trending content calculation trigger endpoint.
"""
import requests
import json
import time
from datetime import datetime
# Configuration
BASE_URL = "http://localhost:8000"
ADMIN_USERNAME = "admin"
ADMIN_PASSWORD = "admin" # We'll need to check what the password is
def login_and_get_token():
"""Login and get authentication token."""
login_url = f"{BASE_URL}/api/v1/auth/login/"
login_data = {
"username": ADMIN_USERNAME,
"password": ADMIN_PASSWORD
}
print(f"🔐 Attempting to login as {ADMIN_USERNAME}...")
response = requests.post(login_url, json=login_data)
if response.status_code == 200:
data = response.json()
token = data.get('token')
print(f"✅ Login successful! Token: {token[:20]}...")
return token
else:
print(f"❌ Login failed: {response.status_code}")
print(f"Response: {response.text}")
return None
def test_trigger_endpoint(token):
"""Test the manual trigger endpoint."""
trigger_url = f"{BASE_URL}/api/v1/trending/calculate/"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
print(f"\n🚀 Testing manual trigger endpoint...")
print(f"URL: {trigger_url}")
response = requests.post(trigger_url, headers=headers)
print(f"Status Code: {response.status_code}")
print(f"Response Headers: {dict(response.headers)}")
try:
response_data = response.json()
print(f"Response Body: {json.dumps(response_data, indent=2)}")
if response.status_code == 202:
print("✅ Manual trigger successful!")
return response_data
else:
print(f"❌ Manual trigger failed with status {response.status_code}")
return None
except json.JSONDecodeError:
print(f"❌ Invalid JSON response: {response.text}")
return None
def test_trending_endpoints():
"""Test the trending content endpoints to see the results."""
print(f"\n📊 Testing trending content endpoints...")
# Test trending content endpoint
trending_url = f"{BASE_URL}/api/v1/trending/content/"
print(f"Testing: {trending_url}")
response = requests.get(trending_url)
print(f"Trending Content Status: {response.status_code}")
if response.status_code == 200:
data = response.json()
print(f"Trending Parks: {len(data.get('trending_parks', []))}")
print(f"Trending Rides: {len(data.get('trending_rides', []))}")
# Show first few trending items
if data.get('trending_parks'):
print(
f"First trending park: {data['trending_parks'][0].get('name', 'Unknown')}")
if data.get('trending_rides'):
print(
f"First trending ride: {data['trending_rides'][0].get('name', 'Unknown')}")
# Test new content endpoint
new_content_url = f"{BASE_URL}/api/v1/trending/new/"
print(f"\nTesting: {new_content_url}")
response = requests.get(new_content_url)
print(f"New Content Status: {response.status_code}")
if response.status_code == 200:
data = response.json()
print(f"Recently Added: {len(data.get('recently_added', []))}")
print(f"Newly Opened: {len(data.get('newly_opened', []))}")
print(f"Upcoming: {len(data.get('upcoming', []))}")
# Show the newly_opened structure to verify our changes
if data.get('newly_opened'):
print(f"\n🎢 First newly opened item structure:")
first_item = data['newly_opened'][0]
print(f" Name: {first_item.get('name')}")
# Should be park name, not location
print(f" Park: {first_item.get('park')}")
# Should be date_opened, not location
print(f" Date Opened: {first_item.get('date_opened')}")
print(f" Category: {first_item.get('category')}")
print(f" Slug: {first_item.get('slug')}")
# Verify location field is NOT present
if 'location' in first_item:
print(
f" ❌ ERROR: 'location' field still present: {first_item['location']}")
else:
print(f" ✅ SUCCESS: 'location' field removed as requested")
def test_unauthorized_access():
"""Test that non-admin users cannot access the trigger endpoint."""
print(f"\n🔒 Testing unauthorized access...")
trigger_url = f"{BASE_URL}/api/v1/trending/calculate/"
# Test without authentication
print("Testing without authentication...")
response = requests.post(trigger_url)
print(f"No auth status: {response.status_code}")
# Test with invalid token
print("Testing with invalid token...")
headers = {"Authorization": "Bearer invalid_token_123"}
response = requests.post(trigger_url, headers=headers)
print(f"Invalid token status: {response.status_code}")
if response.status_code in [401, 403]:
print("✅ Unauthorized access properly blocked")
else:
print(f"❌ Unauthorized access not properly blocked: {response.status_code}")
def main():
"""Main test function."""
print("🧪 ThrillWiki Manual Trigger Endpoint Test")
print("=" * 50)
# First test unauthorized access
test_unauthorized_access()
# Try to login and get token
token = login_and_get_token()
if not token:
print("❌ Cannot proceed without authentication token")
return
# Test the manual trigger endpoint
trigger_result = test_trigger_endpoint(token)
if trigger_result:
print(f"\n⏳ Waiting 10 seconds for tasks to process...")
time.sleep(10)
# Test the trending endpoints to see results
test_trending_endpoints()
print(f"\n🏁 Test completed at {datetime.now()}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,65 @@
#!/usr/bin/env python
"""
Test script to verify manufacturer syncing with ride models.
"""
import os
import sys
import django
# Add the backend directory to the Python path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'backend'))
# Set up Django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.local')
django.setup()
from apps.rides.models import Ride, RideModel, Company
from apps.parks.models import Park
def test_manufacturer_sync():
"""Test that ride manufacturer syncs with ride model manufacturer."""
print("Testing manufacturer sync with ride models...")
# Get a ride that has a ride_model
ride = Ride.objects.select_related('ride_model', 'ride_model__manufacturer', 'manufacturer').first()
if not ride:
print("No rides found in database")
return
print(f"\nRide: {ride.name}")
print(f"Park: {ride.park.name}")
if ride.ride_model:
print(f"Ride Model: {ride.ride_model.name}")
print(f"Ride Model Manufacturer: {ride.ride_model.manufacturer.name if ride.ride_model.manufacturer else 'None'}")
else:
print("Ride Model: None")
print(f"Ride Manufacturer: {ride.manufacturer.name if ride.manufacturer else 'None'}")
# Check if they match
if ride.ride_model and ride.ride_model.manufacturer:
if ride.manufacturer == ride.ride_model.manufacturer:
print("✅ Manufacturer is correctly synced with ride model")
else:
print("❌ Manufacturer is NOT synced with ride model")
print("This should be fixed by the new save() method")
# Test the fix by saving the ride
print("\nTesting fix by re-saving the ride...")
ride.save()
ride.refresh_from_db()
print(f"After save - Ride Manufacturer: {ride.manufacturer.name if ride.manufacturer else 'None'}")
if ride.manufacturer == ride.ride_model.manufacturer:
print("✅ Fix successful! Manufacturer is now synced")
else:
print("❌ Fix failed - manufacturer still not synced")
else:
print("⚠️ Cannot test sync - ride model has no manufacturer")
if __name__ == "__main__":
test_manufacturer_sync()

127
tests/test_park_location.py Normal file
View File

@@ -0,0 +1,127 @@
#!/usr/bin/env python
"""
Test script for ParkLocation model functionality
"""
from parks.models import Park, ParkLocation, Company
import os
import django
# Setup Django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "thrillwiki.settings")
django.setup()
def test_park_location():
print("🧪 Testing ParkLocation Model Functionality")
print("=" * 50)
# Create a test company (operator)
operator, created = Company.objects.get_or_create(
name="Test Theme Parks Inc",
defaults={"slug": "test-theme-parks-inc", "roles": ["OPERATOR"]},
)
print(f"✅ Created operator: {operator.name}")
# Create a test park
park, created = Park.objects.get_or_create(
name="Test Magic Kingdom",
defaults={
"slug": "test-magic-kingdom",
"description": "A test theme park for location testing",
"operator": operator,
},
)
print(f"✅ Created park: {park.name}")
# Create a park location
location, created = ParkLocation.objects.get_or_create(
park=park,
defaults={
"street_address": "1313 Disneyland Dr",
"city": "Anaheim",
"state": "California",
"country": "USA",
"postal_code": "92802",
"highway_exit": "I-5 Exit 110B",
"parking_notes": "Large parking structure available",
"seasonal_notes": "Open year-round",
},
)
print(f"✅ Created location: {location}")
# Test coordinate setting
print("\n🔍 Testing coordinate functionality:")
location.set_coordinates(33.8121, -117.9190) # Disneyland coordinates
location.save()
print(f" Latitude: {location.latitude}")
print(f" Longitude: {location.longitude}")
print(f" Coordinates: {location.coordinates}")
print(f" Formatted Address: {location.formatted_address}")
# Test Park model integration
print("\n🔍 Testing Park model integration:")
print(f" Park formatted location: {park.formatted_location}")
print(f" Park coordinates: {park.coordinates}")
# Create another location for distance testing
operator2, created = Company.objects.get_or_create(
name="Six Flags Entertainment",
defaults={"slug": "six-flags-entertainment", "roles": ["OPERATOR"]},
)
park2, created = Park.objects.get_or_create(
name="Six Flags Magic Mountain",
defaults={
"slug": "six-flags-magic-mountain",
"description": "Another test theme park",
"operator": operator2,
},
)
location2, created = ParkLocation.objects.get_or_create(
park=park2,
defaults={"city": "Valencia", "state": "California", "country": "USA"},
)
# Six Flags Magic Mountain coordinates
location2.set_coordinates(34.4244, -118.5971)
location2.save()
# Test distance calculation
print("\n🔍 Testing distance calculation:")
distance = location.distance_to(location2)
if distance:
print(f" Distance between parks: {distance:.2f} km")
else:
print(" ❌ Distance calculation failed")
# Test spatial indexing
print("\n🔍 Testing spatial queries:")
try:
from django.contrib.gis.measure import D
from django.contrib.gis.geos import Point
# Find parks within 100km of a point
# Same as Disneyland
search_point = Point(-117.9190, 33.8121, srid=4326)
nearby_locations = ParkLocation.objects.filter(
point__distance_lte=(search_point, D(km=100))
)
print(f" Found {nearby_locations.count()} parks within 100km")
for loc in nearby_locations:
print(f" - {loc.park.name} in {loc.city}, {loc.state}")
except Exception as e:
print(f" ⚠️ Spatial queries not fully functional: {e}")
print("\n✅ ParkLocation model tests completed successfully!")
return True
if __name__ == "__main__":
try:
test_park_location()
except Exception as e:
print(f"❌ Test failed: {e}")
import traceback
traceback.print_exc()

View File

@@ -0,0 +1,346 @@
"""
Test script for the RoadTripService implementation.
This script tests all functionality of the OSM Road Trip Service including:
- Geocoding addresses
- Route calculation
- Park discovery along routes
- Multi-park trip planning
- Integration with existing Park models
"""
from django.core.cache import cache
from parks.models import Park
from parks.services.roadtrip import Coordinates
from parks.services import RoadTripService
import os
import django
# Setup Django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "thrillwiki.settings")
django.setup()
def test_geocoding():
"""Test geocoding functionality."""
print("\n=== Testing Geocoding ===")
service = RoadTripService()
# Test various address formats
test_addresses = [
"Cedar Point, Sandusky, Ohio",
"Magic Kingdom, Orlando, Florida",
"Disneyland, Anaheim, California",
"Six Flags Great Adventure, Jackson, New Jersey",
"Invalid Address That Should Not Work 123456789",
]
for address in test_addresses:
print(f"\nGeocoding: {address}")
coords = service.geocode_address(address)
if coords:
print(
f" ✅ Success: {
coords.latitude:.6f}, {
coords.longitude:.6f}"
)
else:
print(f" ❌ Failed")
# Test cache functionality
print(f"\nTesting cache...")
coords1 = service.geocode_address("Cedar Point, Sandusky, Ohio")
coords2 = service.geocode_address("Cedar Point, Sandusky, Ohio")
if coords1 and coords2:
print(f" ✅ Cache working: {coords1.latitude == coords2.latitude}")
def test_route_calculation():
"""Test route calculation between coordinates."""
print("\n=== Testing Route Calculation ===")
service = RoadTripService()
# Cedar Point to Magic Kingdom (long distance)
cedar_point = Coordinates(41.4793, -82.6833)
magic_kingdom = Coordinates(28.4177, -81.5812)
print(f"Calculating route from Cedar Point to Magic Kingdom...")
route = service.calculate_route(cedar_point, magic_kingdom)
if route:
print(f" ✅ Success:")
print(f" Distance: {route.formatted_distance}")
print(f" Duration: {route.formatted_duration}")
print(f" Geometry: {'Yes' if route.geometry else 'No'}")
else:
print(f" ❌ Failed")
# Test short distance (should use OSRM)
disneyland = Coordinates(33.8121, -117.9190)
knotts = Coordinates(33.8442, -118.0000)
print(f"\nCalculating route from Disneyland to Knott's Berry Farm...")
route = service.calculate_route(disneyland, knotts)
if route:
print(f" ✅ Success:")
print(f" Distance: {route.formatted_distance}")
print(f" Duration: {route.formatted_duration}")
else:
print(f" ❌ Failed")
def test_park_integration():
"""Test integration with Park models."""
print("\n=== Testing Park Integration ===")
service = RoadTripService()
# Get some parks from the database
parks = Park.objects.select_related("location").all()[:5]
if not parks:
print(" ⚠️ No parks found in database")
return
print(f"Found {len(parks)} parks to test with:")
for park in parks:
print(f" - {park.name}")
if hasattr(park, "location") and park.location:
coords = park.coordinates
if coords:
print(f" 📍 {coords[0]:.4f}, {coords[1]:.4f}")
else:
print(f" 📍 No coordinates, will try to geocode...")
success = service.geocode_park_if_needed(park)
if success:
coords = park.coordinates
print(
f" ✅ Geocoded to: {
coords[0]:.4f}, {
coords[1]:.4f}"
)
else:
print(f" ❌ Geocoding failed")
else:
print(f" ❌ No location data")
def test_nearby_parks():
"""Test finding nearby parks."""
print("\n=== Testing Nearby Park Discovery ===")
service = RoadTripService()
# Get a park with location data
parks_with_location = Park.objects.filter(
location__point__isnull=False
).select_related("location")[:1]
if not parks_with_location:
print(" ⚠️ No parks with location data found")
return
center_park = parks_with_location[0]
print(f"Finding parks within 200km of {center_park.name}...")
nearby_parks = service.get_park_distances(center_park, radius_km=200)
if nearby_parks:
print(f" ✅ Found {len(nearby_parks)} nearby parks:")
for result in nearby_parks[:5]: # Show first 5
park = result["park"]
print(
f" {
park.name}: {
result['formatted_distance']}, {
result['formatted_duration']}"
)
else:
print(f" ❌ No nearby parks found")
def test_route_park_discovery():
"""Test finding parks along a route."""
print("\n=== Testing Parks Along Route ===")
service = RoadTripService()
# Get two parks with location data
parks_with_location = Park.objects.filter(
location__point__isnull=False
).select_related("location")[:2]
if len(parks_with_location) < 2:
print(" ⚠️ Need at least 2 parks with location data")
return
start_park = parks_with_location[0]
end_park = parks_with_location[1]
print(
f"Finding parks along route from {
start_park.name} to {
end_park.name}..."
)
parks_along_route = service.find_parks_along_route(
start_park, end_park, max_detour_km=100
)
if parks_along_route:
print(f" ✅ Found {len(parks_along_route)} parks along route:")
for park in parks_along_route[:3]: # Show first 3
print(f" - {park.name}")
else:
print(f" ❌ No parks found along route")
def test_multi_park_trip():
"""Test multi-park trip planning."""
print("\n=== Testing Multi-Park Trip Planning ===")
service = RoadTripService()
# Get parks with location data
parks_with_location = Park.objects.filter(
location__point__isnull=False
).select_related("location")[:4]
if len(parks_with_location) < 3:
print(" ⚠️ Need at least 3 parks with location data")
return
parks_list = list(parks_with_location)
print(f"Planning trip for {len(parks_list)} parks:")
for park in parks_list:
print(f" - {park.name}")
trip = service.create_multi_park_trip(parks_list)
if trip:
print(f" ✅ Trip planned successfully:")
print(f" Total Distance: {trip.formatted_total_distance}")
print(f" Total Duration: {trip.formatted_total_duration}")
print(f" Route:")
for i, leg in enumerate(trip.legs, 1):
print(f" {i}. {leg.from_park.name}{leg.to_park.name}")
print(
f" {
leg.route.formatted_distance}, {
leg.route.formatted_duration}"
)
else:
print(f" ❌ Trip planning failed")
def test_error_handling():
"""Test error handling and edge cases."""
print("\n=== Testing Error Handling ===")
service = RoadTripService()
# Test with invalid coordinates
print("Testing invalid coordinates...")
invalid_coords = Coordinates(999, 999)
valid_coords = Coordinates(40.0, -80.0)
route = service.calculate_route(invalid_coords, valid_coords)
if route:
print(
f" ⚠️ Got route with invalid coords: {
route.formatted_distance}"
)
else:
print(f" ✅ Correctly handled invalid coordinates")
# Test with empty address
print("Testing empty address geocoding...")
coords = service.geocode_address("")
if coords:
print(f" ⚠️ Got coordinates for empty address")
else:
print(f" ✅ Correctly handled empty address")
# Test with None values
print("Testing None coordinates...")
route = service.calculate_route(None, valid_coords)
if route:
print(f" ⚠️ Got route with None coordinates")
else:
print(f" ✅ Correctly handled None coordinates")
def test_rate_limiting():
"""Test rate limiting functionality."""
print("\n=== Testing Rate Limiting ===")
service = RoadTripService()
print("Making multiple rapid requests to test rate limiting...")
import time
start_time = time.time()
# Make 3 rapid geocoding requests
addresses = [
"Disney World, Orlando, FL",
"Universal Studios, Orlando, FL",
"SeaWorld, Orlando, FL",
]
for address in addresses:
coords = service.geocode_address(address)
if coords:
print(
f"{address}: {
coords.latitude:.4f}, {
coords.longitude:.4f}"
)
elapsed = time.time() - start_time
print(f" Total time for 3 requests: {elapsed:.2f} seconds")
if elapsed >= 2.0: # Should take at least 2 seconds with 1 req/sec limit
print(f" ✅ Rate limiting appears to be working")
else:
print(f" ⚠️ Requests may have been cached or rate limiting not working")
def main():
"""Run all tests."""
print("🚗 ThrillWiki Road Trip Service Test Suite")
print("=" * 50)
# Clear cache to ensure fresh tests
cache.clear()
try:
test_geocoding()
test_route_calculation()
test_park_integration()
test_nearby_parks()
test_route_park_discovery()
test_multi_park_trip()
test_error_handling()
test_rate_limiting()
print("\n" + "=" * 50)
print("🎉 Test suite completed!")
print("\nNote: Some tests may show failures if:")
print("- No park data exists in the database")
print("- Network connectivity issues")
print("- OSM API rate limits exceeded")
print("- Parks don't have location data")
except Exception as e:
print(f"\n❌ Test suite failed with error: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,268 @@
#!/usr/bin/env python
"""
Test script for the unified map service.
This script tests the map service with real location data.
"""
from core.services.data_structures import GeoBounds, MapFilters, LocationType
from core.services.map_service import unified_map_service
import os
import sys
import django
# Setup Django environment
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "thrillwiki.settings")
django.setup()
def test_basic_map_service():
"""Test basic map service functionality."""
print("=" * 60)
print("TESTING UNIFIED MAP SERVICE")
print("=" * 60)
try:
# Test 1: Get all locations (no filters)
print("\n1. Testing get_map_data() with no filters:")
response = unified_map_service.get_map_data(zoom_level=10, cluster=False)
print(f" Total locations found: {len(response.locations)}")
print(f" Clusters: {len(response.clusters)}")
print(f" Query time: {response.query_time_ms}ms")
print(f" Cache hit: {response.cache_hit}")
if response.locations:
print(
f" Sample location: {
response.locations[0].name} ({
response.locations[0].type.value})"
)
# Test 2: Get locations with bounds (Ohio area - Cedar Point region)
print("\n2. Testing get_map_data() with bounds (Ohio):")
ohio_bounds = GeoBounds(north=42.0, south=40.0, east=-80.5, west=-84.5)
response = unified_map_service.get_map_data(
bounds=ohio_bounds, zoom_level=8, cluster=False
)
print(f" Locations in Ohio bounds: {len(response.locations)}")
print(f" Query time: {response.query_time_ms}ms")
# Test 3: Test clustering
print("\n3. Testing clustering functionality:")
response = unified_map_service.get_map_data(
bounds=ohio_bounds,
zoom_level=6, # Lower zoom should trigger clustering
cluster=True,
)
print(f" Locations (unclustered): {len(response.locations)}")
print(f" Clusters: {len(response.clusters)}")
print(f" Clustered: {response.clustered}")
if response.clusters:
cluster = response.clusters[0]
print(
f" Sample cluster: {
cluster.count} points at {
cluster.coordinates}"
)
# Test 4: Test filtering by type
print("\n4. Testing location type filtering:")
filters = MapFilters(location_types={LocationType.PARK})
response = unified_map_service.get_map_data(
filters=filters, zoom_level=10, cluster=False
)
print(f" Parks only: {len(response.locations)}")
# Test 5: Test search functionality
print("\n5. Testing search functionality:")
results = unified_map_service.search_locations(query="Cedar Point", limit=5)
print(f" Search results for 'Cedar Point': {len(results)}")
if results:
print(
f" First result: {
results[0].name} ({
results[0].type.value})"
)
# Test 6: Test location detail retrieval
print("\n6. Testing location detail retrieval:")
if response.locations:
location = response.locations[0]
location_type, location_id = location.id.split("_", 1)
detail = unified_map_service.get_location_details(
location_type, int(location_id)
)
if detail:
print(f" Retrieved details for: {detail.name}")
print(f" Coordinates: {detail.coordinates}")
print(f" Metadata keys: {list(detail.metadata.keys())}")
else:
print(" No details found")
# Test 7: Test service statistics
print("\n7. Testing service statistics:")
stats = unified_map_service.get_service_stats()
print(
f" Cache hit rate: {
stats['cache_performance']['hit_rate_percent']}%"
)
print(
f" Supported location types: {
stats['supported_location_types']}"
)
print(f" Max unclustered points: {stats['max_unclustered_points']}")
print("\n" + "=" * 60)
print("✅ ALL TESTS PASSED!")
print("The unified map service is working correctly.")
print("=" * 60)
except Exception as e:
print(f"\n❌ ERROR: {str(e)}")
import traceback
traceback.print_exc()
return False
return True
def test_api_endpoints():
"""Test the API endpoints."""
print("\n" + "=" * 60)
print("TESTING API ENDPOINTS")
print("=" * 60)
try:
from django.test import Client
client = Client()
# Test 1: Main locations endpoint
print("\n1. Testing /api/map/locations/")
response = client.get("/api/map/locations/")
print(f" Status: {response.status_code}")
if response.status_code == 200:
data = response.json()
print(f" Response status: {data.get('status')}")
print(f" Locations: {len(data.get('data', {}).get('locations', []))}")
# Test 2: Bounds endpoint
print("\n2. Testing /api/map/bounds/")
response = client.get("/api/map/bounds/?north=42&south=40&east=-80&west=-84")
print(f" Status: {response.status_code}")
if response.status_code == 200:
data = response.json()
print(
f" Locations in bounds: {len(data.get('data', {}).get('locations', []))}"
)
# Test 3: Search endpoint
print("\n3. Testing /api/map/search/")
response = client.get("/api/map/search/?q=park")
print(f" Status: {response.status_code}")
if response.status_code == 200:
data = response.json()
print(
f" Search results: {len(data.get('data', {}).get('locations', []))}"
)
# Test 4: Stats endpoint
print("\n4. Testing /api/map/stats/")
response = client.get("/api/map/stats/")
print(f" Status: {response.status_code}")
if response.status_code == 200:
data = response.json()
print(
f" Service version: {
data.get(
'data',
{}).get('service_version')}"
)
print("\n✅ API ENDPOINTS WORKING!")
except Exception as e:
print(f"\n❌ API ERROR: {str(e)}")
import traceback
traceback.print_exc()
return False
return True
def test_performance():
"""Test performance characteristics."""
print("\n" + "=" * 60)
print("TESTING PERFORMANCE")
print("=" * 60)
import time
try:
# Test response times
times = []
for i in range(5):
start = time.time()
unified_map_service.get_map_data(zoom_level=10, cluster=True)
end = time.time()
times.append((end - start) * 1000) # Convert to ms
avg_time = sum(times) / len(times)
print(f"\n Average response time: {avg_time:.2f}ms")
print(f" Min time: {min(times):.2f}ms")
print(f" Max time: {max(times):.2f}ms")
# Test cache performance
print(f"\n Testing cache performance:")
start = time.time()
response1 = unified_map_service.get_map_data(zoom_level=10, use_cache=True)
time1 = time.time() - start
start = time.time()
response2 = unified_map_service.get_map_data(zoom_level=10, use_cache=True)
time2 = time.time() - start
print(
f" First call: {
time1 *
1000:.2f}ms (cache miss: {
not response1.cache_hit})"
)
print(
f" Second call: {
time2 *
1000:.2f}ms (cache hit: {
response2.cache_hit})"
)
if response2.cache_hit and time2 < time1:
print(" ✅ Cache is working and providing performance benefit!")
print("\n✅ PERFORMANCE TESTS COMPLETED!")
except Exception as e:
print(f"\n❌ PERFORMANCE ERROR: {str(e)}")
import traceback
traceback.print_exc()
return False
return True
if __name__ == "__main__":
print("Starting unified map service tests...")
success = True
success &= test_basic_map_service()
success &= test_api_endpoints()
success &= test_performance()
if success:
print("\n🎉 ALL TESTS COMPLETED SUCCESSFULLY!")
print("The unified map service implementation is working correctly.")
else:
print("\n💥 SOME TESTS FAILED!")
sys.exit(1)