test_state_machine.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. """
  2. Unit tests for the state machine module.
  3. Tests cover all state transitions, persistence, and recovery scenarios.
  4. """
  5. import pytest
  6. from datetime import datetime
  7. from src.pipeline.state_machine import PipelineStateMachine, PipelineState
  8. class TestPipelineStateMachine:
  9. """Test cases for PipelineStateMachine."""
  10. def test_initial_state_is_idle(self):
  11. """Test that the machine starts in IDLE state."""
  12. machine = PipelineStateMachine()
  13. assert machine.state == PipelineState.IDLE
  14. def test_valid_transition_idle_to_preparing(self):
  15. """Test valid transition from IDLE to PREPARING."""
  16. machine = PipelineStateMachine()
  17. assert machine.transition_to(PipelineState.PREPARING)
  18. assert machine.state == PipelineState.PREPARING
  19. def test_valid_transition_preparing_to_cleaning(self):
  20. """Test valid transition from PREPARING to CLEANING."""
  21. machine = PipelineStateMachine()
  22. machine.transition_to(PipelineState.PREPARING)
  23. assert machine.transition_to(PipelineState.CLEANING)
  24. assert machine.state == PipelineState.CLEANING
  25. def test_valid_transition_cleaning_to_translating(self):
  26. """Test valid transition from CLEANING to TRANSLATING."""
  27. machine = PipelineStateMachine()
  28. machine.transition_to(PipelineState.PREPARING)
  29. machine.transition_to(PipelineState.CLEANING)
  30. assert machine.transition_to(PipelineState.TRANSLATING)
  31. assert machine.state == PipelineState.TRANSLATING
  32. def test_valid_transition_translating_to_uploading(self):
  33. """Test valid transition from TRANSLATING to UPLOADING."""
  34. machine = PipelineStateMachine()
  35. for state in [
  36. PipelineState.PREPARING,
  37. PipelineState.CLEANING,
  38. PipelineState.TRANSLATING,
  39. ]:
  40. machine.transition_to(state)
  41. assert machine.transition_to(PipelineState.UPLOADING)
  42. assert machine.state == PipelineState.UPLOADING
  43. def test_valid_transition_uploading_to_completed(self):
  44. """Test valid transition from UPLOADING to COMPLETED."""
  45. machine = PipelineStateMachine()
  46. for state in [
  47. PipelineState.PREPARING,
  48. PipelineState.CLEANING,
  49. PipelineState.TRANSLATING,
  50. PipelineState.UPLOADING,
  51. ]:
  52. machine.transition_to(state)
  53. assert machine.transition_to(PipelineState.COMPLETED)
  54. assert machine.state == PipelineState.COMPLETED
  55. def test_invalid_transition_rejected(self):
  56. """Test that invalid transitions are rejected."""
  57. machine = PipelineStateMachine()
  58. # Can't skip from IDLE to TRANSLATING
  59. assert not machine.transition_to(PipelineState.TRANSLATING)
  60. assert machine.state == PipelineState.IDLE
  61. def test_pause_from_any_active_state(self):
  62. """Test pausing from any active state."""
  63. active_states = [
  64. PipelineState.PREPARING,
  65. PipelineState.CLEANING,
  66. PipelineState.TRANSLATING,
  67. PipelineState.UPLOADING,
  68. ]
  69. for state in active_states:
  70. machine = PipelineStateMachine()
  71. machine.transition_to(PipelineState.PREPARING)
  72. machine.transition_to(state)
  73. assert machine.transition_to(PipelineState.PAUSED)
  74. assert machine.state == PipelineState.PAUSED
  75. def test_resume_from_pause(self):
  76. """Test resuming from PAUSED back to active state."""
  77. machine = PipelineStateMachine()
  78. machine.transition_to(PipelineState.PREPARING)
  79. machine.transition_to(PipelineState.TRANSLATING)
  80. machine.transition_to(PipelineState.PAUSED)
  81. # Can resume to TRANSLATING
  82. assert machine.transition_to(PipelineState.TRANSLATING)
  83. assert machine.state == PipelineState.TRANSLATING
  84. def test_failed_state_only_goes_to_idle(self):
  85. """Test that FAILED state can only transition to IDLE."""
  86. machine = PipelineStateMachine()
  87. machine.transition_to(PipelineState.PREPARING)
  88. machine.transition_to(PipelineState.FAILED)
  89. # Can go to IDLE
  90. assert machine.transition_to(PipelineState.IDLE)
  91. assert machine.state == PipelineState.IDLE
  92. # Can't go directly to another state
  93. machine.transition_to(PipelineState.PREPARING)
  94. machine.transition_to(PipelineState.FAILED)
  95. assert not machine.transition_to(PipelineState.TRANSLATING)
  96. def test_completed_goes_to_idle(self):
  97. """Test that COMPLETED transitions to IDLE."""
  98. machine = PipelineStateMachine()
  99. for state in [
  100. PipelineState.PREPARING,
  101. PipelineState.CLEANING,
  102. PipelineState.TRANSLATING,
  103. PipelineState.UPLOADING,
  104. PipelineState.COMPLETED,
  105. ]:
  106. machine.transition_to(state)
  107. assert machine.transition_to(PipelineState.IDLE)
  108. assert machine.state == PipelineState.IDLE
  109. def test_state_history_tracking(self):
  110. """Test that state history is tracked."""
  111. machine = PipelineStateMachine()
  112. machine.transition_to(PipelineState.PREPARING, reason="Starting task")
  113. machine.transition_to(PipelineState.CLEANING)
  114. history = machine.get_state_history()
  115. assert len(history) == 3 # IDLE + PREPARING + CLEANING
  116. assert history[0]["state"] == "idle"
  117. assert history[1]["state"] == "preparing"
  118. assert history[1]["reason"] == "Starting task"
  119. assert history[2]["state"] == "cleaning"
  120. def test_can_transition_to_check(self):
  121. """Test the can_transition_to method."""
  122. machine = PipelineStateMachine()
  123. assert machine.can_transition_to(PipelineState.PREPARING)
  124. assert not machine.can_transition_to(PipelineState.TRANSLATING)
  125. machine.transition_to(PipelineState.PREPARING)
  126. assert machine.can_transition_to(PipelineState.CLEANING)
  127. assert not machine.can_transition_to(PipelineState.IDLE)
  128. def test_reset(self):
  129. """Test resetting the state machine."""
  130. machine = PipelineStateMachine()
  131. machine.transition_to(PipelineState.PREPARING)
  132. machine.transition_to(PipelineState.CLEANING)
  133. machine.reset()
  134. assert machine.state == PipelineState.IDLE
  135. assert len(machine.get_state_history()) == 1
  136. def test_full_workflow(self):
  137. """Test a complete workflow from start to finish."""
  138. machine = PipelineStateMachine()
  139. workflow = [
  140. PipelineState.PREPARING,
  141. PipelineState.CLEANING,
  142. PipelineState.TRANSLATING,
  143. PipelineState.UPLOADING,
  144. PipelineState.COMPLETED,
  145. ]
  146. for state in workflow:
  147. assert machine.transition_to(state), f"Failed to transition to {state}"
  148. assert machine.state == state
  149. # Should be able to start a new task
  150. assert machine.transition_to(PipelineState.IDLE)
  151. assert machine.transition_to(PipelineState.PREPARING)