Refactor test utilities and enhance ASGI settings

- Cleaned up and standardized assertions in ApiTestMixin for API response validation.
- Updated ASGI settings to use os.environ for setting the DJANGO_SETTINGS_MODULE.
- Removed unused imports and improved formatting in settings.py.
- Refactored URL patterns in urls.py for better readability and organization.
- Enhanced view functions in views.py for consistency and clarity.
- Added .flake8 configuration for linting and style enforcement.
- Introduced type stubs for django-environ to improve type checking with Pylance.
This commit is contained in:
pacnpal
2025-08-20 19:51:59 -04:00
parent 69c07d1381
commit 66ed4347a9
230 changed files with 15094 additions and 11578 deletions

View File

@@ -3,355 +3,380 @@ 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 parks.models import Park
from tests.factories import (
UserFactory, StaffUserFactory, CompanyFactory, ParkFactory,
TestScenarios
UserFactory,
StaffUserFactory,
CompanyFactory,
ParkFactory,
)
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.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")
ParkFactory(operator=self.company, name="Park B", status="CLOSED_TEMP"),
ParkFactory(operator=self.company, name="Park C"),
]
self.url = reverse('parks_api:park-list')
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)
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)
self.assertEqual(len(response.data["data"]), 3)
# Check response structure
park_data = response.data['data'][0]
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'
"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):
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'})
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']
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'})
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')
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'})
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'])
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.company = CompanyFactory(roles=["OPERATOR"])
self.park = ParkFactory(operator=self.company)
self.url = reverse('parks_api:park-detail', kwargs={'slug': self.park.slug})
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)
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'
"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'})
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')
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.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'
"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')
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):
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
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):
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
})
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))
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.company = CompanyFactory(roles=["OPERATOR"])
self.park = ParkFactory(operator=self.company)
self.url = reverse('parks_api:park-detail', kwargs={'slug': self.park.slug})
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'
"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')
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):
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
"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'])
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')
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']
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'
"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)
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')
url = reverse("parks_api:park-list")
response = self.client.post(
url,
data='{"invalid": json}',
content_type='application/json'
url, data='{"invalid": json}', content_type="application/json"
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.data['status'], 'error')
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})
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'])
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
"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
)
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']
park_slug = create_response.data["data"]["slug"]
# 2. Retrieve park
detail_url = reverse('parks_api:park-detail', kwargs={'slug': park_slug})
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')
self.assertEqual(
retrieve_response.data["data"]["name"], "Integration Test Park"
)
# 3. Update park
update_data = {'description': 'Updated integration test description'}
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'
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)