use crate::channels::telegram::TelegramState;
use std::sync::Arc;
use tokio::time::{Duration, timeout};
#[tokio::test]
async fn single_photo_no_debounce() {
let state = Arc::new(TelegramState::new());
let chat_id = 12345i64;
let user_id = 67890i64;
let buffered = state
.drain_photo_buffer(chat_id, user_id, "test_group")
.await;
assert!(
buffered.is_empty(),
"single photo should not buffer anything"
);
}
#[tokio::test]
async fn album_photos_batched_by_media_group() {
let state = Arc::new(TelegramState::new());
let chat_id = 12345i64;
let user_id = 67890i64;
let media_group_id = "album_123";
let count1 = state
.buffer_photo(
chat_id,
user_id,
media_group_id,
"<<IMG:/path/to/photo1.jpg>>".to_string(),
Some("First photo caption".to_string()),
)
.await;
assert_eq!(count1, 1);
let count2 = state
.buffer_photo(
chat_id,
user_id,
media_group_id,
"<<IMG:/path/to/photo2.jpg>>".to_string(),
None,
)
.await;
assert_eq!(count2, 2);
let count3 = state
.buffer_photo(
chat_id,
user_id,
media_group_id,
"<<IMG:/path/to/photo3.jpg>>".to_string(),
None,
)
.await;
assert_eq!(count3, 3);
let buffered = state
.drain_photo_buffer(chat_id, user_id, media_group_id)
.await;
assert_eq!(buffered.len(), 3);
assert_eq!(
buffered[0].1,
Some("First photo caption".to_string()),
"first photo should have caption"
);
assert_eq!(buffered[1].1, None, "second photo should have no caption");
assert_eq!(buffered[2].1, None, "third photo should have no caption");
assert!(buffered[0].0.contains("photo1.jpg"));
assert!(buffered[1].0.contains("photo2.jpg"));
assert!(buffered[2].0.contains("photo3.jpg"));
}
#[tokio::test]
async fn different_albums_not_merged() {
let state = Arc::new(TelegramState::new());
let chat_id = 12345i64;
let user_id = 67890i64;
state
.buffer_photo(
chat_id,
user_id,
"album_A",
"<<IMG:/path/to/A1.jpg>>".to_string(),
Some("Album A".to_string()),
)
.await;
state
.buffer_photo(
chat_id,
user_id,
"album_B",
"<<IMG:/path/to/B1.jpg>>".to_string(),
Some("Album B".to_string()),
)
.await;
state
.buffer_photo(
chat_id,
user_id,
"album_A",
"<<IMG:/path/to/A2.jpg>>".to_string(),
None,
)
.await;
let album_a = state.drain_photo_buffer(chat_id, user_id, "album_A").await;
assert_eq!(album_a.len(), 2, "album A should have 2 photos");
assert!(album_a[0].0.contains("A1.jpg"));
assert!(album_a[1].0.contains("A2.jpg"));
let album_b = state.drain_photo_buffer(chat_id, user_id, "album_B").await;
assert_eq!(album_b.len(), 1, "album B should have 1 photo");
assert!(album_b[0].0.contains("B1.jpg"));
}
#[tokio::test]
async fn debounce_cancellation() {
let state = Arc::new(TelegramState::new());
let chat_id = 12345i64;
let user_id = 67890i64;
let media_group_id = "test_album";
let token1 = state
.reset_photo_debounce(chat_id, user_id, media_group_id)
.await;
let token2 = state
.reset_photo_debounce(chat_id, user_id, media_group_id)
.await;
assert!(
token1.is_cancelled(),
"first debounce token should be cancelled when second photo arrives"
);
assert!(
!token2.is_cancelled(),
"second debounce token should not be cancelled yet"
);
}
#[tokio::test]
async fn debounce_expires_after_timeout() {
let state = Arc::new(TelegramState::new());
let chat_id = 12345i64;
let user_id = 67890i64;
let media_group_id = "test_album";
let token = state
.reset_photo_debounce(chat_id, user_id, media_group_id)
.await;
let result = timeout(Duration::from_secs(4), state.wait_photo_debounce(token)).await;
assert!(result.is_ok(), "debounce wait should complete");
assert!(result.unwrap(), "debounce should expire (return true)");
}
#[tokio::test]
async fn debounce_cancelled_by_new_photo() {
let state = Arc::new(TelegramState::new());
let chat_id = 12345i64;
let user_id = 67890i64;
let media_group_id = "test_album";
let token1 = state
.reset_photo_debounce(chat_id, user_id, media_group_id)
.await;
let state_clone = state.clone();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(1)).await;
state_clone
.reset_photo_debounce(chat_id, user_id, media_group_id)
.await;
});
let result = timeout(Duration::from_secs(4), state.wait_photo_debounce(token1)).await;
assert!(result.is_ok(), "debounce wait should complete");
assert!(
!result.unwrap(),
"debounce should be cancelled (return false)"
);
}
#[tokio::test]
async fn drain_empty_buffer_no_ghost_dispatch() {
let state = Arc::new(TelegramState::new());
let chat_id = 12345i64;
let user_id = 67890i64;
let media_group_id = "nonexistent_album";
let buffered = state
.drain_photo_buffer(chat_id, user_id, media_group_id)
.await;
assert!(
buffered.is_empty(),
"draining empty buffer should return empty vec"
);
}
#[tokio::test]
async fn cleanup_removes_debounce_token() {
let state = Arc::new(TelegramState::new());
let chat_id = 12345i64;
let user_id = 67890i64;
let media_group_id = "test_album";
let token = state
.reset_photo_debounce(chat_id, user_id, media_group_id)
.await;
assert!(!token.is_cancelled());
state
.cleanup_photo_debounce(chat_id, user_id, media_group_id)
.await;
let token2 = state
.reset_photo_debounce(chat_id, user_id, media_group_id)
.await;
assert!(
!token2.is_cancelled(),
"new token after cleanup should not be cancelled"
);
}