from __future__ import annotations import pytest from genesis.state_machine import StateTransitionError, SessionStateMachine # ---------- 正常流转 ---------- def test_initial_state_is_uploading(): sm = SessionStateMachine() assert sm.state == "uploading" def test_unknown_initial_state_rejected(): with pytest.raises(StateTransitionError): SessionStateMachine(initial="bogus") def test_unknown_target_state_rejected(): sm = SessionStateMachine() with pytest.raises(StateTransitionError): sm.transition("bogus") def test_happy_path_full_flow(): sm = SessionStateMachine() sm.transition("parsing") sm.transition("awaiting_parse_confirm") sm.transition("impact_running") sm.transition("awaiting_impact_confirm") sm.transition("writing") sm.transition("qa") sm.transition("done") assert sm.state == "done" # ---------- 非法转移拒绝 ---------- def test_illegal_transition_rejected(): sm = SessionStateMachine() with pytest.raises(StateTransitionError): sm.transition("writing") # uploading → writing 非法 def test_done_is_terminal_no_transition_out(): sm = SessionStateMachine() sm.transition("parsing") sm.transition("awaiting_parse_confirm") sm.transition("impact_running") sm.transition("awaiting_impact_confirm") sm.transition("writing") sm.transition("qa") sm.transition("done") with pytest.raises(StateTransitionError): sm.transition("writing") # ---------- T3: cancelled + resume ---------- def test_cancel_from_running_state(): sm = SessionStateMachine() sm.transition("parsing") sm.transition("awaiting_parse_confirm") sm.transition("impact_running") sm.transition("awaiting_impact_confirm") sm.transition("writing") sm.cancel() assert sm.state == "cancelled" assert sm.cancelled_from == "writing" def test_cancel_from_done_is_illegal(): sm = SessionStateMachine() sm.transition("parsing") sm.transition("awaiting_parse_confirm") sm.transition("impact_running") sm.transition("awaiting_impact_confirm") sm.transition("writing") sm.transition("qa") sm.transition("done") with pytest.raises(StateTransitionError): sm.cancel() def test_resume_returns_to_interrupted_state(): sm = SessionStateMachine() sm.transition("parsing") sm.transition("awaiting_parse_confirm") sm.transition("impact_running") sm.transition("awaiting_impact_confirm") sm.transition("writing") sm.cancel() sm.resume() assert sm.state == "writing" def test_resume_without_cancel_is_illegal(): sm = SessionStateMachine() sm.transition("parsing") with pytest.raises(StateTransitionError): sm.resume() # 非 cancelled 状态不能 resume def test_cancelled_cannot_jump_to_arbitrary_state(): sm = SessionStateMachine() sm.transition("parsing") sm.cancel() with pytest.raises(StateTransitionError): sm.transition("done") # cancelled 只能 resume 回中断前状态 def test_resume_after_resume_allows_forward_progress(): # 取消 → resume → 再次取消 → resume 循环稳定(cancelled_from 保持最后一次中断点) sm = SessionStateMachine() sm.transition("parsing") sm.transition("awaiting_parse_confirm") sm.transition("impact_running") sm.cancel() sm.resume() assert sm.state == "impact_running" sm.transition("awaiting_impact_confirm") sm.transition("writing") sm.cancel() assert sm.cancelled_from == "writing" sm.resume() assert sm.state == "writing"