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

378
tests/factories.py Normal file
View File

@@ -0,0 +1,378 @@
"""
Test factories for ThrillWiki models.
Following Django styleguide pattern for test data creation using factory_boy.
"""
import factory
from factory import fuzzy
from factory.django import DjangoModelFactory
from django.contrib.auth import get_user_model
from django.contrib.gis.geos import Point
from django.utils.text import slugify
from decimal import Decimal
import random
User = get_user_model()
class UserFactory(DjangoModelFactory):
"""Factory for creating User instances."""
class Meta:
model = User
django_get_or_create = ('username',)
username = factory.Sequence(lambda n: f"testuser{n}")
email = factory.LazyAttribute(lambda obj: f"{obj.username}@example.com")
first_name = factory.Faker('first_name')
last_name = factory.Faker('last_name')
is_active = True
is_staff = False
is_superuser = False
@factory.post_generation
def set_password(obj, create, extracted, **kwargs):
if create:
[PASSWORD-REMOVED] or 'testpass123'
obj.set_password(password)
obj.save()
class StaffUserFactory(UserFactory):
"""Factory for creating staff User instances."""
is_staff = True
class SuperUserFactory(UserFactory):
"""Factory for creating superuser instances."""
is_staff = True
is_superuser = True
class CompanyFactory(DjangoModelFactory):
"""Factory for creating Company instances."""
class Meta:
model = 'parks.Company'
django_get_or_create = ('name',)
name = factory.Faker('company')
slug = factory.LazyAttribute(lambda obj: slugify(obj.name))
description = factory.Faker('text', max_nb_chars=500)
website = factory.Faker('url')
founded_year = fuzzy.FuzzyInteger(1800, 2024)
roles = factory.LazyFunction(lambda: ['OPERATOR'])
@factory.post_generation
def multiple_roles(obj, create, extracted, **kwargs):
"""Optionally add multiple roles."""
if create and extracted:
obj.roles = extracted
obj.save()
class OperatorCompanyFactory(CompanyFactory):
"""Factory for companies that operate parks."""
roles = factory.LazyFunction(lambda: ['OPERATOR'])
class ManufacturerCompanyFactory(CompanyFactory):
"""Factory for companies that manufacture rides."""
roles = factory.LazyFunction(lambda: ['MANUFACTURER'])
class DesignerCompanyFactory(CompanyFactory):
"""Factory for companies that design rides."""
roles = factory.LazyFunction(lambda: ['DESIGNER'])
class LocationFactory(DjangoModelFactory):
"""Factory for creating Location instances."""
class Meta:
model = 'location.Location'
name = factory.Faker('city')
location_type = 'park'
latitude = fuzzy.FuzzyFloat(-90, 90)
longitude = fuzzy.FuzzyFloat(-180, 180)
street_address = factory.Faker('street_address')
city = factory.Faker('city')
state = factory.Faker('state')
country = factory.Faker('country')
postal_code = factory.Faker('postcode')
@factory.lazy_attribute
def point(self):
return Point(float(self.longitude), float(self.latitude))
class ParkFactory(DjangoModelFactory):
"""Factory for creating Park instances."""
class Meta:
model = 'parks.Park'
django_get_or_create = ('slug',)
name = factory.Sequence(lambda n: f"Test Park {n}")
slug = factory.LazyAttribute(lambda obj: slugify(obj.name))
description = factory.Faker('text', max_nb_chars=1000)
status = 'OPERATING'
opening_date = factory.Faker('date_between', start_date='-50y', end_date='today')
closing_date = None
operating_season = factory.Faker('sentence', nb_words=4)
size_acres = fuzzy.FuzzyDecimal(1, 1000, precision=2)
website = factory.Faker('url')
average_rating = fuzzy.FuzzyDecimal(1, 10, precision=2)
ride_count = fuzzy.FuzzyInteger(5, 100)
coaster_count = fuzzy.FuzzyInteger(1, 20)
# Relationships
operator = factory.SubFactory(OperatorCompanyFactory)
property_owner = factory.SubFactory(OperatorCompanyFactory)
@factory.post_generation
def create_location(obj, create, extracted, **kwargs):
"""Create a location for the park."""
if create:
LocationFactory(
content_object=obj,
name=obj.name,
location_type='park'
)
class ClosedParkFactory(ParkFactory):
"""Factory for creating closed parks."""
status = 'CLOSED_PERM'
closing_date = factory.Faker('date_between', start_date='-10y', end_date='today')
class ParkAreaFactory(DjangoModelFactory):
"""Factory for creating ParkArea instances."""
class Meta:
model = 'parks.ParkArea'
django_get_or_create = ('park', 'slug')
name = factory.Faker('word')
slug = factory.LazyAttribute(lambda obj: slugify(obj.name))
description = factory.Faker('text', max_nb_chars=500)
# Relationships
park = factory.SubFactory(ParkFactory)
class RideModelFactory(DjangoModelFactory):
"""Factory for creating RideModel instances."""
class Meta:
model = 'rides.RideModel'
django_get_or_create = ('name', 'manufacturer')
name = factory.Faker('word')
description = factory.Faker('text', max_nb_chars=500)
# Relationships
manufacturer = factory.SubFactory(ManufacturerCompanyFactory)
class RideFactory(DjangoModelFactory):
"""Factory for creating Ride instances."""
class Meta:
model = 'rides.Ride'
django_get_or_create = ('park', 'slug')
name = factory.Sequence(lambda n: f"Test Ride {n}")
slug = factory.LazyAttribute(lambda obj: slugify(obj.name))
description = factory.Faker('text', max_nb_chars=1000)
category = fuzzy.FuzzyChoice(['RC', 'WC', 'TR', 'WR', 'DR', 'CR', 'FR', 'SP'])
status = 'OPERATING'
opening_date = factory.Faker('date_between', start_date='-30y', end_date='today')
closing_date = None
min_height_in = fuzzy.FuzzyInteger(36, 48)
max_height_in = None
capacity_per_hour = fuzzy.FuzzyInteger(500, 3000)
ride_duration_seconds = fuzzy.FuzzyInteger(60, 300)
average_rating = fuzzy.FuzzyDecimal(1, 10, precision=2)
# Relationships
park = factory.SubFactory(ParkFactory)
manufacturer = factory.SubFactory(ManufacturerCompanyFactory)
designer = factory.SubFactory(DesignerCompanyFactory)
ride_model = factory.SubFactory(RideModelFactory)
park_area = factory.SubFactory(ParkAreaFactory, park=factory.SelfAttribute('..park'))
@factory.post_generation
def create_location(obj, create, extracted, **kwargs):
"""Create a location for the ride."""
if create:
LocationFactory(
content_object=obj,
name=obj.name,
location_type='ride'
)
class CoasterFactory(RideFactory):
"""Factory for creating roller coaster rides."""
category = fuzzy.FuzzyChoice(['RC', 'WC'])
min_height_in = fuzzy.FuzzyInteger(42, 54)
ride_duration_seconds = fuzzy.FuzzyInteger(90, 240)
class ParkReviewFactory(DjangoModelFactory):
"""Factory for creating ParkReview instances."""
class Meta:
model = 'parks.ParkReview'
django_get_or_create = ('park', 'user')
rating = fuzzy.FuzzyInteger(1, 10)
title = factory.Faker('sentence', nb_words=6)
content = factory.Faker('text', max_nb_chars=2000)
visit_date = factory.Faker('date_between', start_date='-2y', end_date='today')
is_published = True
moderation_notes = ''
# Relationships
park = factory.SubFactory(ParkFactory)
user = factory.SubFactory(UserFactory)
class RideReviewFactory(DjangoModelFactory):
"""Factory for creating RideReview instances."""
class Meta:
model = 'rides.RideReview'
django_get_or_create = ('ride', 'user')
rating = fuzzy.FuzzyInteger(1, 10)
title = factory.Faker('sentence', nb_words=6)
content = factory.Faker('text', max_nb_chars=2000)
visit_date = factory.Faker('date_between', start_date='-2y', end_date='today')
is_published = True
moderation_notes = ''
# Relationships
ride = factory.SubFactory(RideFactory)
user = factory.SubFactory(UserFactory)
class ModeratedReviewFactory(ParkReviewFactory):
"""Factory for creating moderated reviews."""
moderation_notes = factory.Faker('sentence')
moderated_by = factory.SubFactory(StaffUserFactory)
moderated_at = factory.Faker('date_time_between', start_date='-1y', end_date='now')
class EditSubmissionFactory(DjangoModelFactory):
"""Factory for creating EditSubmission instances."""
class Meta:
model = 'moderation.EditSubmission'
submission_type = 'UPDATE'
changes = factory.LazyFunction(lambda: {'name': 'Updated Name'})
status = 'PENDING'
notes = factory.Faker('sentence')
# Relationships
submitted_by = factory.SubFactory(UserFactory)
content_object = factory.SubFactory(ParkFactory)
# Trait mixins for common scenarios
class Traits:
"""Common trait mixins for factories."""
@staticmethod
def operating_park():
"""Trait for operating parks."""
return {
'status': 'OPERATING',
'closing_date': None
}
@staticmethod
def closed_park():
"""Trait for closed parks."""
return {
'status': 'CLOSED_PERM',
'closing_date': factory.Faker('date_between', start_date='-10y', end_date='today')
}
@staticmethod
def high_rated():
"""Trait for highly rated items."""
return {
'average_rating': fuzzy.FuzzyDecimal(8, 10, precision=2)
}
@staticmethod
def recent_submission():
"""Trait for recent submissions."""
return {
'submitted_at': factory.Faker('date_time_between', start_date='-7d', end_date='now')
}
# Specialized factories for testing scenarios
class TestScenarios:
"""Pre-configured factory combinations for common test scenarios."""
@staticmethod
def complete_park_with_rides(num_rides=5):
"""Create a complete park with rides and reviews."""
park = ParkFactory()
rides = [RideFactory(park=park) for _ in range(num_rides)]
park_review = ParkReviewFactory(park=park)
ride_reviews = [RideReviewFactory(ride=ride) for ride in rides[:2]]
return {
'park': park,
'rides': rides,
'park_review': park_review,
'ride_reviews': ride_reviews
}
@staticmethod
def moderation_workflow():
"""Create a complete moderation workflow scenario."""
user = UserFactory()
moderator = StaffUserFactory()
park = ParkFactory()
submission = EditSubmissionFactory(
submitted_by=user,
content_object=park
)
return {
'user': user,
'moderator': moderator,
'park': park,
'submission': submission
}
@staticmethod
def review_scenario():
"""Create a scenario with multiple reviews and ratings."""
park = ParkFactory()
users = [UserFactory() for _ in range(5)]
reviews = [ParkReviewFactory(park=park, user=user) for user in users]
return {
'park': park,
'users': users,
'reviews': reviews
}