test_core_states.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. """
  2. Unit tests for pipeline state management.
  3. Tests cover state definitions, transition rules, and validation functions.
  4. """
  5. import pytest
  6. from src.core.states import PipelineState
  7. from src.core.transitions import (
  8. is_transition_allowed,
  9. get_allowed_transitions,
  10. can_pause_from,
  11. can_resume_to,
  12. ALLOWED_TRANSITIONS,
  13. )
  14. class TestPipelineState:
  15. """Test PipelineState enum functionality."""
  16. def test_all_states_defined(self):
  17. """Test that all required states are defined."""
  18. required_states = {
  19. "IDLE",
  20. "FINGERPRINTING",
  21. "CLEANING",
  22. "TERM_EXTRACTION",
  23. "TRANSLATING",
  24. "UPLOADING",
  25. "PAUSED",
  26. "COMPLETED",
  27. "FAILED",
  28. }
  29. actual_states = {state.name for state in PipelineState}
  30. assert actual_states == required_states
  31. def test_state_values(self):
  32. """Test that state values are correct."""
  33. assert PipelineState.IDLE.value == "idle"
  34. assert PipelineState.FINGERPRINTING.value == "fingerprinting"
  35. assert PipelineState.CLEANING.value == "cleaning"
  36. assert PipelineState.TERM_EXTRACTION.value == "term_extraction"
  37. assert PipelineState.TRANSLATING.value == "translating"
  38. assert PipelineState.UPLOADING.value == "uploading"
  39. assert PipelineState.PAUSED.value == "paused"
  40. assert PipelineState.COMPLETED.value == "completed"
  41. assert PipelineState.FAILED.value == "failed"
  42. def test_is_terminal(self):
  43. """Test terminal state detection."""
  44. assert PipelineState.COMPLETED.is_terminal() is True
  45. assert PipelineState.FAILED.is_terminal() is True
  46. assert PipelineState.IDLE.is_terminal() is False
  47. assert PipelineState.TRANSLATING.is_terminal() is False
  48. def test_is_active(self):
  49. """Test active state detection."""
  50. active_states = {
  51. PipelineState.FINGERPRINTING,
  52. PipelineState.CLEANING,
  53. PipelineState.TERM_EXTRACTION,
  54. PipelineState.TRANSLATING,
  55. PipelineState.UPLOADING,
  56. }
  57. for state in active_states:
  58. assert state.is_active() is True
  59. inactive_states = {
  60. PipelineState.IDLE,
  61. PipelineState.PAUSED,
  62. PipelineState.COMPLETED,
  63. PipelineState.FAILED,
  64. }
  65. for state in inactive_states:
  66. assert state.is_active() is False
  67. def test_can_pause(self):
  68. """Test states from which pausing is allowed."""
  69. assert PipelineState.TRANSLATING.can_pause() is True
  70. assert PipelineState.CLEANING.can_pause() is True
  71. assert PipelineState.IDLE.can_pause() is False
  72. assert PipelineState.COMPLETED.can_pause() is False
  73. def test_can_resume(self):
  74. """Test states from which resuming is allowed."""
  75. assert PipelineState.PAUSED.can_resume() is True
  76. assert PipelineState.IDLE.can_resume() is False
  77. assert PipelineState.TRANSLATING.can_resume() is False
  78. class TestStateTransitions:
  79. """Test state transition rules."""
  80. def test_idle_to_fingerprinting(self):
  81. """Test IDLE -> FINGERPRINTING transition."""
  82. assert is_transition_allowed(PipelineState.IDLE, PipelineState.FINGERPRINTING) is True
  83. def test_idle_to_other_states(self):
  84. """Test IDLE cannot transition to most states."""
  85. assert is_transition_allowed(PipelineState.IDLE, PipelineState.COMPLETED) is False
  86. assert is_transition_allowed(PipelineState.IDLE, PipelineState.TRANSLATING) is False
  87. assert is_transition_allowed(PipelineState.IDLE, PipelineState.FAILED) is False
  88. def test_normal_flow(self):
  89. """Test normal forward flow through the pipeline."""
  90. flow = [
  91. (PipelineState.IDLE, PipelineState.FINGERPRINTING),
  92. (PipelineState.FINGERPRINTING, PipelineState.CLEANING),
  93. (PipelineState.CLEANING, PipelineState.TERM_EXTRACTION),
  94. (PipelineState.TERM_EXTRACTION, PipelineState.TRANSLATING),
  95. (PipelineState.TRANSLATING, PipelineState.UPLOADING),
  96. (PipelineState.UPLOADING, PipelineState.COMPLETED),
  97. ]
  98. for from_state, to_state in flow:
  99. assert is_transition_allowed(from_state, to_state) is True, \
  100. f"Failed: {from_state} -> {to_state}"
  101. def test_failure_transitions(self):
  102. """Test transitions to FAILED state."""
  103. failure_sources = {
  104. PipelineState.FINGERPRINTING,
  105. PipelineState.CLEANING,
  106. PipelineState.TERM_EXTRACTION,
  107. PipelineState.TRANSLATING,
  108. PipelineState.UPLOADING,
  109. }
  110. for state in failure_sources:
  111. assert is_transition_allowed(state, PipelineState.FAILED) is True
  112. def test_pause_to_resume(self):
  113. """Test PAUSED can resume to active states."""
  114. assert is_transition_allowed(PipelineState.PAUSED, PipelineState.FINGERPRINTING) is True
  115. assert is_transition_allowed(PipelineState.PAUSED, PipelineState.TRANSLATING) is True
  116. assert is_transition_allowed(PipelineState.PAUSED, PipelineState.UPLOADING) is True
  117. def test_failed_to_idle(self):
  118. """Test FAILED can reset to IDLE."""
  119. assert is_transition_allowed(PipelineState.FAILED, PipelineState.IDLE) is True
  120. def test_completed_to_idle(self):
  121. """Test COMPLETED can reset to IDLE."""
  122. assert is_transition_allowed(PipelineState.COMPLETED, PipelineState.IDLE) is True
  123. def test_invalid_transitions(self):
  124. """Test some invalid transitions."""
  125. # Cannot skip states
  126. assert is_transition_allowed(PipelineState.IDLE, PipelineState.TRANSLATING) is False
  127. # Cannot go backwards (except via FAILED/COMPLETED -> IDLE)
  128. assert is_transition_allowed(PipelineState.TRANSLATING, PipelineState.CLEANING) is False
  129. # Cannot go from COMPLETED to active states directly
  130. assert is_transition_allowed(PipelineState.COMPLETED, PipelineState.TRANSLATING) is False
  131. class TestTransitionHelpers:
  132. """Test helper functions for state transitions."""
  133. def test_get_allowed_transitions(self):
  134. """Test getting allowed transitions from a state."""
  135. allowed = get_allowed_transitions(PipelineState.IDLE)
  136. assert allowed == {PipelineState.FINGERPRINTING}
  137. allowed = get_allowed_transitions(PipelineState.TRANSLATING)
  138. assert PipelineState.UPLOADING in allowed
  139. assert PipelineState.FAILED in allowed
  140. assert PipelineState.CLEANING not in allowed
  141. def test_can_pause_from(self):
  142. """Test can_pause_from helper."""
  143. assert can_pause_from(PipelineState.TRANSLATING) is True
  144. assert can_pause_from(PipelineState.UPLOADING) is True
  145. assert can_pause_from(PipelineState.IDLE) is False
  146. assert can_pause_from(PipelineState.COMPLETED) is False
  147. def test_can_resume_to(self):
  148. """Test can_resume_to helper."""
  149. assert can_resume_to(PipelineState.TRANSLATING) is True
  150. assert can_resume_to(PipelineState.CLEANING) is True
  151. assert can_resume_to(PipelineState.COMPLETED) is False
  152. assert can_resume_to(PipelineState.FAILED) is False
  153. def test_allowed_transitions_completeness(self):
  154. """Test that all states have transition rules defined."""
  155. all_states = set(PipelineState)
  156. defined_states = set(ALLOWED_TRANSITIONS.keys())
  157. assert all_states == defined_states, \
  158. f"Missing transitions for: {all_states - defined_states}"
  159. class TestTransitionEdgeCases:
  160. """Test edge cases in state transitions."""
  161. def test_same_state_transition(self):
  162. """Test that staying in same state is not a transition."""
  163. # Transitions to same state should not be explicitly allowed
  164. # (they're handled as "no transition needed")
  165. result = is_transition_allowed(PipelineState.IDLE, PipelineState.IDLE)
  166. # This should be False since we didn't define it
  167. assert result is False
  168. def test_all_states_can_reach_terminal(self):
  169. """Test that all active states can reach a terminal state."""
  170. active_states = {
  171. PipelineState.FINGERPRINTING,
  172. PipelineState.CLEANING,
  173. PipelineState.TERM_EXTRACTION,
  174. PipelineState.TRANSLATING,
  175. PipelineState.UPLOADING,
  176. }
  177. for state in active_states:
  178. # Can reach FAILED
  179. assert is_transition_allowed(state, PipelineState.FAILED), \
  180. f"{state} cannot reach FAILED"
  181. # Can reach COMPLETED via normal flow
  182. assert state == PipelineState.UPLOADING or \
  183. any(is_transition_allowed(state, s) for s in PipelineState), \
  184. f"{state} cannot progress"