""" Comprehensive tests for Rides API endpoints. This module provides extensive test coverage for: - RideListCreateAPIView: List and create ride operations - RideDetailAPIView: Retrieve, update, delete operations - FilterOptionsAPIView: Filter option retrieval - HybridRideAPIView: Intelligent hybrid filtering strategy - RideFilterMetadataAPIView: Filter metadata retrieval - RideSearchSuggestionsAPIView: Search suggestions - CompanySearchAPIView: Company autocomplete search - RideModelSearchAPIView: Ride model autocomplete search - RideImageSettingsAPIView: Ride image configuration Test patterns follow Django styleguide conventions. """ from rest_framework import status from rest_framework.test import APIClient from tests.factories import ( CoasterFactory, DesignerCompanyFactory, ManufacturerCompanyFactory, ParkFactory, RideFactory, RideModelFactory, StaffUserFactory, UserFactory, ) from tests.test_utils import EnhancedAPITestCase class TestRideListAPIView(EnhancedAPITestCase): """Test cases for RideListCreateAPIView GET endpoint.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.park = ParkFactory() self.manufacturer = ManufacturerCompanyFactory() self.designer = DesignerCompanyFactory() self.rides = [ RideFactory( park=self.park, manufacturer=self.manufacturer, designer=self.designer, name="Alpha Coaster", status="OPERATING", category="RC", ), RideFactory( park=self.park, manufacturer=self.manufacturer, name="Beta Ride", status="OPERATING", category="DR" ), RideFactory(park=self.park, name="Gamma Coaster", status="CLOSED_TEMP", category="RC"), ] self.url = "/api/v1/rides/" def test__ride_list__unauthenticated__can_access(self): """Test that unauthenticated users can access ride list.""" response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__ride_list__returns_paginated_results(self): """Test that ride list returns paginated results.""" response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_200_OK) # Should have pagination info self.assertIn("results", response.data) self.assertIn("count", response.data) def test__ride_list__with_search__returns_matching_rides(self): """Test search functionality.""" response = self.client.get(self.url, {"search": "Alpha"}) self.assertEqual(response.status_code, status.HTTP_200_OK) # Should find Alpha Coaster results = response.data.get("results", []) if results: names = [r.get("name", "") for r in results] self.assertTrue(any("Alpha" in name for name in names)) def test__ride_list__with_park_slug__returns_filtered_rides(self): """Test filtering by park slug.""" response = self.client.get(self.url, {"park_slug": self.park.slug}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__ride_list__with_park_id__returns_filtered_rides(self): """Test filtering by park ID.""" response = self.client.get(self.url, {"park_id": self.park.id}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__ride_list__with_category_filter__returns_filtered_rides(self): """Test filtering by category.""" response = self.client.get(self.url, {"category": "RC"}) self.assertEqual(response.status_code, status.HTTP_200_OK) # All returned rides should be roller coasters for ride in response.data.get("results", []): self.assertEqual(ride.get("category"), "RC") def test__ride_list__with_status_filter__returns_filtered_rides(self): """Test filtering by status.""" response = self.client.get(self.url, {"status": "OPERATING"}) self.assertEqual(response.status_code, status.HTTP_200_OK) for ride in response.data.get("results", []): self.assertEqual(ride.get("status"), "OPERATING") def test__ride_list__with_manufacturer_filter__returns_filtered_rides(self): """Test filtering by manufacturer ID.""" response = self.client.get(self.url, {"manufacturer_id": self.manufacturer.id}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__ride_list__with_manufacturer_slug__returns_filtered_rides(self): """Test filtering by manufacturer slug.""" response = self.client.get(self.url, {"manufacturer_slug": self.manufacturer.slug}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__ride_list__with_designer_filter__returns_filtered_rides(self): """Test filtering by designer ID.""" response = self.client.get(self.url, {"designer_id": self.designer.id}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__ride_list__with_rating_filters__returns_filtered_rides(self): """Test filtering by rating range.""" response = self.client.get(self.url, {"min_rating": 5, "max_rating": 10}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__ride_list__with_height_requirement_filters__returns_filtered_rides(self): """Test filtering by height requirement.""" response = self.client.get(self.url, {"min_height_requirement": 36, "max_height_requirement": 54}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__ride_list__with_capacity_filters__returns_filtered_rides(self): """Test filtering by capacity.""" response = self.client.get(self.url, {"min_capacity": 500, "max_capacity": 3000}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__ride_list__with_opening_year_filters__returns_filtered_rides(self): """Test filtering by opening year.""" response = self.client.get(self.url, {"min_opening_year": 2000, "max_opening_year": 2024}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__ride_list__with_ordering__returns_ordered_results(self): """Test ordering functionality.""" response = self.client.get(self.url, {"ordering": "-name"}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__ride_list__with_multiple_filters__returns_combined_results(self): """Test combining multiple filters.""" response = self.client.get(self.url, {"category": "RC", "status": "OPERATING", "ordering": "name"}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__ride_list__pagination__page_size_respected(self): """Test that page_size parameter is respected.""" response = self.client.get(self.url, {"page_size": 1}) self.assertEqual(response.status_code, status.HTTP_200_OK) results = response.data.get("results", []) self.assertLessEqual(len(results), 1) class TestRideCreateAPIView(EnhancedAPITestCase): """Test cases for RideListCreateAPIView POST endpoint.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.user = UserFactory() self.staff_user = StaffUserFactory() self.park = ParkFactory() self.manufacturer = ManufacturerCompanyFactory() self.url = "/api/v1/rides/" self.valid_ride_data = { "name": "New Test Ride", "description": "A test ride for API testing", "park_id": self.park.id, "category": "RC", "status": "OPERATING", } def test__ride_create__unauthenticated__returns_401(self): """Test that unauthenticated users cannot create rides.""" response = self.client.post(self.url, self.valid_ride_data) # Based on the view, AllowAny is used, so it might allow creation # If not, it should be 401 self.assertIn( response.status_code, [status.HTTP_201_CREATED, status.HTTP_401_UNAUTHORIZED, status.HTTP_400_BAD_REQUEST] ) def test__ride_create__with_valid_data__creates_ride(self): """Test creating ride with valid data.""" self.client.force_authenticate(user=self.user) response = self.client.post(self.url, self.valid_ride_data) # Should create or return validation error if models not available self.assertIn( response.status_code, [status.HTTP_201_CREATED, status.HTTP_400_BAD_REQUEST, status.HTTP_501_NOT_IMPLEMENTED], ) def test__ride_create__with_invalid_park__returns_error(self): """Test creating ride with invalid park ID.""" self.client.force_authenticate(user=self.user) invalid_data = self.valid_ride_data.copy() invalid_data["park_id"] = 99999 response = self.client.post(self.url, invalid_data) self.assertIn( response.status_code, [status.HTTP_400_BAD_REQUEST, status.HTTP_404_NOT_FOUND, status.HTTP_501_NOT_IMPLEMENTED], ) def test__ride_create__with_manufacturer__creates_ride_with_relationship(self): """Test creating ride with manufacturer relationship.""" self.client.force_authenticate(user=self.user) data_with_manufacturer = self.valid_ride_data.copy() data_with_manufacturer["manufacturer_id"] = self.manufacturer.id response = self.client.post(self.url, data_with_manufacturer) self.assertIn( response.status_code, [status.HTTP_201_CREATED, status.HTTP_400_BAD_REQUEST, status.HTTP_501_NOT_IMPLEMENTED], ) class TestRideDetailAPIView(EnhancedAPITestCase): """Test cases for RideDetailAPIView.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.user = UserFactory() self.park = ParkFactory() self.ride = RideFactory(park=self.park) self.url = f"/api/v1/rides/{self.ride.id}/" def test__ride_detail__unauthenticated__can_access(self): """Test that unauthenticated users can access ride detail.""" response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__ride_detail__returns_full_ride_data(self): """Test that ride detail returns all expected fields.""" response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_200_OK) expected_fields = ["id", "name", "description", "category", "status", "park"] for field in expected_fields: self.assertIn(field, response.data) def test__ride_detail__invalid_id__returns_404(self): """Test that invalid ride ID returns 404.""" response = self.client.get("/api/v1/rides/99999/") self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) class TestRideUpdateAPIView(EnhancedAPITestCase): """Test cases for RideDetailAPIView PATCH/PUT.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.user = UserFactory() self.park = ParkFactory() self.ride = RideFactory(park=self.park) self.url = f"/api/v1/rides/{self.ride.id}/" def test__ride_update__partial_update__updates_field(self): """Test partial update (PATCH).""" self.client.force_authenticate(user=self.user) update_data = {"description": "Updated description"} response = self.client.patch(self.url, update_data) self.assertIn( response.status_code, [status.HTTP_200_OK, status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN] ) def test__ride_update__move_to_new_park__updates_relationship(self): """Test moving ride to a different park.""" self.client.force_authenticate(user=self.user) new_park = ParkFactory() update_data = {"park_id": new_park.id} response = self.client.patch(self.url, update_data) self.assertIn( response.status_code, [status.HTTP_200_OK, status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN] ) class TestRideDeleteAPIView(EnhancedAPITestCase): """Test cases for RideDetailAPIView DELETE.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.user = UserFactory() self.park = ParkFactory() self.ride = RideFactory(park=self.park) self.url = f"/api/v1/rides/{self.ride.id}/" def test__ride_delete__authenticated__deletes_ride(self): """Test deleting a ride.""" self.client.force_authenticate(user=self.user) response = self.client.delete(self.url) self.assertIn( response.status_code, [status.HTTP_204_NO_CONTENT, status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN] ) class TestFilterOptionsAPIView(EnhancedAPITestCase): """Test cases for FilterOptionsAPIView.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.url = "/api/v1/rides/filter-options/" def test__filter_options__returns_all_options(self): """Test that filter options endpoint returns all filter options.""" response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_200_OK) # Check for expected filter categories expected_keys = ["categories", "statuses"] for key in expected_keys: self.assertIn(key, response.data) def test__filter_options__includes_ranges(self): """Test that filter options include numeric ranges.""" response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIn("ranges", response.data) def test__filter_options__includes_ordering_options(self): """Test that filter options include ordering options.""" response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIn("ordering_options", response.data) class TestHybridRideAPIView(EnhancedAPITestCase): """Test cases for HybridRideAPIView.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.park = ParkFactory() self.manufacturer = ManufacturerCompanyFactory() self.rides = [ RideFactory(park=self.park, manufacturer=self.manufacturer, status="OPERATING", category="RC"), RideFactory(park=self.park, status="OPERATING", category="DR"), RideFactory(park=self.park, status="CLOSED_TEMP", category="RC"), ] self.url = "/api/v1/rides/hybrid/" def test__hybrid_ride__initial_load__returns_rides(self): """Test initial load returns rides with metadata.""" response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertTrue(response.data.get("success", False)) self.assertIn("data", response.data) self.assertIn("rides", response.data["data"]) self.assertIn("total_count", response.data["data"]) def test__hybrid_ride__with_category_filter__returns_filtered_rides(self): """Test filtering by category.""" response = self.client.get(self.url, {"category": "RC"}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__hybrid_ride__with_status_filter__returns_filtered_rides(self): """Test filtering by status.""" response = self.client.get(self.url, {"status": "OPERATING"}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__hybrid_ride__with_park_slug__returns_filtered_rides(self): """Test filtering by park slug.""" response = self.client.get(self.url, {"park_slug": self.park.slug}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__hybrid_ride__with_manufacturer_filter__returns_filtered_rides(self): """Test filtering by manufacturer.""" response = self.client.get(self.url, {"manufacturer": self.manufacturer.slug}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__hybrid_ride__with_offset__returns_progressive_data(self): """Test progressive loading with offset.""" response = self.client.get(self.url, {"offset": 0}) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIn("has_more", response.data["data"]) def test__hybrid_ride__with_invalid_offset__returns_400(self): """Test invalid offset parameter.""" response = self.client.get(self.url, {"offset": "invalid"}) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test__hybrid_ride__with_search__returns_matching_rides(self): """Test search functionality.""" response = self.client.get(self.url, {"search": "test"}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__hybrid_ride__with_rating_filters__returns_filtered_rides(self): """Test filtering by rating range.""" response = self.client.get(self.url, {"rating_min": 5.0, "rating_max": 10.0}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__hybrid_ride__with_height_filters__returns_filtered_rides(self): """Test filtering by height requirement range.""" response = self.client.get(self.url, {"height_requirement_min": 36, "height_requirement_max": 54}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__hybrid_ride__with_roller_coaster_filters__returns_filtered_rides(self): """Test filtering by roller coaster specific fields.""" response = self.client.get(self.url, {"roller_coaster_type": "SITDOWN", "track_material": "STEEL"}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__hybrid_ride__with_inversions_filter__returns_filtered_rides(self): """Test filtering by inversions.""" response = self.client.get(self.url, {"has_inversions": "true"}) self.assertEqual(response.status_code, status.HTTP_200_OK) class TestRideFilterMetadataAPIView(EnhancedAPITestCase): """Test cases for RideFilterMetadataAPIView.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.url = "/api/v1/rides/filter-metadata/" def test__filter_metadata__unscoped__returns_all_metadata(self): """Test getting unscoped filter metadata.""" response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertTrue(response.data.get("success", False)) self.assertIn("data", response.data) def test__filter_metadata__scoped__returns_filtered_metadata(self): """Test getting scoped filter metadata.""" response = self.client.get(self.url, {"scoped": "true", "category": "RC"}) self.assertEqual(response.status_code, status.HTTP_200_OK) class TestCompanySearchAPIView(EnhancedAPITestCase): """Test cases for CompanySearchAPIView.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.manufacturer = ManufacturerCompanyFactory(name="Bolliger & Mabillard") self.url = "/api/v1/rides/search/companies/" def test__company_search__with_query__returns_matching_companies(self): """Test searching for companies.""" response = self.client.get(self.url, {"q": "Bolliger"}) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIsInstance(response.data, list) def test__company_search__empty_query__returns_empty_list(self): """Test empty query returns empty list.""" response = self.client.get(self.url, {"q": ""}) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, []) def test__company_search__no_query__returns_empty_list(self): """Test no query parameter returns empty list.""" response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, []) class TestRideModelSearchAPIView(EnhancedAPITestCase): """Test cases for RideModelSearchAPIView.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.ride_model = RideModelFactory(name="Hyper Coaster") self.url = "/api/v1/rides/search-ride-models/" def test__ride_model_search__with_query__returns_matching_models(self): """Test searching for ride models.""" response = self.client.get(self.url, {"q": "Hyper"}) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIsInstance(response.data, list) def test__ride_model_search__empty_query__returns_empty_list(self): """Test empty query returns empty list.""" response = self.client.get(self.url, {"q": ""}) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, []) class TestRideSearchSuggestionsAPIView(EnhancedAPITestCase): """Test cases for RideSearchSuggestionsAPIView.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.park = ParkFactory() self.ride = RideFactory(park=self.park, name="Superman: Escape from Krypton") self.url = "/api/v1/rides/search-suggestions/" def test__search_suggestions__with_query__returns_suggestions(self): """Test getting search suggestions.""" response = self.client.get(self.url, {"q": "Superman"}) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIsInstance(response.data, list) def test__search_suggestions__empty_query__returns_empty_list(self): """Test empty query returns empty list.""" response = self.client.get(self.url, {"q": ""}) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data, []) class TestRideImageSettingsAPIView(EnhancedAPITestCase): """Test cases for RideImageSettingsAPIView.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.user = UserFactory() self.park = ParkFactory() self.ride = RideFactory(park=self.park) self.url = f"/api/v1/rides/{self.ride.id}/image-settings/" def test__image_settings__patch__updates_settings(self): """Test updating ride image settings.""" self.client.force_authenticate(user=self.user) response = self.client.patch(self.url, {}) # Should handle the request self.assertIn( response.status_code, [status.HTTP_200_OK, status.HTTP_400_BAD_REQUEST, status.HTTP_401_UNAUTHORIZED] ) def test__image_settings__invalid_ride__returns_404(self): """Test updating image settings for non-existent ride.""" self.client.force_authenticate(user=self.user) response = self.client.patch("/api/v1/rides/99999/image-settings/", {}) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) class TestRideAPIRollerCoasterFilters(EnhancedAPITestCase): """Test cases for roller coaster specific filters.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.park = ParkFactory() # Create coasters with different stats self.coaster1 = CoasterFactory(park=self.park, name="Steel Vengeance") self.coaster2 = CoasterFactory(park=self.park, name="Millennium Force") self.url = "/api/v1/rides/" def test__ride_list__with_roller_coaster_type__filters_correctly(self): """Test filtering by roller coaster type.""" response = self.client.get(self.url, {"roller_coaster_type": "SITDOWN"}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__ride_list__with_track_material__filters_correctly(self): """Test filtering by track material.""" response = self.client.get(self.url, {"track_material": "STEEL"}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__ride_list__with_propulsion_system__filters_correctly(self): """Test filtering by propulsion system.""" response = self.client.get(self.url, {"propulsion_system": "CHAIN"}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__ride_list__with_height_ft_range__filters_correctly(self): """Test filtering by height in feet.""" response = self.client.get(self.url, {"min_height_ft": 100, "max_height_ft": 500}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__ride_list__with_speed_mph_range__filters_correctly(self): """Test filtering by speed in mph.""" response = self.client.get(self.url, {"min_speed_mph": 50, "max_speed_mph": 150}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__ride_list__with_inversions_range__filters_correctly(self): """Test filtering by number of inversions.""" response = self.client.get(self.url, {"min_inversions": 0, "max_inversions": 14}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__ride_list__ordering_by_height__orders_correctly(self): """Test ordering by height.""" response = self.client.get(self.url, {"ordering": "-height_ft"}) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__ride_list__ordering_by_speed__orders_correctly(self): """Test ordering by speed.""" response = self.client.get(self.url, {"ordering": "-speed_mph"}) self.assertEqual(response.status_code, status.HTTP_200_OK) class TestRideAPIEdgeCases(EnhancedAPITestCase): """Test cases for edge cases in ride APIs.""" def setUp(self): """Set up test data.""" self.client = APIClient() def test__ride_list__empty_database__returns_empty_list(self): """Test API behavior with no rides in database.""" # This depends on existing data, just verify no error response = self.client.get("/api/v1/rides/") self.assertEqual(response.status_code, status.HTTP_200_OK) def test__ride_list__special_characters_in_search__handled_safely(self): """Test that special characters in search are handled safely.""" special_searches = [ "O'Brien", "Ride & Coaster", "Test; DROP TABLE rides;", "Ride", ] for search_term in special_searches: response = self.client.get("/api/v1/rides/", {"search": search_term}) # Should not crash self.assertIn(response.status_code, [status.HTTP_200_OK, status.HTTP_400_BAD_REQUEST]) def test__ride_list__extreme_pagination__handled_safely(self): """Test extreme pagination values.""" response = self.client.get("/api/v1/rides/", {"page": 99999, "page_size": 1000}) # Should handle gracefully self.assertIn(response.status_code, [status.HTTP_200_OK, status.HTTP_404_NOT_FOUND]) def test__ride_list__invalid_ordering__handled_safely(self): """Test invalid ordering parameter.""" response = self.client.get("/api/v1/rides/", {"ordering": "invalid_field"}) # Should use default ordering self.assertEqual(response.status_code, status.HTTP_200_OK) class TestRideAPIQueryOptimization(EnhancedAPITestCase): """Test cases for query optimization in ride APIs.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.park = ParkFactory() def test__ride_list__uses_select_related(self): """Test that ride list uses select_related for optimization.""" # Create multiple rides for _i in range(5): RideFactory(park=self.park) response = self.client.get("/api/v1/rides/") self.assertEqual(response.status_code, status.HTTP_200_OK) def test__ride_list__handles_large_dataset(self): """Test that ride list handles larger datasets efficiently.""" # Create batch of rides for i in range(10): RideFactory(park=self.park, name=f"Ride {i}") response = self.client.get("/api/v1/rides/") self.assertEqual(response.status_code, status.HTTP_200_OK)