Files
thrillwiki_django_no_react/tests/test_roadtrip_service.py

337 lines
11 KiB
Python

"""
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 typing import cast
from apps.parks.services import RoadTripService
from apps.parks.services.roadtrip import Coordinates
from apps.parks.models import Park
from django.core.cache import cache
import os
import sys
import django
# Ensure project root is on sys.path so imports like `parks.models` resolve.
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if PROJECT_ROOT not in sys.path:
sys.path.insert(0, PROJECT_ROOT)
# Setup Django before importing project modules that depend on settings.
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:
# fixed: single-line f-string formatting
print(f" ✅ Success: {coords.latitude:.6f}, {coords.longitude:.6f}")
else:
print(" ❌ Failed")
# Test cache functionality
print("\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("Calculating route from Cedar Point to Magic Kingdom...")
route = service.calculate_route(cedar_point, magic_kingdom)
if route:
print(" ✅ Success:")
print(f" Distance: {route.formatted_distance}")
print(f" Duration: {route.formatted_duration}")
print(f" Geometry: {'Yes' if route.geometry else 'No'}")
else:
print(" ❌ Failed")
# Test short distance (should use OSRM)
disneyland = Coordinates(33.8121, -117.9190)
knotts = Coordinates(33.8442, -118.0000)
print("\nCalculating route from Disneyland to Knott's Berry Farm...")
route = service.calculate_route(disneyland, knotts)
if route:
print(" ✅ Success:")
print(f" Distance: {route.formatted_distance}")
print(f" Duration: {route.formatted_duration}")
else:
print(" ❌ 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}")
# Use getattr to safely access the optional related attribute and avoid static type warnings.
location = getattr(park, "location", None)
if location:
coords = getattr(park, "coordinates", None)
if coords:
print(f" 📍 {coords[0]:.4f}, {coords[1]:.4f}")
else:
print(" 📍 No coordinates, will try to geocode...")
success = service.geocode_park_if_needed(park)
if success:
coords = getattr(park, "coordinates", None)
if coords:
print(f" ✅ Geocoded to: {coords[0]:.4f}, {coords[1]:.4f}")
else:
print(" ❌ Geocoding succeeded but coordinates still missing")
else:
print(" ❌ Geocoding failed")
else:
print(" ❌ 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(" ❌ 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(" ❌ 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(" ✅ Trip planned successfully:")
print(f" Total Distance: {trip.formatted_total_distance}")
print(f" Total Duration: {trip.formatted_total_duration}")
print(" 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(" ❌ 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(" ✅ Correctly handled invalid coordinates")
# Test with empty address
print("Testing empty address geocoding...")
coords = service.geocode_address("")
if coords:
print(" ⚠️ Got coordinates for empty address")
else:
print(" ✅ Correctly handled empty address")
# Test with None values
print("Testing None coordinates...")
# cast(None, Coordinates) is used only to satisfy static typing checks
# while keeping the runtime intent of passing None to the service.
route = service.calculate_route(cast(Coordinates, None), valid_coords)
if route:
print(" ⚠️ Got route with None coordinates")
else:
print(" ✅ 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(" ✅ Rate limiting appears to be working")
else:
print(" ⚠️ 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()