169 lines
7.0 KiB
Python
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"
|