mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-23 09:11:09 -05:00
feat: Refactor rides app with unique constraints, mixins, and enhanced documentation
- Added migration to convert unique_together constraints to UniqueConstraint for RideModel. - Introduced RideFormMixin for handling entity suggestions in ride forms. - Created comprehensive code standards documentation outlining formatting, docstring requirements, complexity guidelines, and testing requirements. - Established error handling guidelines with a structured exception hierarchy and best practices for API and view error handling. - Documented view pattern guidelines, emphasizing the use of CBVs, FBVs, and ViewSets with examples. - Implemented a benchmarking script for query performance analysis and optimization. - Developed security documentation detailing measures, configurations, and a security checklist. - Compiled a database optimization guide covering indexing strategies, query optimization patterns, and computed fields.
This commit is contained in:
243
backend/apps/parks/tests/test_query_optimization.py
Normal file
243
backend/apps/parks/tests/test_query_optimization.py
Normal file
@@ -0,0 +1,243 @@
|
||||
"""
|
||||
Tests for query optimization patterns in the parks app.
|
||||
|
||||
These tests verify that:
|
||||
1. Manager methods use proper select_related/prefetch_related
|
||||
2. Views don't trigger N+1 queries
|
||||
3. Computed fields are updated correctly
|
||||
"""
|
||||
|
||||
from django.test import TestCase
|
||||
from django.db import connection
|
||||
from django.test.utils import CaptureQueriesContext
|
||||
|
||||
from apps.parks.models import Park, ParkLocation, Company
|
||||
|
||||
|
||||
class ParkQueryOptimizationTests(TestCase):
|
||||
"""Tests for Park query optimization."""
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
"""Set up test data for all tests."""
|
||||
# Create a test operator company
|
||||
cls.operator = Company.objects.create(
|
||||
name="Test Operator",
|
||||
slug="test-operator",
|
||||
roles=["OPERATOR"],
|
||||
)
|
||||
|
||||
# Create test parks
|
||||
cls.parks = []
|
||||
for i in range(5):
|
||||
park = Park.objects.create(
|
||||
name=f"Test Park {i}",
|
||||
slug=f"test-park-{i}",
|
||||
operator=cls.operator,
|
||||
timezone="UTC",
|
||||
)
|
||||
# Create location for each park
|
||||
ParkLocation.objects.create(
|
||||
park=park,
|
||||
city=f"City {i}",
|
||||
state="CA",
|
||||
country="USA",
|
||||
)
|
||||
cls.parks.append(park)
|
||||
|
||||
def test_optimized_for_list_query_count(self):
|
||||
"""Verify optimized_for_list uses expected number of queries."""
|
||||
with CaptureQueriesContext(connection) as context:
|
||||
parks = Park.objects.optimized_for_list()
|
||||
# Force evaluation
|
||||
list(parks)
|
||||
|
||||
# Should be a small number of queries (main query + prefetch)
|
||||
# The exact count depends on prefetch_related configuration
|
||||
self.assertLessEqual(
|
||||
len(context.captured_queries),
|
||||
5,
|
||||
f"Expected <= 5 queries, got {len(context.captured_queries)}"
|
||||
)
|
||||
|
||||
def test_optimized_for_detail_query_count(self):
|
||||
"""Verify optimized_for_detail uses expected number of queries."""
|
||||
with CaptureQueriesContext(connection) as context:
|
||||
parks = Park.objects.optimized_for_detail()
|
||||
park = parks.first()
|
||||
if park:
|
||||
# Access related objects that should be prefetched
|
||||
_ = park.operator
|
||||
_ = list(park.areas.all())
|
||||
|
||||
# Should be a reasonable number of queries
|
||||
self.assertLessEqual(
|
||||
len(context.captured_queries),
|
||||
10,
|
||||
f"Expected <= 10 queries, got {len(context.captured_queries)}"
|
||||
)
|
||||
|
||||
def test_with_location_includes_location(self):
|
||||
"""Verify with_location prefetches location data."""
|
||||
with CaptureQueriesContext(connection) as context:
|
||||
parks = Park.objects.with_location()
|
||||
for park in parks:
|
||||
# Accessing location should not cause additional queries
|
||||
_ = park.location
|
||||
|
||||
# Should be minimal queries
|
||||
self.assertLessEqual(len(context.captured_queries), 3)
|
||||
|
||||
def test_for_map_display_returns_minimal_fields(self):
|
||||
"""Verify for_map_display returns only necessary fields."""
|
||||
result = Park.objects.for_map_display()
|
||||
if result.exists():
|
||||
first = result.first()
|
||||
# Should include these fields
|
||||
self.assertIn('id', first)
|
||||
self.assertIn('name', first)
|
||||
self.assertIn('slug', first)
|
||||
self.assertIn('status', first)
|
||||
|
||||
def test_search_autocomplete_limits_results(self):
|
||||
"""Verify search_autocomplete respects limit parameter."""
|
||||
result = Park.objects.search_autocomplete(query="Test", limit=3)
|
||||
self.assertLessEqual(len(result), 3)
|
||||
|
||||
|
||||
class CompanyQueryOptimizationTests(TestCase):
|
||||
"""Tests for Company query optimization."""
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
"""Set up test data for all tests."""
|
||||
# Create test companies with different roles
|
||||
cls.manufacturers = []
|
||||
for i in range(5):
|
||||
company = Company.objects.create(
|
||||
name=f"Manufacturer {i}",
|
||||
slug=f"manufacturer-{i}",
|
||||
roles=["MANUFACTURER"],
|
||||
)
|
||||
cls.manufacturers.append(company)
|
||||
|
||||
cls.operators = []
|
||||
for i in range(3):
|
||||
company = Company.objects.create(
|
||||
name=f"Operator {i}",
|
||||
slug=f"operator-{i}",
|
||||
roles=["OPERATOR"],
|
||||
)
|
||||
cls.operators.append(company)
|
||||
|
||||
def test_manufacturers_query_only_returns_manufacturers(self):
|
||||
"""Verify manufacturers() only returns companies with MANUFACTURER role."""
|
||||
result = Company.objects.manufacturers()
|
||||
for company in result:
|
||||
self.assertIn("MANUFACTURER", company.roles)
|
||||
|
||||
def test_operators_query_only_returns_operators(self):
|
||||
"""Verify operators() only returns companies with OPERATOR role."""
|
||||
result = Company.objects.operators()
|
||||
for company in result:
|
||||
self.assertIn("OPERATOR", company.roles)
|
||||
|
||||
def test_manufacturers_with_ride_count_includes_annotation(self):
|
||||
"""Verify manufacturers_with_ride_count adds ride_count annotation."""
|
||||
result = Company.objects.manufacturers_with_ride_count()
|
||||
if result.exists():
|
||||
first = result.first()
|
||||
# Should have ride_count attribute
|
||||
self.assertTrue(hasattr(first, 'ride_count'))
|
||||
|
||||
def test_operators_with_park_count_includes_annotation(self):
|
||||
"""Verify operators_with_park_count adds park count annotations."""
|
||||
result = Company.objects.operators_with_park_count()
|
||||
if result.exists():
|
||||
first = result.first()
|
||||
# Should have operated_parks_count attribute
|
||||
self.assertTrue(hasattr(first, 'operated_parks_count'))
|
||||
|
||||
|
||||
class ComputedFieldMaintenanceTests(TestCase):
|
||||
"""Tests for computed field maintenance via signals."""
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
"""Set up test data for all tests."""
|
||||
cls.operator = Company.objects.create(
|
||||
name="Test Operator",
|
||||
slug="test-operator",
|
||||
roles=["OPERATOR"],
|
||||
)
|
||||
|
||||
def test_park_search_text_includes_name(self):
|
||||
"""Verify park search_text includes park name."""
|
||||
park = Park.objects.create(
|
||||
name="Magic Kingdom",
|
||||
slug="magic-kingdom",
|
||||
operator=self.operator,
|
||||
timezone="UTC",
|
||||
)
|
||||
self.assertIn("magic kingdom", park.search_text.lower())
|
||||
|
||||
def test_park_search_text_includes_description(self):
|
||||
"""Verify park search_text includes description."""
|
||||
park = Park.objects.create(
|
||||
name="Test Park",
|
||||
slug="test-park",
|
||||
description="A magical theme park experience",
|
||||
operator=self.operator,
|
||||
timezone="UTC",
|
||||
)
|
||||
self.assertIn("magical", park.search_text.lower())
|
||||
|
||||
def test_park_search_text_includes_operator(self):
|
||||
"""Verify park search_text includes operator name."""
|
||||
park = Park.objects.create(
|
||||
name="Test Park",
|
||||
slug="test-park-2",
|
||||
operator=self.operator,
|
||||
timezone="UTC",
|
||||
)
|
||||
self.assertIn("test operator", park.search_text.lower())
|
||||
|
||||
def test_park_opening_year_computed_from_date(self):
|
||||
"""Verify opening_year is computed from opening_date."""
|
||||
from datetime import date
|
||||
|
||||
park = Park.objects.create(
|
||||
name="Test Park",
|
||||
slug="test-park-3",
|
||||
operator=self.operator,
|
||||
timezone="UTC",
|
||||
opening_date=date(1971, 10, 1),
|
||||
)
|
||||
self.assertEqual(park.opening_year, 1971)
|
||||
|
||||
def test_park_search_text_updated_on_location_change(self):
|
||||
"""Verify park search_text updates when location changes."""
|
||||
park = Park.objects.create(
|
||||
name="Test Park",
|
||||
slug="test-park-4",
|
||||
operator=self.operator,
|
||||
timezone="UTC",
|
||||
)
|
||||
|
||||
# Initially no location in search_text
|
||||
original_search_text = park.search_text
|
||||
|
||||
# Add location
|
||||
location = ParkLocation.objects.create(
|
||||
park=park,
|
||||
city="Orlando",
|
||||
state="Florida",
|
||||
country="USA",
|
||||
)
|
||||
|
||||
# Refresh park from database
|
||||
park.refresh_from_db()
|
||||
|
||||
# Search text should now include location
|
||||
# Note: This depends on signal handlers being properly registered
|
||||
# The actual behavior may vary based on signal configuration
|
||||
Reference in New Issue
Block a user