use ilink_hub::{hub::queue::InMemoryQueue, ilink::types::InboundMessage, MessageQueue};
use std::sync::Arc;
fn make_msg(content: &str) -> InboundMessage {
InboundMessage {
msg_id: format!("test_{content}"),
from_user: "user1".to_string(),
chat_id: None,
chat_type: None,
msg_type: 1,
content: Some(content.to_string()),
context_token: "ctx1".to_string(),
timestamp: None,
extra: serde_json::Value::Object(serde_json::Map::new()),
}
}
#[tokio::test]
async fn test_push_and_drain() {
let q = InMemoryQueue::new();
q.push("v1", make_msg("a")).await.unwrap();
q.push("v1", make_msg("b")).await.unwrap();
q.push("v1", make_msg("c")).await.unwrap();
let msgs = q.drain("v1").await.unwrap();
assert_eq!(msgs.len(), 3);
assert_eq!(msgs[0].content, Some("a".to_string()));
assert_eq!(msgs[1].content, Some("b".to_string()));
assert_eq!(msgs[2].content, Some("c".to_string()));
}
#[tokio::test]
async fn test_drain_empty() {
let q = InMemoryQueue::new();
let msgs = q.drain("v1").await.unwrap();
assert!(
msgs.is_empty(),
"drain on empty queue should return empty vec"
);
}
#[tokio::test]
async fn test_overflow_head_drop() {
let q = InMemoryQueue::new();
for i in 0..=200 {
let dropped = q.push("v1", make_msg(&format!("msg_{i}"))).await.unwrap();
if i < 200 {
assert!(!dropped, "unexpected overflow at push {i}");
} else {
assert!(dropped, "expected overflow flag on 201st push");
}
}
let msgs = q.drain("v1").await.unwrap();
assert_eq!(
msgs.len(),
200,
"queue should hold exactly MAX_QUEUE_SIZE messages"
);
assert_eq!(
msgs[0].content,
Some("msg_1".to_string()),
"oldest message (msg_0) should have been head-dropped"
);
assert_eq!(msgs[199].content, Some("msg_200".to_string()));
}
#[tokio::test]
async fn test_wait_notify_receives() {
let q = Arc::new(InMemoryQueue::new());
let q2 = q.clone();
tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
q2.push("v1", make_msg("hello")).await.unwrap();
});
let notified = q.wait_notify("v1", 2).await.unwrap();
assert!(
notified,
"wait_notify should return true when a message is pushed"
);
}
#[tokio::test]
async fn test_wait_notify_timeout() {
let q = InMemoryQueue::new();
let notified = q.wait_notify("v1", 1).await.unwrap();
assert!(
!notified,
"wait_notify should return false on timeout with no push"
);
}
#[tokio::test]
async fn test_queue_sizes() {
let q = InMemoryQueue::new();
q.push("a", make_msg("1")).await.unwrap();
q.push("a", make_msg("2")).await.unwrap();
q.push("b", make_msg("x")).await.unwrap();
q.push("b", make_msg("y")).await.unwrap();
q.push("b", make_msg("z")).await.unwrap();
let sizes = q.queue_sizes().await.unwrap();
assert_eq!(sizes["a"], 2);
assert_eq!(sizes["b"], 3);
}
#[tokio::test]
async fn test_remove_client() {
let q = InMemoryQueue::new();
q.push("v1", make_msg("1")).await.unwrap();
q.push("v1", make_msg("2")).await.unwrap();
q.remove_client("v1").await.unwrap();
let msgs = q.drain("v1").await.unwrap();
assert!(
msgs.is_empty(),
"drain after remove_client should return empty"
);
q.push("v1", make_msg("3")).await.unwrap();
let msgs = q.drain("v1").await.unwrap();
assert_eq!(msgs.len(), 1);
assert_eq!(msgs[0].content, Some("3".to_string()));
}
#[tokio::test]
async fn test_concurrent_push() {
let q = Arc::new(InMemoryQueue::new());
let mut handles = Vec::new();
for task_id in 0..10 {
let q2 = q.clone();
handles.push(tokio::spawn(async move {
for i in 0..10 {
q2.push("v1", make_msg(&format!("t{task_id}_m{i}")))
.await
.unwrap();
}
}));
}
for handle in handles {
handle.await.unwrap();
}
let msgs = q.drain("v1").await.unwrap();
assert!(
!msgs.is_empty(),
"queue should contain messages after concurrent pushes"
);
assert!(
msgs.len() <= 200,
"queue should respect MAX_QUEUE_SIZE cap; got {}",
msgs.len()
);
}
#[test]
fn test_object_safe() {
let _: Arc<dyn MessageQueue> = Arc::new(InMemoryQueue::new());
}
#[tokio::test]
async fn test_mock_implementation() {
use async_trait::async_trait;
use ilink_hub::error::HubError;
use std::collections::HashMap;
struct NoopQueue;
#[async_trait]
impl MessageQueue for NoopQueue {
async fn push(&self, _vtoken: &str, _msg: InboundMessage) -> Result<bool, HubError> {
Ok(false)
}
async fn drain(&self, _vtoken: &str) -> Result<Vec<InboundMessage>, HubError> {
Ok(vec![])
}
async fn wait_notify(&self, _vtoken: &str, _timeout_secs: u64) -> Result<bool, HubError> {
Ok(false)
}
async fn remove_client(&self, _vtoken: &str) -> Result<(), HubError> {
Ok(())
}
async fn queue_sizes(&self) -> Result<HashMap<String, usize>, HubError> {
Ok(HashMap::new())
}
}
let q: Arc<dyn MessageQueue> = Arc::new(NoopQueue);
assert!(q.push("x", make_msg("y")).await.is_ok());
assert!(q.drain("x").await.unwrap().is_empty());
assert!(!q.wait_notify("x", 0).await.unwrap());
}