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