mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 09:51:09 -05:00
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:
43
tests/test-args.sh
Executable file
43
tests/test-args.sh
Executable 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
1
tests/test-auto-pull.md
Normal file
@@ -0,0 +1 @@
|
||||
# Auto-pull test - Sun Aug 17 11:29:56 EDT 2025
|
||||
408
tests/test_hybrid_endpoints.sh
Executable file
408
tests/test_hybrid_endpoints.sh
Executable 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 "$@"
|
||||
136
tests/test_location_models.py
Normal file
136
tests/test_location_models.py
Normal 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)
|
||||
182
tests/test_manual_trigger.py
Normal file
182
tests/test_manual_trigger.py
Normal 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()
|
||||
65
tests/test_manufacturer_sync.py
Normal file
65
tests/test_manufacturer_sync.py
Normal 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
127
tests/test_park_location.py
Normal 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()
|
||||
346
tests/test_roadtrip_service.py
Normal file
346
tests/test_roadtrip_service.py
Normal 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()
|
||||
268
tests/test_unified_map_service.py
Normal file
268
tests/test_unified_map_service.py
Normal 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)
|
||||
Reference in New Issue
Block a user