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

View File

@@ -9,6 +9,11 @@ from .parks import Park
@pghistory.track()
class ParkArea(TrackedModel):
# Import managers
from ..managers import ParkAreaManager
objects = ParkAreaManager()
id: int # Type hint for Django's automatic id field
park = models.ForeignKey(Park, on_delete=models.CASCADE, related_name="areas")
name = models.CharField(max_length=255)

View File

@@ -1,8 +1,15 @@
from django.contrib.postgres.fields import ArrayField
from django.db import models
from core.models import TrackedModel
import pghistory
@pghistory.track()
class Company(TrackedModel):
# Import managers
from ..managers import CompanyManager
objects = CompanyManager()
class CompanyRole(models.TextChoices):
OPERATOR = 'OPERATOR', 'Park Operator'
PROPERTY_OWNER = 'PROPERTY_OWNER', 'Property Owner'

View File

@@ -17,6 +17,11 @@ if TYPE_CHECKING:
@pghistory.track()
class Park(TrackedModel):
# Import managers
from ..managers import ParkManager
objects = ParkManager()
id: int # Type hint for Django's automatic id field
STATUS_CHOICES = [
("OPERATING", "Operating"),
@@ -81,6 +86,43 @@ class Park(TrackedModel):
class Meta:
ordering = ["name"]
constraints = [
# Business rule: Closing date must be after opening date
models.CheckConstraint(
name="park_closing_after_opening",
check=models.Q(closing_date__isnull=True) | models.Q(opening_date__isnull=True) | models.Q(closing_date__gte=models.F("opening_date")),
violation_error_message="Closing date must be after opening date"
),
# Business rule: Size must be positive
models.CheckConstraint(
name="park_size_positive",
check=models.Q(size_acres__isnull=True) | models.Q(size_acres__gt=0),
violation_error_message="Park size must be positive"
),
# Business rule: Rating must be between 1 and 10
models.CheckConstraint(
name="park_rating_range",
check=models.Q(average_rating__isnull=True) | (models.Q(average_rating__gte=1) & models.Q(average_rating__lte=10)),
violation_error_message="Average rating must be between 1 and 10"
),
# Business rule: Counts must be non-negative
models.CheckConstraint(
name="park_ride_count_non_negative",
check=models.Q(ride_count__isnull=True) | models.Q(ride_count__gte=0),
violation_error_message="Ride count must be non-negative"
),
models.CheckConstraint(
name="park_coaster_count_non_negative",
check=models.Q(coaster_count__isnull=True) | models.Q(coaster_count__gte=0),
violation_error_message="Coaster count must be non-negative"
),
# Business rule: Coaster count cannot exceed ride count
models.CheckConstraint(
name="park_coaster_count_lte_ride_count",
check=models.Q(coaster_count__isnull=True) | models.Q(ride_count__isnull=True) | models.Q(coaster_count__lte=models.F("ride_count")),
violation_error_message="Coaster count cannot exceed total ride count"
),
]
def __str__(self) -> str:
return self.name

View File

@@ -1,10 +1,16 @@
from django.db import models
from django.db.models import functions
from django.core.validators import MinValueValidator, MaxValueValidator
from core.history import TrackedModel
import pghistory
@pghistory.track()
class ParkReview(TrackedModel):
# Import managers
from ..managers import ParkReviewManager
objects = ParkReviewManager()
"""
A review of a park.
"""
@@ -44,6 +50,27 @@ class ParkReview(TrackedModel):
class Meta:
ordering = ['-created_at']
unique_together = ['park', 'user']
constraints = [
# Business rule: Rating must be between 1 and 10 (database level enforcement)
models.CheckConstraint(
name="park_review_rating_range",
check=models.Q(rating__gte=1) & models.Q(rating__lte=10),
violation_error_message="Rating must be between 1 and 10"
),
# Business rule: Visit date cannot be in the future
models.CheckConstraint(
name="park_review_visit_date_not_future",
check=models.Q(visit_date__lte=functions.Now()),
violation_error_message="Visit date cannot be in the future"
),
# Business rule: If moderated, must have moderator and timestamp
models.CheckConstraint(
name="park_review_moderation_consistency",
check=models.Q(moderated_by__isnull=True, moderated_at__isnull=True) |
models.Q(moderated_by__isnull=False, moderated_at__isnull=False),
violation_error_message="Moderated reviews must have both moderator and moderation timestamp"
),
]
def __str__(self):
return f"Review of {self.park.name} by {self.user.username}"