teamtalk 6.0.0

TeamTalk SDK for Rust
Documentation
#![cfg(feature = "bot")]

use std::time::Duration;
use teamtalk::{
    DialogFlow, DialogMachine, DialogState, DialogStatus, DialogTimeoutPolicy, MemoryStateStore,
};

#[test]
fn dialog_machine_roundtrip() {
    let mut store = MemoryStateStore::new();
    let mut fsm = DialogMachine::new(&mut store);

    fsm.start(42, "onboarding", "ask_name");
    let cur = fsm.current(42).expect("state exists");
    assert_eq!(cur.dialog, "onboarding");
    assert_eq!(cur.step, "ask_name");

    let next = fsm.advance(42, "ask_email").expect("advance state");
    assert_eq!(next.step, "ask_email");

    let stopped = fsm.stop(42).expect("stop state");
    assert_eq!(stopped.dialog, "onboarding");
    assert!(fsm.current(42).is_none());
}

#[test]
fn dialog_flow_contains_start_and_steps() {
    let flow = DialogFlow::new("onboarding", "ask_name")
        .step("ask_email")
        .step("done");
    assert_eq!(flow.name(), "onboarding");
    assert_eq!(flow.start_step(), "ask_name");
    assert!(flow.contains_step("ask_name"));
    assert!(flow.contains_step("ask_email"));
    assert!(!flow.contains_step("missing"));
    assert_eq!(flow.next_step("ask_name"), Some("ask_email"));
    assert_eq!(flow.next_step("ask_email"), Some("done"));
    assert_eq!(flow.next_step("done"), None);
    assert_eq!(flow.previous_step("ask_email"), Some("ask_name"));
    assert_eq!(flow.previous_step("done"), Some("ask_email"));
    assert!(flow.is_start_step("ask_name"));
    assert!(flow.is_terminal_step("done"));
}

#[test]
fn dialog_state_decodes_legacy_format() {
    let state = DialogState::decode("legacy|step1").expect("legacy state");
    assert_eq!(state.dialog, "legacy");
    assert_eq!(state.step, "step1");
    assert_eq!(state.status, DialogStatus::Active);
    assert_eq!(state.deadline_unix_ms, None);
    assert!(state.metadata.is_empty());
}

#[test]
fn dialog_machine_preserves_metadata_and_timeout_state() {
    let mut store = MemoryStateStore::new();
    let mut fsm = DialogMachine::new(&mut store);

    let state = DialogState::new("wizard", "ask_name")
        .with_timeout(Duration::from_secs(30))
        .with_metadata([("locale", "ru"), ("mode", "guided")]);
    fsm.start_state(7, state);

    let cur = fsm.current_live(7).expect("live state exists");
    assert_eq!(cur.metadata("locale"), Some("ru"));
    assert_eq!(cur.metadata("mode"), Some("guided"));
    assert!(cur.deadline_unix_ms.is_some());
    assert!(cur.session_id().is_some());

    let updated = fsm
        .set_metadata(7, "locale", "en")
        .expect("metadata updated");
    assert_eq!(updated.metadata("locale"), Some("en"));

    let (updated, removed) = fsm.remove_metadata(7, "mode").expect("metadata removed");
    assert_eq!(removed.as_deref(), Some("guided"));
    assert_eq!(updated.metadata("mode"), None);
}

#[test]
fn dialog_machine_pause_resume_and_active_state() {
    let mut store = MemoryStateStore::new();
    let mut fsm = DialogMachine::new(&mut store);

    fsm.start(9, "wizard", "step1");
    let paused = fsm.pause(9).expect("paused");
    assert_eq!(paused.status, DialogStatus::Paused);
    assert!(fsm.current_active(9).is_none());
    assert!(fsm.current_live(9).is_some());

    let resumed = fsm.resume(9).expect("resumed");
    assert_eq!(resumed.status, DialogStatus::Active);
    assert_eq!(fsm.current_active(9).expect("active").step, "step1");
}

#[test]
fn dialog_machine_removes_expired_state_from_live_queries() {
    let mut store = MemoryStateStore::new();
    let mut fsm = DialogMachine::new(&mut store);

    let expired = DialogState::new("wizard", "step1")
        .with_deadline_unix_ms(1)
        .with_metadata([("key", "value")]);
    fsm.start_state(11, expired);

    assert!(fsm.current_live(11).is_none());
    assert!(fsm.current(11).is_none());
}

#[test]
fn dialog_machine_pauses_when_timeout_policy_requests_pause() {
    let mut store = MemoryStateStore::new();
    let mut fsm = DialogMachine::new(&mut store);

    let state = DialogState::new("wizard", "step1")
        .with_deadline_unix_ms(1)
        .with_timeout_policy(DialogTimeoutPolicy::Pause);
    fsm.start_state(12, state);

    let paused = fsm.current_live(12).expect("paused state kept");
    assert_eq!(paused.status, DialogStatus::Paused);
    assert_eq!(paused.timeout_policy(), DialogTimeoutPolicy::Pause);
    assert_eq!(paused.deadline_unix_ms, None);
    assert!(fsm.current_active(12).is_none());
}

#[test]
fn dialog_machine_advances_flow_in_order() {
    let mut store = MemoryStateStore::new();
    let mut fsm = DialogMachine::new(&mut store);
    let flow = DialogFlow::new("wizard", "ask_name")
        .step("ask_email")
        .step("done");

    let restarted = fsm.restart_flow(21, &flow);
    assert_eq!(restarted.dialog, "wizard");
    assert_eq!(restarted.step, "ask_name");

    let next = fsm.advance_flow(21, &flow).expect("advance to ask_email");
    assert_eq!(next.step, "ask_email");

    let next = fsm.advance_flow(21, &flow).expect("advance to done");
    assert_eq!(next.step, "done");

    assert!(fsm.advance_flow(21, &flow).is_none());
}

#[test]
fn dialog_machine_restart_flow_rotates_session_id() {
    let mut store = MemoryStateStore::new();
    let mut fsm = DialogMachine::new(&mut store);
    let flow = DialogFlow::new("wizard", "ask_name").step("done");

    fsm.start_state(30, DialogState::new("wizard", "ask_name"));
    let first = fsm
        .current(30)
        .and_then(|state| state.session_id().map(str::to_owned));

    let restarted = fsm.restart_flow(30, &flow);
    assert_eq!(restarted.step, "ask_name");
    let second = restarted.session_id().map(str::to_owned);

    assert!(first.is_some());
    assert!(second.is_some());
    assert_ne!(first, second);
}