"""Job/item state-machine rules: only declared transitions are legal, and terminal states are truly terminal (no duplicate terminal transition).""" import pytest from photo_pipeline.services.jobs import ( ALLOWED_TRANSITIONS, ITEM_TRANSITIONS, TERMINAL_STATES, ItemState, JobState, can_transition, ) def test_happy_path_transitions_are_allowed(): assert can_transition(JobState.QUEUED, JobState.RUNNING) assert can_transition(JobState.RUNNING, JobState.SUCCEEDED) assert can_transition(JobState.RUNNING, JobState.FAILED) assert can_transition(JobState.FAILED, JobState.RETRY_QUEUED) assert can_transition(JobState.RETRY_QUEUED, JobState.RUNNING) assert can_transition(JobState.RUNNING, JobState.CANCELLING) assert can_transition(JobState.CANCELLING, JobState.CANCELLED) @pytest.mark.parametrize( "current,target", [ (JobState.QUEUED, JobState.SUCCEEDED), # can't skip running (JobState.SUCCEEDED, JobState.RUNNING), # terminal, no revive (JobState.CANCELLED, JobState.RUNNING), # terminal (JobState.RUNNING, JobState.QUEUED), # no backward ], ) def test_invalid_transitions_are_rejected(current, target): assert not can_transition(current, target) def test_terminal_states_have_no_outgoing_transition(): for state in TERMINAL_STATES: assert ALLOWED_TRANSITIONS[state] == set() def test_duplicate_terminal_transition_is_rejected(): assert not can_transition(JobState.SUCCEEDED, JobState.SUCCEEDED) assert not can_transition(JobState.CANCELLED, JobState.CANCELLED) def test_item_states_are_consistent(): assert ItemState.SUCCEEDED in ITEM_TRANSITIONS[ItemState.RUNNING] assert ITEM_TRANSITIONS[ItemState.SUCCEEDED] == set() # terminal item