""" Unit tests for pipeline state management. Tests cover state definitions, transition rules, and validation functions. """ import pytest from src.core.states import PipelineState from src.core.transitions import ( is_transition_allowed, get_allowed_transitions, can_pause_from, can_resume_to, ALLOWED_TRANSITIONS, ) class TestPipelineState: """Test PipelineState enum functionality.""" def test_all_states_defined(self): """Test that all required states are defined.""" required_states = { "IDLE", "FINGERPRINTING", "CLEANING", "TERM_EXTRACTION", "TRANSLATING", "UPLOADING", "PAUSED", "COMPLETED", "FAILED", } actual_states = {state.name for state in PipelineState} assert actual_states == required_states def test_state_values(self): """Test that state values are correct.""" assert PipelineState.IDLE.value == "idle" assert PipelineState.FINGERPRINTING.value == "fingerprinting" assert PipelineState.CLEANING.value == "cleaning" assert PipelineState.TERM_EXTRACTION.value == "term_extraction" assert PipelineState.TRANSLATING.value == "translating" assert PipelineState.UPLOADING.value == "uploading" assert PipelineState.PAUSED.value == "paused" assert PipelineState.COMPLETED.value == "completed" assert PipelineState.FAILED.value == "failed" def test_is_terminal(self): """Test terminal state detection.""" assert PipelineState.COMPLETED.is_terminal() is True assert PipelineState.FAILED.is_terminal() is True assert PipelineState.IDLE.is_terminal() is False assert PipelineState.TRANSLATING.is_terminal() is False def test_is_active(self): """Test active state detection.""" active_states = { PipelineState.FINGERPRINTING, PipelineState.CLEANING, PipelineState.TERM_EXTRACTION, PipelineState.TRANSLATING, PipelineState.UPLOADING, } for state in active_states: assert state.is_active() is True inactive_states = { PipelineState.IDLE, PipelineState.PAUSED, PipelineState.COMPLETED, PipelineState.FAILED, } for state in inactive_states: assert state.is_active() is False def test_can_pause(self): """Test states from which pausing is allowed.""" assert PipelineState.TRANSLATING.can_pause() is True assert PipelineState.CLEANING.can_pause() is True assert PipelineState.IDLE.can_pause() is False assert PipelineState.COMPLETED.can_pause() is False def test_can_resume(self): """Test states from which resuming is allowed.""" assert PipelineState.PAUSED.can_resume() is True assert PipelineState.IDLE.can_resume() is False assert PipelineState.TRANSLATING.can_resume() is False class TestStateTransitions: """Test state transition rules.""" def test_idle_to_fingerprinting(self): """Test IDLE -> FINGERPRINTING transition.""" assert is_transition_allowed(PipelineState.IDLE, PipelineState.FINGERPRINTING) is True def test_idle_to_other_states(self): """Test IDLE cannot transition to most states.""" assert is_transition_allowed(PipelineState.IDLE, PipelineState.COMPLETED) is False assert is_transition_allowed(PipelineState.IDLE, PipelineState.TRANSLATING) is False assert is_transition_allowed(PipelineState.IDLE, PipelineState.FAILED) is False def test_normal_flow(self): """Test normal forward flow through the pipeline.""" flow = [ (PipelineState.IDLE, PipelineState.FINGERPRINTING), (PipelineState.FINGERPRINTING, PipelineState.CLEANING), (PipelineState.CLEANING, PipelineState.TERM_EXTRACTION), (PipelineState.TERM_EXTRACTION, PipelineState.TRANSLATING), (PipelineState.TRANSLATING, PipelineState.UPLOADING), (PipelineState.UPLOADING, PipelineState.COMPLETED), ] for from_state, to_state in flow: assert is_transition_allowed(from_state, to_state) is True, \ f"Failed: {from_state} -> {to_state}" def test_failure_transitions(self): """Test transitions to FAILED state.""" failure_sources = { PipelineState.FINGERPRINTING, PipelineState.CLEANING, PipelineState.TERM_EXTRACTION, PipelineState.TRANSLATING, PipelineState.UPLOADING, } for state in failure_sources: assert is_transition_allowed(state, PipelineState.FAILED) is True def test_pause_to_resume(self): """Test PAUSED can resume to active states.""" assert is_transition_allowed(PipelineState.PAUSED, PipelineState.FINGERPRINTING) is True assert is_transition_allowed(PipelineState.PAUSED, PipelineState.TRANSLATING) is True assert is_transition_allowed(PipelineState.PAUSED, PipelineState.UPLOADING) is True def test_failed_to_idle(self): """Test FAILED can reset to IDLE.""" assert is_transition_allowed(PipelineState.FAILED, PipelineState.IDLE) is True def test_completed_to_idle(self): """Test COMPLETED can reset to IDLE.""" assert is_transition_allowed(PipelineState.COMPLETED, PipelineState.IDLE) is True def test_invalid_transitions(self): """Test some invalid transitions.""" # Cannot skip states assert is_transition_allowed(PipelineState.IDLE, PipelineState.TRANSLATING) is False # Cannot go backwards (except via FAILED/COMPLETED -> IDLE) assert is_transition_allowed(PipelineState.TRANSLATING, PipelineState.CLEANING) is False # Cannot go from COMPLETED to active states directly assert is_transition_allowed(PipelineState.COMPLETED, PipelineState.TRANSLATING) is False class TestTransitionHelpers: """Test helper functions for state transitions.""" def test_get_allowed_transitions(self): """Test getting allowed transitions from a state.""" allowed = get_allowed_transitions(PipelineState.IDLE) assert allowed == {PipelineState.FINGERPRINTING} allowed = get_allowed_transitions(PipelineState.TRANSLATING) assert PipelineState.UPLOADING in allowed assert PipelineState.FAILED in allowed assert PipelineState.CLEANING not in allowed def test_can_pause_from(self): """Test can_pause_from helper.""" assert can_pause_from(PipelineState.TRANSLATING) is True assert can_pause_from(PipelineState.UPLOADING) is True assert can_pause_from(PipelineState.IDLE) is False assert can_pause_from(PipelineState.COMPLETED) is False def test_can_resume_to(self): """Test can_resume_to helper.""" assert can_resume_to(PipelineState.TRANSLATING) is True assert can_resume_to(PipelineState.CLEANING) is True assert can_resume_to(PipelineState.COMPLETED) is False assert can_resume_to(PipelineState.FAILED) is False def test_allowed_transitions_completeness(self): """Test that all states have transition rules defined.""" all_states = set(PipelineState) defined_states = set(ALLOWED_TRANSITIONS.keys()) assert all_states == defined_states, \ f"Missing transitions for: {all_states - defined_states}" class TestTransitionEdgeCases: """Test edge cases in state transitions.""" def test_same_state_transition(self): """Test that staying in same state is not a transition.""" # Transitions to same state should not be explicitly allowed # (they're handled as "no transition needed") result = is_transition_allowed(PipelineState.IDLE, PipelineState.IDLE) # This should be False since we didn't define it assert result is False def test_all_states_can_reach_terminal(self): """Test that all active states can reach a terminal state.""" active_states = { PipelineState.FINGERPRINTING, PipelineState.CLEANING, PipelineState.TERM_EXTRACTION, PipelineState.TRANSLATING, PipelineState.UPLOADING, } for state in active_states: # Can reach FAILED assert is_transition_allowed(state, PipelineState.FAILED), \ f"{state} cannot reach FAILED" # Can reach COMPLETED via normal flow assert state == PipelineState.UPLOADING or \ any(is_transition_allowed(state, s) for s in PipelineState), \ f"{state} cannot progress"