""" Comprehensive tests for the parks app state machine. This module contains tests for: - Park status FSM transitions - Park transition wrapper methods - Transition history logging - Related model updates during transitions """ from django.test import TestCase from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType from django.utils import timezone from django_fsm import TransitionNotAllowed from .models import Park, Company from datetime import date User = get_user_model() # ============================================================================ # Park FSM Transition Tests # ============================================================================ class ParkTransitionTests(TestCase): """Comprehensive tests for Park FSM transitions.""" def setUp(self): """Set up test fixtures.""" self.user = User.objects.create_user( username='testuser', email='test@example.com', password='testpass123', role='USER' ) self.moderator = User.objects.create_user( username='moderator', email='moderator@example.com', password='testpass123', role='MODERATOR' ) self.admin = User.objects.create_user( username='admin', email='admin@example.com', password='testpass123', role='ADMIN' ) # Create operator company self.operator = Company.objects.create( name='Test Operator', description='Test operator company', roles=['OPERATOR'] ) def _create_park(self, status='OPERATING', **kwargs): """Helper to create a Park with specified status.""" defaults = { 'name': 'Test Park', 'slug': 'test-park', 'description': 'A test park', 'operator': self.operator, 'timezone': 'America/New_York' } defaults.update(kwargs) return Park.objects.create(status=status, **defaults) # ------------------------------------------------------------------------- # Operating status transitions # ------------------------------------------------------------------------- def test_operating_to_closed_temp_transition(self): """Test transition from OPERATING to CLOSED_TEMP.""" park = self._create_park(status='OPERATING') self.assertEqual(park.status, 'OPERATING') park.transition_to_closed_temp(user=self.user) park.save() park.refresh_from_db() self.assertEqual(park.status, 'CLOSED_TEMP') def test_operating_to_closed_perm_transition(self): """Test transition from OPERATING to CLOSED_PERM.""" park = self._create_park(status='OPERATING') park.transition_to_closed_perm(user=self.moderator) park.closing_date = date.today() park.save() park.refresh_from_db() self.assertEqual(park.status, 'CLOSED_PERM') self.assertIsNotNone(park.closing_date) # ------------------------------------------------------------------------- # Under construction transitions # ------------------------------------------------------------------------- def test_under_construction_to_operating_transition(self): """Test transition from UNDER_CONSTRUCTION to OPERATING.""" park = self._create_park(status='UNDER_CONSTRUCTION') self.assertEqual(park.status, 'UNDER_CONSTRUCTION') park.transition_to_operating(user=self.user) park.save() park.refresh_from_db() self.assertEqual(park.status, 'OPERATING') # ------------------------------------------------------------------------- # Closed temp transitions # ------------------------------------------------------------------------- def test_closed_temp_to_operating_transition(self): """Test transition from CLOSED_TEMP to OPERATING (reopen).""" park = self._create_park(status='CLOSED_TEMP') park.transition_to_operating(user=self.user) park.save() park.refresh_from_db() self.assertEqual(park.status, 'OPERATING') def test_closed_temp_to_closed_perm_transition(self): """Test transition from CLOSED_TEMP to CLOSED_PERM.""" park = self._create_park(status='CLOSED_TEMP') park.transition_to_closed_perm(user=self.moderator) park.closing_date = date.today() park.save() park.refresh_from_db() self.assertEqual(park.status, 'CLOSED_PERM') # ------------------------------------------------------------------------- # Closed perm transitions (to final states) # ------------------------------------------------------------------------- def test_closed_perm_to_demolished_transition(self): """Test transition from CLOSED_PERM to DEMOLISHED.""" park = self._create_park(status='CLOSED_PERM') park.transition_to_demolished(user=self.moderator) park.save() park.refresh_from_db() self.assertEqual(park.status, 'DEMOLISHED') def test_closed_perm_to_relocated_transition(self): """Test transition from CLOSED_PERM to RELOCATED.""" park = self._create_park(status='CLOSED_PERM') park.transition_to_relocated(user=self.moderator) park.save() park.refresh_from_db() self.assertEqual(park.status, 'RELOCATED') # ------------------------------------------------------------------------- # Invalid transitions (final states) # ------------------------------------------------------------------------- def test_demolished_cannot_transition(self): """Test that DEMOLISHED state cannot transition further.""" park = self._create_park(status='DEMOLISHED') with self.assertRaises(TransitionNotAllowed): park.transition_to_operating(user=self.moderator) def test_relocated_cannot_transition(self): """Test that RELOCATED state cannot transition further.""" park = self._create_park(status='RELOCATED') with self.assertRaises(TransitionNotAllowed): park.transition_to_operating(user=self.moderator) def test_operating_cannot_directly_demolish(self): """Test that OPERATING cannot directly transition to DEMOLISHED.""" park = self._create_park(status='OPERATING') with self.assertRaises(TransitionNotAllowed): park.transition_to_demolished(user=self.moderator) def test_operating_cannot_directly_relocate(self): """Test that OPERATING cannot directly transition to RELOCATED.""" park = self._create_park(status='OPERATING') with self.assertRaises(TransitionNotAllowed): park.transition_to_relocated(user=self.moderator) # ------------------------------------------------------------------------- # Wrapper method tests # ------------------------------------------------------------------------- def test_reopen_wrapper_method(self): """Test the reopen() wrapper method.""" park = self._create_park(status='CLOSED_TEMP') park.reopen(user=self.user) park.refresh_from_db() self.assertEqual(park.status, 'OPERATING') def test_close_temporarily_wrapper_method(self): """Test the close_temporarily() wrapper method.""" park = self._create_park(status='OPERATING') park.close_temporarily(user=self.user) park.refresh_from_db() self.assertEqual(park.status, 'CLOSED_TEMP') def test_close_permanently_wrapper_method(self): """Test the close_permanently() wrapper method.""" park = self._create_park(status='OPERATING') closing = date(2025, 12, 31) park.close_permanently(closing_date=closing, user=self.moderator) park.refresh_from_db() self.assertEqual(park.status, 'CLOSED_PERM') self.assertEqual(park.closing_date, closing) def test_close_permanently_without_date(self): """Test close_permanently() without closing_date.""" park = self._create_park(status='OPERATING') park.close_permanently(user=self.moderator) park.refresh_from_db() self.assertEqual(park.status, 'CLOSED_PERM') self.assertIsNone(park.closing_date) def test_demolish_wrapper_method(self): """Test the demolish() wrapper method.""" park = self._create_park(status='CLOSED_PERM') park.demolish(user=self.moderator) park.refresh_from_db() self.assertEqual(park.status, 'DEMOLISHED') def test_relocate_wrapper_method(self): """Test the relocate() wrapper method.""" park = self._create_park(status='CLOSED_PERM') park.relocate(user=self.moderator) park.refresh_from_db() self.assertEqual(park.status, 'RELOCATED') def test_start_construction_wrapper_method(self): """Test the start_construction() wrapper method if applicable.""" # This depends on allowed transitions - skip if not allowed try: park = self._create_park(status='OPERATING') park.start_construction(user=self.moderator) park.refresh_from_db() self.assertEqual(park.status, 'UNDER_CONSTRUCTION') except TransitionNotAllowed: # If transition from OPERATING to UNDER_CONSTRUCTION is not allowed pass # ============================================================================ # Park Transition History Tests # ============================================================================ class ParkTransitionHistoryTests(TestCase): """Tests for Park transition history logging.""" def setUp(self): """Set up test fixtures.""" self.moderator = User.objects.create_user( username='moderator', email='moderator@example.com', password='testpass123', role='MODERATOR' ) self.operator = Company.objects.create( name='Test Operator', description='Test operator company', roles=['OPERATOR'] ) def _create_park(self, status='OPERATING'): """Helper to create a Park.""" return Park.objects.create( name='Test Park', slug='test-park', description='A test park', operator=self.operator, status=status, timezone='America/New_York' ) def test_transition_creates_state_log(self): """Test that transitions create StateLog entries.""" from django_fsm_log.models import StateLog park = self._create_park(status='OPERATING') park.transition_to_closed_temp(user=self.moderator) park.save() park_ct = ContentType.objects.get_for_model(park) log = StateLog.objects.filter( content_type=park_ct, object_id=park.id ).first() self.assertIsNotNone(log) self.assertEqual(log.state, 'CLOSED_TEMP') self.assertEqual(log.by, self.moderator) def test_multiple_transitions_create_multiple_logs(self): """Test that multiple transitions create multiple log entries.""" from django_fsm_log.models import StateLog park = self._create_park(status='OPERATING') park_ct = ContentType.objects.get_for_model(park) # First transition park.transition_to_closed_temp(user=self.moderator) park.save() # Second transition park.transition_to_operating(user=self.moderator) park.save() logs = StateLog.objects.filter( content_type=park_ct, object_id=park.id ).order_by('timestamp') self.assertEqual(logs.count(), 2) self.assertEqual(logs[0].state, 'CLOSED_TEMP') self.assertEqual(logs[1].state, 'OPERATING') def test_transition_log_includes_user(self): """Test that transition logs include the user who made the change.""" from django_fsm_log.models import StateLog park = self._create_park(status='OPERATING') park.transition_to_closed_perm(user=self.moderator) park.save() park_ct = ContentType.objects.get_for_model(park) log = StateLog.objects.filter( content_type=park_ct, object_id=park.id ).first() self.assertEqual(log.by, self.moderator) # ============================================================================ # Park Model Business Logic Tests # ============================================================================ class ParkBusinessLogicTests(TestCase): """Tests for Park model business logic.""" def setUp(self): """Set up test fixtures.""" self.operator = Company.objects.create( name='Test Operator', description='Test operator company', roles=['OPERATOR'] ) self.property_owner = Company.objects.create( name='Property Owner', description='Property owner company', roles=['PROPERTY_OWNER'] ) def test_park_creates_with_valid_operator(self): """Test park can be created with valid operator.""" park = Park.objects.create( name='Test Park', slug='test-park', description='A test park', operator=self.operator, timezone='America/New_York' ) self.assertEqual(park.operator, self.operator) def test_park_slug_auto_generated(self): """Test that park slug is auto-generated from name.""" park = Park.objects.create( name='My Amazing Theme Park', description='A test park', operator=self.operator, timezone='America/New_York' ) self.assertEqual(park.slug, 'my-amazing-theme-park') def test_park_url_generated(self): """Test that frontend URL is generated on save.""" park = Park.objects.create( name='Test Park', slug='test-park', description='A test park', operator=self.operator, timezone='America/New_York' ) self.assertIn('test-park', park.url) def test_opening_year_computed_from_opening_date(self): """Test that opening_year is computed from opening_date.""" park = Park.objects.create( name='Test Park', slug='test-park', description='A test park', operator=self.operator, opening_date=date(2020, 6, 15), timezone='America/New_York' ) self.assertEqual(park.opening_year, 2020) def test_search_text_populated(self): """Test that search_text is populated on save.""" park = Park.objects.create( name='Test Park', slug='test-park', description='A wonderful theme park', operator=self.operator, timezone='America/New_York' ) self.assertIn('test park', park.search_text) self.assertIn('wonderful theme park', park.search_text) self.assertIn('test operator', park.search_text) def test_park_with_property_owner(self): """Test park with separate property owner.""" park = Park.objects.create( name='Test Park', slug='test-park', description='A test park', operator=self.operator, property_owner=self.property_owner, timezone='America/New_York' ) self.assertEqual(park.operator, self.operator) self.assertEqual(park.property_owner, self.property_owner) # ============================================================================ # Park Historical Slug Tests # ============================================================================ class ParkSlugHistoryTests(TestCase): """Tests for Park historical slug tracking.""" def setUp(self): """Set up test fixtures.""" self.operator = Company.objects.create( name='Test Operator', description='Test operator company', roles=['OPERATOR'] ) def test_historical_slug_created_on_name_change(self): """Test that historical slug is created when name changes.""" from django.contrib.contenttypes.models import ContentType from apps.core.history import HistoricalSlug park = Park.objects.create( name='Original Name', description='A test park', operator=self.operator, timezone='America/New_York' ) original_slug = park.slug # Change name park.name = 'New Name' park.save() # Check historical slug was created park_ct = ContentType.objects.get_for_model(park) historical = HistoricalSlug.objects.filter( content_type=park_ct, object_id=park.id, slug=original_slug ).first() self.assertIsNotNone(historical) self.assertEqual(historical.slug, original_slug) def test_get_by_slug_finds_current_slug(self): """Test get_by_slug finds park by current slug.""" park = Park.objects.create( name='Test Park', slug='test-park', description='A test park', operator=self.operator, timezone='America/New_York' ) found_park, is_historical = Park.get_by_slug('test-park') self.assertEqual(found_park, park) self.assertFalse(is_historical) def test_get_by_slug_finds_historical_slug(self): """Test get_by_slug finds park by historical slug.""" from django.contrib.contenttypes.models import ContentType from apps.core.history import HistoricalSlug park = Park.objects.create( name='Original Name', description='A test park', operator=self.operator, timezone='America/New_York' ) original_slug = park.slug # Change name to create historical slug park.name = 'New Name' park.save() # Find by historical slug found_park, is_historical = Park.get_by_slug(original_slug) self.assertEqual(found_park, park) self.assertTrue(is_historical)