feat: Implement MFA authentication, add ride statistics model, and update various services, APIs, and tests across the application.

This commit is contained in:
pacnpal
2025-12-28 17:32:53 -05:00
parent aa56c46c27
commit c95f99ca10
452 changed files with 7948 additions and 6073 deletions

View File

@@ -5,20 +5,18 @@ These tests verify the functionality of park, area, company, location,
and review admin classes including query optimization and custom actions.
"""
import pytest
from django.contrib.admin.sites import AdminSite
from django.contrib.auth import get_user_model
from django.test import RequestFactory, TestCase
from apps.parks.admin import (
CompanyAdmin,
CompanyHeadquartersAdmin,
ParkAdmin,
ParkAreaAdmin,
ParkLocationAdmin,
ParkReviewAdmin,
)
from apps.parks.models import Company, CompanyHeadquarters, Park, ParkArea, ParkLocation, ParkReview
from apps.parks.models import Company, Park, ParkArea, ParkLocation, ParkReview
User = get_user_model()

View File

@@ -9,8 +9,8 @@ This module tests end-to-end park lifecycle workflows including:
- Related ride status updates
"""
from django.test import TestCase
from django.contrib.auth import get_user_model
from django.test import TestCase
from django.utils import timezone
User = get_user_model()
@@ -18,7 +18,7 @@ User = get_user_model()
class ParkOpeningWorkflowTests(TestCase):
"""Tests for park opening workflow."""
@classmethod
def setUpTestData(cls):
cls.user = User.objects.create_user(
@@ -33,16 +33,16 @@ class ParkOpeningWorkflowTests(TestCase):
password='testpass123',
role='MODERATOR'
)
def _create_park(self, status='OPERATING', **kwargs):
"""Helper to create a park."""
from apps.parks.models import Park, Company
from apps.parks.models import Company, Park
operator = Company.objects.create(
name=f'Operator {status}',
roles=['OPERATOR']
)
defaults = {
'name': f'Test Park {status}',
'slug': f'test-park-{status.lower()}-{timezone.now().timestamp()}',
@@ -52,28 +52,28 @@ class ParkOpeningWorkflowTests(TestCase):
}
defaults.update(kwargs)
return Park.objects.create(**defaults)
def test_park_opens_from_under_construction(self):
"""
Test park opening from under construction state.
Flow: UNDER_CONSTRUCTION → OPERATING
"""
park = self._create_park(status='UNDER_CONSTRUCTION')
self.assertEqual(park.status, 'UNDER_CONSTRUCTION')
# Park opens
park.transition_to_operating(user=self.user)
park.save()
park.refresh_from_db()
self.assertEqual(park.status, 'OPERATING')
class ParkTemporaryClosureWorkflowTests(TestCase):
"""Tests for park temporary closure workflow."""
@classmethod
def setUpTestData(cls):
cls.user = User.objects.create_user(
@@ -82,15 +82,15 @@ class ParkTemporaryClosureWorkflowTests(TestCase):
password='testpass123',
role='USER'
)
def _create_park(self, status='OPERATING', **kwargs):
from apps.parks.models import Park, Company
from apps.parks.models import Company, Park
operator = Company.objects.create(
name=f'Operator Temp {timezone.now().timestamp()}',
roles=['OPERATOR']
)
defaults = {
'name': f'Test Park Temp {timezone.now().timestamp()}',
'slug': f'test-park-temp-{timezone.now().timestamp()}',
@@ -100,35 +100,35 @@ class ParkTemporaryClosureWorkflowTests(TestCase):
}
defaults.update(kwargs)
return Park.objects.create(**defaults)
def test_park_temporary_closure_and_reopen(self):
"""
Test park temporary closure and reopening.
Flow: OPERATING → CLOSED_TEMP → OPERATING
"""
park = self._create_park(status='OPERATING')
self.assertEqual(park.status, 'OPERATING')
# Close temporarily (e.g., off-season)
park.transition_to_closed_temp(user=self.user)
park.save()
park.refresh_from_db()
self.assertEqual(park.status, 'CLOSED_TEMP')
# Reopen
park.transition_to_operating(user=self.user)
park.save()
park.refresh_from_db()
self.assertEqual(park.status, 'OPERATING')
class ParkPermanentClosureWorkflowTests(TestCase):
"""Tests for park permanent closure workflow."""
@classmethod
def setUpTestData(cls):
cls.moderator = User.objects.create_user(
@@ -137,15 +137,15 @@ class ParkPermanentClosureWorkflowTests(TestCase):
password='testpass123',
role='MODERATOR'
)
def _create_park(self, status='OPERATING', **kwargs):
from apps.parks.models import Park, Company
from apps.parks.models import Company, Park
operator = Company.objects.create(
name=f'Operator Perm {timezone.now().timestamp()}',
roles=['OPERATOR']
)
defaults = {
'name': f'Test Park Perm {timezone.now().timestamp()}',
'slug': f'test-park-perm-{timezone.now().timestamp()}',
@@ -155,48 +155,48 @@ class ParkPermanentClosureWorkflowTests(TestCase):
}
defaults.update(kwargs)
return Park.objects.create(**defaults)
def test_park_permanent_closure(self):
"""
Test park permanent closure from operating state.
Flow: OPERATING → CLOSED_PERM
"""
park = self._create_park(status='OPERATING')
# Close permanently
park.transition_to_closed_perm(user=self.moderator)
park.closing_date = timezone.now().date()
park.save()
park.refresh_from_db()
self.assertEqual(park.status, 'CLOSED_PERM')
self.assertIsNotNone(park.closing_date)
def test_park_permanent_closure_from_temp(self):
"""
Test park permanent closure from temporary closure.
Flow: OPERATING → CLOSED_TEMP → CLOSED_PERM
"""
park = self._create_park(status='OPERATING')
# Temporary closure
park.transition_to_closed_temp(user=self.moderator)
park.save()
# Becomes permanent
park.transition_to_closed_perm(user=self.moderator)
park.closing_date = timezone.now().date()
park.save()
park.refresh_from_db()
self.assertEqual(park.status, 'CLOSED_PERM')
class ParkDemolitionWorkflowTests(TestCase):
"""Tests for park demolition workflow."""
@classmethod
def setUpTestData(cls):
cls.moderator = User.objects.create_user(
@@ -205,15 +205,15 @@ class ParkDemolitionWorkflowTests(TestCase):
password='testpass123',
role='MODERATOR'
)
def _create_park(self, status='CLOSED_PERM', **kwargs):
from apps.parks.models import Park, Company
from apps.parks.models import Company, Park
operator = Company.objects.create(
name=f'Operator Demo {timezone.now().timestamp()}',
roles=['OPERATOR']
)
defaults = {
'name': f'Test Park Demo {timezone.now().timestamp()}',
'slug': f'test-park-demo-{timezone.now().timestamp()}',
@@ -223,28 +223,28 @@ class ParkDemolitionWorkflowTests(TestCase):
}
defaults.update(kwargs)
return Park.objects.create(**defaults)
def test_park_demolition_workflow(self):
"""
Test complete park demolition workflow.
Flow: OPERATING → CLOSED_PERM → DEMOLISHED
"""
park = self._create_park(status='CLOSED_PERM')
# Demolish
park.transition_to_demolished(user=self.moderator)
park.save()
park.refresh_from_db()
self.assertEqual(park.status, 'DEMOLISHED')
def test_demolished_is_final_state(self):
"""Test that demolished parks cannot transition further."""
from django_fsm import TransitionNotAllowed
park = self._create_park(status='DEMOLISHED')
# Cannot transition from demolished
with self.assertRaises(TransitionNotAllowed):
park.transition_to_operating(user=self.moderator)
@@ -252,7 +252,7 @@ class ParkDemolitionWorkflowTests(TestCase):
class ParkRelocationWorkflowTests(TestCase):
"""Tests for park relocation workflow."""
@classmethod
def setUpTestData(cls):
cls.moderator = User.objects.create_user(
@@ -261,15 +261,15 @@ class ParkRelocationWorkflowTests(TestCase):
password='testpass123',
role='MODERATOR'
)
def _create_park(self, status='CLOSED_PERM', **kwargs):
from apps.parks.models import Park, Company
from apps.parks.models import Company, Park
operator = Company.objects.create(
name=f'Operator Reloc {timezone.now().timestamp()}',
roles=['OPERATOR']
)
defaults = {
'name': f'Test Park Reloc {timezone.now().timestamp()}',
'slug': f'test-park-reloc-{timezone.now().timestamp()}',
@@ -279,28 +279,28 @@ class ParkRelocationWorkflowTests(TestCase):
}
defaults.update(kwargs)
return Park.objects.create(**defaults)
def test_park_relocation_workflow(self):
"""
Test park relocation workflow.
Flow: OPERATING → CLOSED_PERM → RELOCATED
"""
park = self._create_park(status='CLOSED_PERM')
# Relocate
park.transition_to_relocated(user=self.moderator)
park.save()
park.refresh_from_db()
self.assertEqual(park.status, 'RELOCATED')
def test_relocated_is_final_state(self):
"""Test that relocated parks cannot transition further."""
from django_fsm import TransitionNotAllowed
park = self._create_park(status='RELOCATED')
# Cannot transition from relocated
with self.assertRaises(TransitionNotAllowed):
park.transition_to_operating(user=self.moderator)
@@ -308,7 +308,7 @@ class ParkRelocationWorkflowTests(TestCase):
class ParkWrapperMethodTests(TestCase):
"""Tests for park wrapper methods."""
@classmethod
def setUpTestData(cls):
cls.user = User.objects.create_user(
@@ -323,15 +323,15 @@ class ParkWrapperMethodTests(TestCase):
password='testpass123',
role='MODERATOR'
)
def _create_park(self, status='OPERATING', **kwargs):
from apps.parks.models import Park, Company
from apps.parks.models import Company, Park
operator = Company.objects.create(
name=f'Operator Wrapper {timezone.now().timestamp()}',
roles=['OPERATOR']
)
defaults = {
'name': f'Test Park Wrapper {timezone.now().timestamp()}',
'slug': f'test-park-wrapper-{timezone.now().timestamp()}',
@@ -341,40 +341,40 @@ class ParkWrapperMethodTests(TestCase):
}
defaults.update(kwargs)
return Park.objects.create(**defaults)
def test_close_temporarily_wrapper(self):
"""Test close_temporarily wrapper method."""
park = self._create_park(status='OPERATING')
# Use wrapper method if it exists
if hasattr(park, 'close_temporarily'):
park.close_temporarily(user=self.user)
else:
park.transition_to_closed_temp(user=self.user)
park.save()
park.refresh_from_db()
self.assertEqual(park.status, 'CLOSED_TEMP')
def test_reopen_wrapper(self):
"""Test reopen wrapper method."""
park = self._create_park(status='CLOSED_TEMP')
# Use wrapper method if it exists
if hasattr(park, 'reopen'):
park.reopen(user=self.user)
else:
park.transition_to_operating(user=self.user)
park.save()
park.refresh_from_db()
self.assertEqual(park.status, 'OPERATING')
def test_close_permanently_wrapper(self):
"""Test close_permanently wrapper method."""
park = self._create_park(status='OPERATING')
closing_date = timezone.now().date()
# Use wrapper method if it exists
if hasattr(park, 'close_permanently'):
park.close_permanently(closing_date=closing_date, user=self.moderator)
@@ -382,24 +382,24 @@ class ParkWrapperMethodTests(TestCase):
park.transition_to_closed_perm(user=self.moderator)
park.closing_date = closing_date
park.save()
park.refresh_from_db()
self.assertEqual(park.status, 'CLOSED_PERM')
def test_demolish_wrapper(self):
"""Test demolish wrapper method."""
park = self._create_park(status='CLOSED_PERM')
# Use wrapper method if it exists
if hasattr(park, 'demolish'):
park.demolish(user=self.moderator)
else:
park.transition_to_demolished(user=self.moderator)
park.save()
park.refresh_from_db()
self.assertEqual(park.status, 'DEMOLISHED')
def test_relocate_wrapper(self):
"""Test relocate wrapper method."""
park = self._create_park(status='CLOSED_PERM')
@@ -434,7 +434,7 @@ class ParkStateLogTests(TestCase):
)
def _create_park(self, status='OPERATING', **kwargs):
from apps.parks.models import Park, Company
from apps.parks.models import Company, Park
operator = Company.objects.create(
name=f'Operator Log {timezone.now().timestamp()}',
@@ -453,8 +453,8 @@ class ParkStateLogTests(TestCase):
def test_transition_creates_state_log(self):
"""Test that park transitions create StateLog entries."""
from django_fsm_log.models import StateLog
from django.contrib.contenttypes.models import ContentType
from django_fsm_log.models import StateLog
park = self._create_park(status='OPERATING')
park_ct = ContentType.objects.get_for_model(park)
@@ -475,8 +475,8 @@ class ParkStateLogTests(TestCase):
def test_multiple_transitions_logged(self):
"""Test that multiple park transitions are all logged."""
from django_fsm_log.models import StateLog
from django.contrib.contenttypes.models import ContentType
from django_fsm_log.models import StateLog
park = self._create_park(status='OPERATING')
park_ct = ContentType.objects.get_for_model(park)
@@ -503,8 +503,8 @@ class ParkStateLogTests(TestCase):
def test_full_lifecycle_logged(self):
"""Test complete park lifecycle is logged."""
from django_fsm_log.models import StateLog
from django.contrib.contenttypes.models import ContentType
from django_fsm_log.models import StateLog
park = self._create_park(status='OPERATING')
park_ct = ContentType.objects.get_for_model(park)

View File

@@ -7,11 +7,11 @@ These tests verify that:
3. Computed fields are updated correctly
"""
from django.test import TestCase
from django.db import connection
from django.test import TestCase
from django.test.utils import CaptureQueriesContext
from apps.parks.models import Park, ParkLocation, Company
from apps.parks.models import Company, Park, ParkLocation
class ParkQueryOptimizationTests(TestCase):
@@ -225,10 +225,9 @@ class ComputedFieldMaintenanceTests(TestCase):
)
# Initially no location in search_text
original_search_text = park.search_text
# Add location
location = ParkLocation.objects.create(
ParkLocation.objects.create(
park=park,
city="Orlando",
state="Florida",