use crate::channels::telegram::flow::StreamingState;
use crate::channels::telegram::state::{LiveFlowHandle, TelegramState};
use std::sync::Arc;
use uuid::Uuid;
fn open_roll() -> std::sync::Arc<std::sync::Mutex<StreamingState>> {
Arc::new(std::sync::Mutex::new(StreamingState {
is_dm: true,
pending_suggestions: None,
pending_trailer: None,
msg_id: None,
thinking: String::new(),
tool_msgs: Vec::new(),
display_queue: Vec::new(),
open_group_msg_id: Some(teloxide::types::MessageId(1)),
rich_transport_failures: 0,
flow_entries: Vec::new(),
flow_status: None,
flow_rich: false,
response: String::new(),
final_bubble: None,
dirty: false,
recreate: false,
header_preview: None,
compacting: false,
sections: Default::default(),
retained_goal: None,
applied_plan_kb: Default::default(),
tool_round_count: 0,
tools_started_at: None,
turn_started_at: std::time::Instant::now(),
flow_outcome: None,
bg_indicator: None,
bg_count: None,
subagent_counts: Default::default(),
sent_intermediates: Vec::new(),
intermediate_msg_ids: Vec::new(),
voice_msg_ids: Vec::new(),
processing: true,
is_cli: false,
}))
}
fn handle_for(streaming: &std::sync::Arc<std::sync::Mutex<StreamingState>>) -> LiveFlowHandle {
LiveFlowHandle {
streaming: std::sync::Arc::clone(streaming),
}
}
#[test]
fn live_flow_registration_roundtrip() {
let state = std::sync::Arc::new(TelegramState::new());
let sid = Uuid::new_v4();
assert!(state.live_flow(sid).is_none(), "fresh state: no live flow");
let roll = open_roll();
let guard = state.register_live_flow(sid, handle_for(&roll));
let h = state.live_flow(sid).expect("registered handle visible");
assert!(
std::sync::Arc::ptr_eq(&h.streaming, &roll),
"registered handle carries this turn's streaming Arc"
);
state.unregister_live_flow(sid);
assert!(state.live_flow(sid).is_none(), "explicit unregister clears");
drop(guard); assert!(state.live_flow(sid).is_none());
}
#[test]
fn live_flow_guard_drop_unregisters() {
let state = std::sync::Arc::new(TelegramState::new());
let sid = Uuid::new_v4();
{
let _g = state.register_live_flow(sid, handle_for(&open_roll()));
assert!(state.live_flow(sid).is_some(), "live during the turn");
}
assert!(state.live_flow(sid).is_none(), "guard drop must unregister");
}
#[test]
fn live_flow_stale_guard_spares_successor() {
let state = std::sync::Arc::new(TelegramState::new());
let sid = Uuid::new_v4();
let roll1 = open_roll();
let roll2 = open_roll();
let g1 = state.register_live_flow(sid, handle_for(&roll1));
let g2 = state.register_live_flow(sid, handle_for(&roll2));
drop(g1); let h = state
.live_flow(sid)
.expect("successor survives the stale guard drop");
assert!(
std::sync::Arc::ptr_eq(&h.streaming, &roll2),
"successor's handle"
);
drop(g2);
assert!(
state.live_flow(sid).is_none(),
"successor's own drop clears"
);
}
#[test]
fn notify_fold_dedup_second_delivery_is_duplicate() {
let state = TelegramState::new();
let sid = Uuid::new_v4();
let now = std::time::Instant::now();
assert!(!state.note_notify_fold_at(sid, 111, now), "first fold");
assert!(state.note_notify_fold_at(sid, 111, now), "re-delivery dup");
assert!(!state.note_notify_fold_at(sid, 222, now), "distinct folds");
assert!(state.note_notify_fold_at(sid, 222, now), "its re-delivery");
}
#[test]
fn notify_fold_dedup_ttl_expiry_refolds() {
let state = TelegramState::new();
let sid = Uuid::new_v4();
let t0 = std::time::Instant::now();
assert!(!state.note_notify_fold_at(sid, 7, t0));
let later = t0 + TelegramState::NOTIFY_FOLD_DEDUP_TTL + std::time::Duration::from_secs(1);
assert!(
!state.note_notify_fold_at(sid, 7, later),
"expired -> foldable"
);
assert!(state.note_notify_fold_at(sid, 7, later), "re-recorded");
}
#[test]
fn notify_fold_dedup_cap_prunes_oldest() {
let state = TelegramState::new();
let sid = Uuid::new_v4();
let now = std::time::Instant::now();
for fp in 0..TelegramState::NOTIFY_FOLD_DEDUP_CAP as u64 {
assert!(!state.note_notify_fold_at(sid, fp, now));
}
assert!(
state.note_notify_fold_at(sid, 0, now),
"oldest still held at cap"
);
assert!(
!state.note_notify_fold_at(sid, 9999, now),
"distinct push evicts the oldest"
);
assert!(
state.note_notify_fold_at(sid, 1, now),
"younger survives the cap eviction"
);
assert!(
!state.note_notify_fold_at(sid, 0, now),
"evicted -> foldable"
);
assert!(
state.note_notify_fold_at(sid, 2, now),
"the rest of the window survives"
);
}
#[test]
fn notify_fold_dedup_is_per_session() {
let state = TelegramState::new();
let (a, b) = (Uuid::new_v4(), Uuid::new_v4());
let now = std::time::Instant::now();
assert!(!state.note_notify_fold_at(a, 5, now));
assert!(
!state.note_notify_fold_at(b, 5, now),
"other session folds the same fp"
);
assert!(state.note_notify_fold_at(a, 5, now));
}