""" Integration tests for the FSM system. Tests the complete FSM functionality including models, state management, and API endpoints. """ from datetime import datetime from unittest.mock import patch from django.test import TestCase from fsm.state_manager import get_state_manager from fsm.state_models import AnnotationState, ProjectState, TaskState from projects.tests.factories import ProjectFactory from tasks.tests.factories import AnnotationFactory, TaskFactory from users.tests.factories import UserFactory class TestFSMModels(TestCase): """Test FSM model functionality""" def setUp(self): self.user = UserFactory(email='test@example.com') self.project = ProjectFactory(created_by=self.user) self.task = TaskFactory(project=self.project, data={'text': 'test'}) # Clear cache to ensure tests start with clean state from django.core.cache import cache cache.clear() def test_task_state_creation(self): """Test TaskState creation and basic functionality""" task_state = TaskState.objects.create( task=self.task, project_id=self.task.project_id, # Denormalized from task.project_id state='CREATED', triggered_by=self.user, reason='Task created for testing', ) # Check basic fields assert task_state.state == 'CREATED' assert task_state.task == self.task assert task_state.triggered_by == self.user # Check UUID7 functionality assert task_state.id.version == 7 assert isinstance(task_state.timestamp_from_uuid, datetime) # Check string representation str_repr = str(task_state) assert 'Task' in str_repr assert 'CREATED' in str_repr def test_annotation_state_creation(self): """Test AnnotationState creation and basic functionality""" annotation = AnnotationFactory(task=self.task, completed_by=self.user, result=[]) annotation_state = AnnotationState.objects.create( annotation=annotation, task_id=annotation.task.id, # Denormalized from annotation.task_id project_id=annotation.task.project_id, # Denormalized from annotation.task.project_id completed_by_id=annotation.completed_by.id if annotation.completed_by else None, # Denormalized state='DRAFT', triggered_by=self.user, reason='Annotation draft created', ) # Check basic fields assert annotation_state.state == 'DRAFT' assert annotation_state.annotation == annotation # Check terminal state property assert not annotation_state.is_terminal_state # Test completed state completed_state = AnnotationState.objects.create( annotation=annotation, task_id=annotation.task.id, project_id=annotation.task.project_id, completed_by_id=annotation.completed_by.id if annotation.completed_by else None, state='COMPLETED', triggered_by=self.user, ) assert completed_state.is_terminal_state def test_project_state_creation(self): """Test ProjectState creation and basic functionality""" project_state = ProjectState.objects.create( project=self.project, state='CREATED', triggered_by=self.user, reason='Project created for testing' ) # Check basic fields assert project_state.state == 'CREATED' assert project_state.project == self.project # Test terminal state assert not project_state.is_terminal_state completed_state = ProjectState.objects.create(project=self.project, state='COMPLETED', triggered_by=self.user) assert completed_state.is_terminal_state class TestStateManager(TestCase): """Test StateManager functionality with mocked transaction support""" def setUp(self): from core.current_request import CurrentContext self.user = UserFactory(email='test@example.com') # Set up CurrentContext BEFORE creating entities that need FSM CurrentContext.set_user(self.user) self.project = ProjectFactory(created_by=self.user) if hasattr(self.project, 'organization') and self.project.organization: CurrentContext.set_organization_id(self.project.organization.id) self.task = TaskFactory(project=self.project, data={'text': 'test'}) self.StateManager = get_state_manager() # Clear cache to ensure tests start with clean state from django.core.cache import cache cache.clear() # Ensure registry is properly initialized for TaskState from fsm.registry import state_model_registry from fsm.state_models import TaskState if not state_model_registry.get_model('task'): state_model_registry.register_model('task', TaskState) def tearDown(self): from core.current_request import CurrentContext CurrentContext.clear() def test_get_current_state_empty(self): """Test getting current state when task is created""" # With FsmHistoryStateModel, tasks automatically get a state on creation current_state = self.StateManager.get_current_state_value(self.task) assert current_state == 'CREATED' # FsmHistoryStateModel auto-creates state @patch('fsm.state_manager.flag_set') def test_transition_state(self, mock_flag_set): """Test state transition functionality with immediate cache updates""" from django.core.cache import cache cache.clear() # Enable FSM feature flag mock_flag_set.return_value = True # Initial transition success = self.StateManager.transition_state( entity=self.task, new_state='CREATED', user=self.user, transition_name='create_task', reason='Initial task creation', ) assert success # Check current state - should work with immediate cache update current_state = self.StateManager.get_current_state_value(self.task) assert current_state == 'CREATED' # Another transition success = self.StateManager.transition_state( entity=self.task, new_state='IN_PROGRESS', user=self.user, transition_name='start_work', context={'started_by': 'user'}, ) assert success current_state = self.StateManager.get_current_state_value(self.task) assert current_state == 'IN_PROGRESS' @patch('fsm.state_manager.flag_set') def test_get_current_state_object(self, mock_flag_set): """Test getting current state object with full details""" from django.core.cache import cache cache.clear() # Enable FSM feature flag mock_flag_set.return_value = True # Create some state transitions self.StateManager.transition_state(entity=self.task, new_state='CREATED', user=self.user) self.StateManager.transition_state( entity=self.task, new_state='IN_PROGRESS', user=self.user, context={'test': 'data'} ) current_state_obj = self.StateManager.get_current_state_object(self.task) assert current_state_obj is not None assert current_state_obj.state == 'IN_PROGRESS' assert current_state_obj.previous_state == 'CREATED' assert current_state_obj.triggered_by == self.user assert current_state_obj.context_data == {'test': 'data'} @patch('fsm.state_manager.flag_set') def test_get_state_history(self, mock_flag_set): """Test state history retrieval""" from django.core.cache import cache cache.clear() # Enable FSM feature flag mock_flag_set.return_value = True transitions = [('CREATED', 'create_task'), ('IN_PROGRESS', 'start_work'), ('COMPLETED', 'finish_work')] for state, transition in transitions: self.StateManager.transition_state( entity=self.task, new_state=state, user=self.user, transition_name=transition ) history = self.StateManager.get_state_history(self.task) # Should have 3 state records assert len(history) == 3 # Should be ordered by most recent first (UUID7 ordering) states = [h.state for h in history] assert states == ['COMPLETED', 'IN_PROGRESS', 'CREATED'] # Check previous states are set correctly assert history[2].previous_state is None # First state has no previous assert history[1].previous_state == 'CREATED' assert history[0].previous_state == 'IN_PROGRESS' @patch('fsm.state_manager.flag_set') def test_get_states_in_time_range(self, mock_flag_set): """Test time-based state queries using StateManager""" from django.core.cache import cache cache.clear() # Enable FSM feature flag mock_flag_set.return_value = True # Create some states self.StateManager.transition_state(entity=self.task, new_state='CREATED', user=self.user) self.StateManager.transition_state(entity=self.task, new_state='IN_PROGRESS', user=self.user) # Get state history (which gives us states ordered by time) states = self.StateManager.get_state_history(self.task) # Should have at least the states we created assert len(states) >= 2 @patch('fsm.state_manager.flag_set') def test_immediate_cache_update_success_case(self, mock_flag_set): """Test that cache is updated immediately on successful transitions""" from django.core.cache import cache cache.clear() # Enable FSM feature flag mock_flag_set.return_value = True # Perform a successful transition success = self.StateManager.transition_state( entity=self.task, new_state='CREATED', user=self.user, transition_name='create_task', reason='Initial task creation', ) # Verify success and immediate cache update assert success current_state = self.StateManager.get_current_state_value(self.task) assert current_state == 'CREATED' # Perform another successful transition success = self.StateManager.transition_state( entity=self.task, new_state='IN_PROGRESS', user=self.user, transition_name='start_work', ) assert success current_state = self.StateManager.get_current_state_value(self.task) assert current_state == 'IN_PROGRESS' @patch('fsm.state_manager.flag_set') def test_transaction_on_commit_success_case(self, mock_flag_set): """Test successful state transitions""" from django.core.cache import cache cache.clear() # Enable FSM feature flag mock_flag_set.return_value = True # Perform a successful transition success = self.StateManager.transition_state( entity=self.task, new_state='CREATED', user=self.user, transition_name='create_task', ) # Verify transition succeeded assert success assert self.StateManager.get_current_state_value(self.task) == 'CREATED' @patch('fsm.state_manager.flag_set') def test_transaction_on_commit_database_failure_case(self, mock_flag_set): """Test state transitions work correctly""" from django.core.cache import cache cache.clear() # Enable FSM feature flag mock_flag_set.return_value = True # Perform a transition self.StateManager.transition_state( entity=self.task, new_state='CREATED', user=self.user, transition_name='create_task', ) # Verify state was set correctly current_state = self.StateManager.get_current_state_value(self.task) assert current_state == 'CREATED' @patch('fsm.state_manager.flag_set') def test_immediate_cache_update_content(self, mock_flag_set): """Test that cache is immediately updated during transition""" from django.core.cache import cache cache.clear() # Enable FSM feature flag mock_flag_set.return_value = True # Perform a transition success = self.StateManager.transition_state( entity=self.task, new_state='CREATED', user=self.user, ) assert success # Cache should be immediately updated during transition cache_key = self.StateManager.get_cache_key(self.task) cached_state = cache.get(cache_key) assert cached_state == 'CREATED' # Verify get_current_state_value uses the cached value current_state = self.StateManager.get_current_state_value(self.task) assert current_state == 'CREATED' @patch('fsm.state_manager.flag_set') def test_cache_cleanup_on_transaction_rollback(self, mock_flag_set): """Test cache behavior with state transitions""" from django.core.cache import cache cache.clear() # Enable FSM feature flag mock_flag_set.return_value = True # Create a state transition self.StateManager.transition_state( entity=self.task, new_state='CREATED', user=self.user, transition_name='create_task', reason='Test transition', ) # Verify cache contains the state cache_key = self.StateManager.get_cache_key(self.task) cached_state = cache.get(cache_key) assert cached_state == 'CREATED' @patch('fsm.state_manager.flag_set') def test_same_state_transition_prevention(self, mock_flag_set): """Test that same-state transitions are prevented""" from django.core.cache import cache cache.clear() # Enable FSM feature flag mock_flag_set.return_value = True # Create initial state success = self.StateManager.transition_state( entity=self.task, new_state='CREATED', user=self.user, ) assert success # Verify initial state is set current_state = self.StateManager.get_current_state_value(self.task) assert current_state == 'CREATED' # Get initial state count from fsm.state_models import TaskState initial_count = TaskState.objects.filter(task=self.task).count() assert initial_count == 1 # Attempt same-state transition (should be skipped) success = self.StateManager.transition_state( entity=self.task, new_state='CREATED', # Same state as current user=self.user, reason='This should be skipped', ) assert success # Returns True but doesn't create new record # Verify no new state record was created final_count = TaskState.objects.filter(task=self.task).count() assert final_count == initial_count # Should still be 1 # Verify state is still CREATED current_state = self.StateManager.get_current_state_value(self.task) assert current_state == 'CREATED'