Files
site_aegisone/max_bot/tests/test_fsm.py
T

169 lines
7.0 KiB
Python

from app.fsm import BotState, FSM, STATE_TRANSITIONS
class TestFSM:
def test_initial_state(self):
FSM.clear_all()
assert FSM.get_state(1) == BotState.IDLE
def test_set_and_get_state(self):
FSM.clear_all()
FSM.set_state(1, BotState.NAME_REQUEST)
assert FSM.get_state(1) == BotState.NAME_REQUEST
def test_reset(self):
FSM.clear_all()
FSM.set_state(1, BotState.NAME_REQUEST)
FSM.reset(1)
assert FSM.get_state(1) == BotState.IDLE
def test_valid_transition(self):
FSM.clear_all()
result = FSM.transition(1, "start")
assert result == BotState.NAME_REQUEST
def test_invalid_transition_returns_same(self):
FSM.clear_all()
# From IDLE, "unknown" should keep us at IDLE
result = FSM.transition(1, "unknown_event")
assert result == BotState.IDLE
def test_can_transition_true(self):
FSM.clear_all()
assert FSM.can_transition(1, "start") is True
def test_can_transition_false(self):
FSM.clear_all()
assert FSM.can_transition(1, "unknown_event") is False
def test_full_greeting_flow(self):
FSM.clear_all()
assert FSM.transition(1, "start") == BotState.NAME_REQUEST
assert FSM.transition(1, "name_provided") == BotState.CONSENT_REQUEST
assert FSM.transition(1, "consent_given") == BotState.CONTACT_REQUEST
assert FSM.transition(1, "contact_provided") == BotState.INQUIRY_REQUEST
assert FSM.transition(1, "inquiry_provided") == BotState.CATEGORY_SELECT
assert FSM.transition(1, "category_selected") == BotState.CONFIRMATION
assert FSM.transition(1, "done") == BotState.IDLE
def test_consent_refused_flow(self):
FSM.clear_all()
FSM.set_state(1, BotState.CONSENT_REQUEST)
assert FSM.transition(1, "consent_refused") == BotState.IDLE
def test_handoff_flow(self):
FSM.clear_all()
FSM.set_state(1, BotState.HANDOFF_REQUEST)
assert FSM.transition(1, "confirmed") == BotState.HANDOFF_CONFIRMATION
assert FSM.transition(1, "done") == BotState.IDLE
def test_handoff_cancel_to_menu(self):
FSM.clear_all()
FSM.set_state(1, BotState.HANDOFF_REQUEST)
assert FSM.transition(1, "cancel") == BotState.MAIN_MENU
def test_main_menu_new_inquiry(self):
FSM.clear_all()
FSM.set_state(1, BotState.MAIN_MENU)
assert FSM.transition(1, "new_inquiry") == BotState.NAME_REQUEST
def test_main_menu_features(self):
FSM.clear_all()
FSM.set_state(1, BotState.MAIN_MENU)
assert FSM.transition(1, "feature_risk_score") == BotState.FEATURE_RISK_SCORE
FSM.set_state(1, BotState.MAIN_MENU)
assert FSM.transition(1, "feature_sla_status") == BotState.FEATURE_SLA_STATUS
FSM.set_state(1, BotState.MAIN_MENU)
assert FSM.transition(1, "feature_photo_report") == BotState.FEATURE_PHOTO_REPORT
FSM.set_state(1, BotState.MAIN_MENU)
assert FSM.transition(1, "feature_appointment") == BotState.FEATURE_APPOINTMENT
FSM.set_state(1, BotState.MAIN_MENU)
assert FSM.transition(1, "feature_quality_rate") == BotState.FEATURE_QUALITY_RATE
FSM.set_state(1, BotState.MAIN_MENU)
assert FSM.transition(1, "feature_commercial") == BotState.FEATURE_COMMERCIAL
FSM.set_state(1, BotState.MAIN_MENU)
assert FSM.transition(1, "feature_my_objects") == BotState.FEATURE_MY_OBJECTS
FSM.set_state(1, BotState.MAIN_MENU)
assert FSM.transition(1, "sla_ticket_create") == BotState.SLA_TICKET_CREATE
def test_feature_done_returns_to_menu(self):
FSM.clear_all()
FSM.set_state(1, BotState.FEATURE_RISK_SCORE)
assert FSM.transition(1, "done") == BotState.MAIN_MENU
FSM.set_state(1, BotState.FEATURE_RISK_SCORE)
assert FSM.transition(1, "cancel") == BotState.MAIN_MENU
def test_out_of_hours_flow(self):
FSM.clear_all()
FSM.set_state(1, BotState.OUT_OF_HOURS)
assert FSM.transition(1, "leave_message") == BotState.INQUIRY_REQUEST
FSM.set_state(1, BotState.OUT_OF_HOURS)
assert FSM.transition(1, "callback") == BotState.NAME_REQUEST
FSM.set_state(1, BotState.OUT_OF_HOURS)
assert FSM.transition(1, "cancel") == BotState.IDLE
FSM.set_state(1, BotState.OUT_OF_HOURS)
assert FSM.transition(1, "main_menu") == BotState.MAIN_MENU
def test_category_select_flow(self):
FSM.clear_all()
FSM.set_state(1, BotState.CATEGORY_SELECT)
assert FSM.transition(1, "category_selected") == BotState.CONFIRMATION
FSM.set_state(1, BotState.CATEGORY_SELECT)
assert FSM.transition(1, "cancel") == BotState.IDLE
def test_kb_search_found(self):
FSM.clear_all()
FSM.set_state(1, BotState.KB_SEARCH)
assert FSM.transition(1, "found") == BotState.MAIN_MENU
FSM.set_state(1, BotState.KB_SEARCH)
assert FSM.transition(1, "not_found") == BotState.INQUIRY_REQUEST
def test_waiting_1c_response(self):
FSM.clear_all()
FSM.set_state(1, BotState.WAITING_1C_RESPONSE)
assert FSM.transition(1, "received") == BotState.MAIN_MENU
FSM.set_state(1, BotState.WAITING_1C_RESPONSE)
assert FSM.transition(1, "timeout") == BotState.IDLE
def test_rating_flow(self):
FSM.clear_all()
FSM.set_state(1, BotState.AWAITING_RATING)
assert FSM.transition(1, "rating_provided") == BotState.IDLE
def test_new_user_start_via_callback(self):
FSM.clear_all()
# Simulate callback: from MAIN_MENU, cancel goes to IDLE
FSM.set_state(1, BotState.MAIN_MENU)
assert FSM.transition(1, "cancel") == BotState.IDLE
def test_idle_handoff_event(self):
FSM.clear_all()
assert FSM.transition(1, "handoff") == BotState.HANDOFF_REQUEST
def test_edit_from_confirmation(self):
FSM.clear_all()
FSM.set_state(1, BotState.CONFIRMATION)
assert FSM.transition(1, "edit_name") == BotState.NAME_REQUEST
FSM.set_state(1, BotState.CONFIRMATION)
assert FSM.transition(1, "edit_contact") == BotState.CONTACT_REQUEST
FSM.set_state(1, BotState.CONFIRMATION)
assert FSM.transition(1, "edit_inquiry") == BotState.INQUIRY_REQUEST
def test_clear_all(self):
FSM.clear_all()
FSM.set_state(1, BotState.NAME_REQUEST)
FSM.set_state(2, BotState.CONSENT_REQUEST)
FSM.clear_all()
assert FSM.get_state(1) == BotState.IDLE
assert FSM.get_state(2) == BotState.IDLE
def test_all_states_have_transitions(self):
for state in BotState:
assert state in STATE_TRANSITIONS, f"{state} missing from STATE_TRANSITIONS"
def test_all_transitions_go_to_valid_states(self):
for state, events in STATE_TRANSITIONS.items():
for event, target in events.items():
assert target in BotState.__members__.values(), \
f"{state}.{event} -> {target} is not a valid BotState"