Upload
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
import pytest
|
||||
|
||||
from bolt_pet.state import InvalidTransition, PetState, PetStateMachine
|
||||
|
||||
|
||||
def test_starts_idle():
|
||||
sm = PetStateMachine()
|
||||
assert sm.state == PetState.IDLE
|
||||
|
||||
|
||||
def test_happy_path_transitions():
|
||||
sm = PetStateMachine()
|
||||
sm.transition(PetState.LISTENING)
|
||||
sm.transition(PetState.THINKING)
|
||||
sm.transition(PetState.TALKING)
|
||||
sm.transition(PetState.IDLE)
|
||||
assert sm.state == PetState.IDLE
|
||||
|
||||
|
||||
def test_idle_to_talking_is_allowed_for_proactive_announcements():
|
||||
# The heartbeat poll can make the pet speak unprompted (a reminder
|
||||
# firing, a nudge from the server) with no preceding listen/think leg.
|
||||
sm = PetStateMachine()
|
||||
sm.transition(PetState.TALKING)
|
||||
assert sm.state == PetState.TALKING
|
||||
|
||||
|
||||
def test_invalid_transition_raises():
|
||||
sm = PetStateMachine()
|
||||
with pytest.raises(InvalidTransition):
|
||||
sm.transition(PetState.THINKING) # can't skip straight to thinking with no utterance
|
||||
|
||||
|
||||
def test_same_state_transition_is_a_noop():
|
||||
calls = []
|
||||
sm = PetStateMachine(on_change=lambda old, new: calls.append((old, new)))
|
||||
sm.transition(PetState.IDLE) # already idle
|
||||
assert calls == []
|
||||
|
||||
|
||||
def test_on_change_callback_fires_with_old_and_new():
|
||||
calls = []
|
||||
sm = PetStateMachine(on_change=lambda old, new: calls.append((old, new)))
|
||||
sm.transition(PetState.LISTENING)
|
||||
assert calls == [(PetState.IDLE, PetState.LISTENING)]
|
||||
|
||||
|
||||
def test_every_state_can_reach_error_and_recover():
|
||||
for path in (
|
||||
[PetState.LISTENING],
|
||||
[PetState.LISTENING, PetState.THINKING],
|
||||
[PetState.LISTENING, PetState.THINKING, PetState.TALKING],
|
||||
):
|
||||
sm = PetStateMachine()
|
||||
for step in path:
|
||||
sm.transition(step)
|
||||
sm.transition(PetState.ERROR)
|
||||
sm.transition(PetState.IDLE)
|
||||
assert sm.state == PetState.IDLE
|
||||
|
||||
|
||||
def test_force_recovers_from_talking_directly_to_idle_without_validation():
|
||||
sm = PetStateMachine()
|
||||
sm.transition(PetState.LISTENING)
|
||||
sm.transition(PetState.THINKING)
|
||||
sm.transition(PetState.TALKING)
|
||||
sm.force(PetState.LISTENING) # not in TALKING's allowed set, but force skips the check
|
||||
assert sm.state == PetState.LISTENING
|
||||
Reference in New Issue
Block a user