""" 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 datetime import date from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType from django.test import TestCase from django_fsm import TransitionNotAllowed from .models import Company, Park 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.""" 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)