use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use crate::channels::telegram::session_gate;
async fn resolve_or_create(store: &Arc<tokio::sync::Mutex<Option<u64>>>, created: &AtomicUsize) {
let existing = *store.lock().await;
tokio::task::yield_now().await;
if existing.is_none() {
created.fetch_add(1, Ordering::SeqCst);
*store.lock().await = Some(1);
}
}
#[tokio::test]
async fn test_two_near_simultaneous_messages_resolve_one_session() {
let store = Arc::new(tokio::sync::Mutex::new(None));
let created = Arc::new(AtomicUsize::new(0));
const CHAT: i64 = -1003936827469;
const TOPIC: Option<i32> = Some(30045);
let tasks: Vec<_> = (0..2)
.map(|_| {
let store = Arc::clone(&store);
let created = Arc::clone(&created);
tokio::spawn(async move {
let _gate = session_gate::hold(CHAT, TOPIC).await;
resolve_or_create(&store, &created).await;
})
})
.collect();
for t in tasks {
t.await.expect("resolve task panicked");
}
assert_eq!(
created.load(Ordering::SeqCst),
1,
"#1201: the second message must find the first message's session, \
not create a parallel one"
);
}
#[tokio::test]
async fn test_without_the_gate_the_same_shape_double_creates() {
let store = Arc::new(tokio::sync::Mutex::new(None));
let created = Arc::new(AtomicUsize::new(0));
let tasks: Vec<_> = (0..2)
.map(|_| {
let store = Arc::clone(&store);
let created = Arc::clone(&created);
tokio::spawn(async move { resolve_or_create(&store, &created).await })
})
.collect();
for t in tasks {
t.await.expect("resolve task panicked");
}
assert_eq!(
created.load(Ordering::SeqCst),
2,
"#1201: ungated, both tasks miss the lookup and both create"
);
}
#[tokio::test]
async fn test_different_topics_in_one_chat_do_not_block_each_other() {
const CHAT: i64 = -1003936827469;
let held = session_gate::hold(CHAT, Some(101)).await;
let other = tokio::time::timeout(
std::time::Duration::from_secs(2),
session_gate::hold(CHAT, Some(202)),
)
.await;
assert!(
other.is_ok(),
"#1201: a second topic must not wait on the first"
);
drop(held);
}
#[tokio::test]
async fn test_the_same_key_serializes() {
const CHAT: i64 = -1000000000001;
let held = session_gate::hold(CHAT, Some(7)).await;
let blocked = tokio::time::timeout(
std::time::Duration::from_millis(200),
session_gate::hold(CHAT, Some(7)),
)
.await;
assert!(
blocked.is_err(),
"#1201: the same (chat, topic) must be single-flight"
);
drop(held);
let after = tokio::time::timeout(
std::time::Duration::from_secs(2),
session_gate::hold(CHAT, Some(7)),
)
.await;
assert!(
after.is_ok(),
"#1201: the gate must be released, not leaked"
);
assert!(session_gate::tracked() > 0);
}
#[tokio::test]
async fn test_general_topic_and_non_forum_share_the_none_key() {
const CHAT: i64 = -1000000000002;
let held = session_gate::hold(CHAT, None).await;
let same = tokio::time::timeout(
std::time::Duration::from_millis(200),
session_gate::hold(CHAT, None),
)
.await;
assert!(same.is_err(), "#1201: None must be a key like any other");
drop(held);
}