Add comprehensive tests for Parks API and models

- Implemented extensive test cases for the Parks API, covering endpoints for listing, retrieving, creating, updating, and deleting parks.
- Added tests for filtering, searching, and ordering parks in the API.
- Created tests for error handling in the API, including malformed JSON and unsupported methods.
- Developed model tests for Park, ParkArea, Company, and ParkReview models, ensuring validation and constraints are enforced.
- Introduced utility mixins for API and model testing to streamline assertions and enhance test readability.
- Included integration tests to validate complete workflows involving park creation, retrieval, updating, and deletion.
This commit is contained in:
pacnpal
2025-08-17 19:36:20 -04:00
parent 17228e9935
commit c26414ff74
210 changed files with 24155 additions and 833 deletions

357
tests/test_parks_api.py Normal file
View File

@@ -0,0 +1,357 @@
"""
Test cases for Parks API following Django styleguide patterns.
Comprehensive API endpoint testing with proper naming conventions.
"""
import json
from decimal import Decimal
from datetime import date
from django.test import TestCase
from django.urls import reverse
from rest_framework.test import APITestCase, APIClient
from rest_framework import status
from parks.models import Park, Company
from accounts.models import User
from tests.factories import (
UserFactory, StaffUserFactory, CompanyFactory, ParkFactory,
TestScenarios
)
class TestParkListApi(APITestCase):
"""Test cases for Park list API endpoint."""
def setUp(self):
"""Set up test data."""
self.client = APIClient()
self.user = UserFactory()
self.company = CompanyFactory(roles=['OPERATOR'])
self.parks = [
ParkFactory(operator=self.company, name="Park A"),
ParkFactory(operator=self.company, name="Park B", status='CLOSED_TEMP'),
ParkFactory(operator=self.company, name="Park C")
]
self.url = reverse('parks_api:park-list')
def test__park_list_api__unauthenticated_user__can_access(self):
"""Test that unauthenticated users can access park list."""
response = self.client.get(self.url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['status'], 'success')
self.assertIsInstance(response.data['data'], list)
def test__park_list_api__returns_all_parks__in_correct_format(self):
"""Test that park list returns all parks in correct format."""
response = self.client.get(self.url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data['data']), 3)
# Check response structure
park_data = response.data['data'][0]
expected_fields = [
'id', 'name', 'slug', 'status', 'description',
'average_rating', 'coaster_count', 'ride_count',
'location', 'operator', 'created_at', 'updated_at'
]
for field in expected_fields:
self.assertIn(field, park_data)
def test__park_list_api__with_status_filter__returns_filtered_results(self):
"""Test that status filter works correctly."""
response = self.client.get(self.url, {'status': 'OPERATING'})
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Should return only operating parks (2 out of 3)
operating_parks = [p for p in response.data['data'] if p['status'] == 'OPERATING']
self.assertEqual(len(operating_parks), 2)
def test__park_list_api__with_search_query__returns_matching_results(self):
"""Test that search functionality works correctly."""
response = self.client.get(self.url, {'search': 'Park A'})
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data['data']), 1)
self.assertEqual(response.data['data'][0]['name'], 'Park A')
def test__park_list_api__with_ordering__returns_ordered_results(self):
"""Test that ordering functionality works correctly."""
response = self.client.get(self.url, {'ordering': '-name'})
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Should be ordered by name descending (C, B, A)
names = [park['name'] for park in response.data['data']]
self.assertEqual(names, ['Park C', 'Park B', 'Park A'])
class TestParkDetailApi(APITestCase):
"""Test cases for Park detail API endpoint."""
def setUp(self):
"""Set up test data."""
self.client = APIClient()
self.company = CompanyFactory(roles=['OPERATOR'])
self.park = ParkFactory(operator=self.company)
self.url = reverse('parks_api:park-detail', kwargs={'slug': self.park.slug})
def test__park_detail_api__with_valid_slug__returns_park_details(self):
"""Test that park detail API returns correct park information."""
response = self.client.get(self.url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['status'], 'success')
park_data = response.data['data']
self.assertEqual(park_data['id'], self.park.id)
self.assertEqual(park_data['name'], self.park.name)
self.assertEqual(park_data['slug'], self.park.slug)
# Check that detailed fields are included
detailed_fields = [
'opening_date', 'closing_date', 'operating_season',
'size_acres', 'website', 'areas', 'operator', 'property_owner'
]
for field in detailed_fields:
self.assertIn(field, park_data)
def test__park_detail_api__with_invalid_slug__returns_404(self):
"""Test that invalid slug returns 404 error."""
invalid_url = reverse('parks_api:park-detail', kwargs={'slug': 'nonexistent'})
response = self.client.get(invalid_url)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
self.assertEqual(response.data['status'], 'error')
self.assertEqual(response.data['error']['code'], 'NOT_FOUND')
class TestParkCreateApi(APITestCase):
"""Test cases for Park creation API endpoint."""
def setUp(self):
"""Set up test data."""
self.client = APIClient()
self.user = UserFactory()
self.staff_user = StaffUserFactory()
self.company = CompanyFactory(roles=['OPERATOR'])
self.url = reverse('parks_api:park-list') # POST to list endpoint
self.valid_park_data = {
'name': 'New Test Park',
'description': 'A test park for API testing',
'operator_id': self.company.id,
'status': 'OPERATING'
}
def test__park_create_api__unauthenticated_user__returns_401(self):
"""Test that unauthenticated users cannot create parks."""
response = self.client.post(self.url, self.valid_park_data)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
def test__park_create_api__authenticated_user__can_create_park(self):
"""Test that authenticated users can create parks."""
self.client.force_authenticate(user=self.user)
response = self.client.post(self.url, self.valid_park_data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data['status'], 'success')
# Verify park was created
park_data = response.data['data']
self.assertEqual(park_data['name'], 'New Test Park')
self.assertTrue(Park.objects.filter(name='New Test Park').exists())
def test__park_create_api__with_invalid_data__returns_validation_errors(self):
"""Test that invalid data returns proper validation errors."""
self.client.force_authenticate(user=self.user)
invalid_data = self.valid_park_data.copy()
invalid_data['name'] = '' # Empty name should be invalid
response = self.client.post(self.url, invalid_data)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.data['status'], 'error')
self.assertIn('name', response.data['error']['details'])
def test__park_create_api__with_invalid_date_range__returns_validation_error(self):
"""Test that invalid date ranges are caught by validation."""
self.client.force_authenticate(user=self.user)
invalid_data = self.valid_park_data.copy()
invalid_data.update({
'opening_date': '2020-06-01',
'closing_date': '2020-05-01' # Before opening date
})
response = self.client.post(self.url, invalid_data)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('Closing date cannot be before opening date', str(response.data))
class TestParkUpdateApi(APITestCase):
"""Test cases for Park update API endpoint."""
def setUp(self):
"""Set up test data."""
self.client = APIClient()
self.user = UserFactory()
self.company = CompanyFactory(roles=['OPERATOR'])
self.park = ParkFactory(operator=self.company)
self.url = reverse('parks_api:park-detail', kwargs={'slug': self.park.slug})
def test__park_update_api__authenticated_user__can_update_park(self):
"""Test that authenticated users can update parks."""
self.client.force_authenticate(user=self.user)
update_data = {
'name': 'Updated Park Name',
'description': 'Updated description'
}
response = self.client.patch(self.url, update_data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['status'], 'success')
# Verify park was updated
self.park.refresh_from_db()
self.assertEqual(self.park.name, 'Updated Park Name')
self.assertEqual(self.park.description, 'Updated description')
def test__park_update_api__with_invalid_data__returns_validation_errors(self):
"""Test that invalid update data returns validation errors."""
self.client.force_authenticate(user=self.user)
invalid_data = {
'opening_date': '2020-06-01',
'closing_date': '2020-05-01' # Invalid date range
}
response = self.client.patch(self.url, invalid_data)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
class TestParkStatsApi(APITestCase):
"""Test cases for Park statistics API endpoint."""
def setUp(self):
"""Set up test data."""
self.client = APIClient()
self.company = CompanyFactory(roles=['OPERATOR'])
# Create parks with different statuses
ParkFactory(operator=self.company, status='OPERATING')
ParkFactory(operator=self.company, status='OPERATING')
ParkFactory(operator=self.company, status='CLOSED_TEMP')
self.url = reverse('parks_api:park-stats')
def test__park_stats_api__returns_correct_statistics(self):
"""Test that park statistics API returns correct data."""
response = self.client.get(self.url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['status'], 'success')
stats = response.data['data']
expected_fields = [
'total_parks', 'operating_parks', 'closed_parks',
'under_construction', 'average_rating', 'recently_added_count'
]
for field in expected_fields:
self.assertIn(field, stats)
# Verify counts are correct
self.assertEqual(stats['total_parks'], 3)
self.assertEqual(stats['operating_parks'], 2)
class TestParkApiErrorHandling(APITestCase):
"""Test cases for Park API error handling."""
def setUp(self):
"""Set up test data."""
self.client = APIClient()
def test__park_api__with_malformed_json__returns_parse_error(self):
"""Test that malformed JSON returns proper error."""
url = reverse('parks_api:park-list')
response = self.client.post(
url,
data='{"invalid": json}',
content_type='application/json'
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.data['status'], 'error')
def test__park_api__with_unsupported_method__returns_405(self):
"""Test that unsupported HTTP methods return 405."""
park = ParkFactory()
url = reverse('parks_api:park-detail', kwargs={'slug': park.slug})
response = self.client.head(url) # HEAD not supported
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
class TestParkApiIntegration(APITestCase):
"""Integration tests for Park API with complete scenarios."""
def test__complete_park_workflow__create_update_retrieve_delete(self):
"""Test complete CRUD workflow for parks."""
user = UserFactory()
company = CompanyFactory(roles=['OPERATOR'])
self.client.force_authenticate(user=user)
# 1. Create park
create_data = {
'name': 'Integration Test Park',
'description': 'A park for integration testing',
'operator_id': company.id
}
create_response = self.client.post(
reverse('parks_api:park-list'),
create_data
)
self.assertEqual(create_response.status_code, status.HTTP_201_CREATED)
park_slug = create_response.data['data']['slug']
# 2. Retrieve park
detail_url = reverse('parks_api:park-detail', kwargs={'slug': park_slug})
retrieve_response = self.client.get(detail_url)
self.assertEqual(retrieve_response.status_code, status.HTTP_200_OK)
self.assertEqual(retrieve_response.data['data']['name'], 'Integration Test Park')
# 3. Update park
update_data = {'description': 'Updated integration test description'}
update_response = self.client.patch(detail_url, update_data)
self.assertEqual(update_response.status_code, status.HTTP_200_OK)
self.assertEqual(
update_response.data['data']['description'],
'Updated integration test description'
)
# 4. Delete park
delete_response = self.client.delete(detail_url)
self.assertEqual(delete_response.status_code, status.HTTP_204_NO_CONTENT)
# 5. Verify park is deleted
verify_response = self.client.get(detail_url)
self.assertEqual(verify_response.status_code, status.HTTP_404_NOT_FOUND)