chat-system 0.1.3

A multi-protocol async chat crate — single interface for IRC, Matrix, Discord, Telegram, Slack, Signal, WhatsApp, and more
use anyhow::Result;
use async_trait::async_trait;
use chat_system::{ChatServer, Message, MessageType};
use std::sync::{Arc, Mutex};

// ─── Mock implementation of ChatServer ───────────────────────────────────────

struct MockChatServer {
    name: String,
    /// Messages delivered to the handler during `run`.
    processed: Arc<Mutex<Vec<Message>>>,
}

impl MockChatServer {
    fn new(name: &str) -> Self {
        Self {
            name: name.to_string(),
            processed: Arc::new(Mutex::new(Vec::new())),
        }
    }

    fn processed_messages(&self) -> Vec<Message> {
        self.processed.lock().unwrap().clone()
    }
}

#[async_trait]
impl ChatServer for MockChatServer {
    fn name(&self) -> &str {
        &self.name
    }

    fn listeners(&self) -> Vec<&dyn chat_system::ChatListener> {
        Vec::new()
    }

    /// Simulates receiving a single test message and passing it to the handler.
    async fn run<F, Fut>(&mut self, handler: F) -> Result<()>
    where
        F: Fn(Message) -> Fut + Send + Sync + 'static,
        Fut: std::future::Future<Output = Result<Option<String>>> + Send + 'static,
    {
        let msg = Message {
            id: "mock-1".to_string(),
            sender: "tester".to_string(),
            content: "hello server".to_string(),
            timestamp: 1_000,
            channel: Some("test-channel".to_string()),
            reply_to: None,
            thread_id: None,
            media: None,
            is_direct: false,
            message_type: MessageType::Text,
            edited_timestamp: None,
            reactions: None,
        };
        self.processed.lock().unwrap().push(msg.clone());
        handler(msg).await?;
        Ok(())
    }

    async fn shutdown(&mut self) -> Result<()> {
        Ok(())
    }
}

// ─── Tests ────────────────────────────────────────────────────────────────────

#[tokio::test]
async fn server_name() {
    let server = MockChatServer::new("test-server");
    assert_eq!(server.name(), "test-server");
}

#[tokio::test]
async fn server_listeners_empty() {
    let server = MockChatServer::new("test-server");
    assert!(server.listeners().is_empty());
}

#[tokio::test]
async fn server_shutdown_is_ok() {
    let mut server = MockChatServer::new("test-server");
    server.shutdown().await.unwrap();
}

#[tokio::test]
async fn server_run_invokes_handler_with_message() {
    let mut server = MockChatServer::new("test-server");
    let received: Arc<Mutex<Vec<Message>>> = Arc::new(Mutex::new(Vec::new()));
    let received_clone = received.clone();

    server
        .run(move |msg| {
            let received = received_clone.clone();
            async move {
                received.lock().unwrap().push(msg);
                Ok(None)
            }
        })
        .await
        .unwrap();

    let msgs = received.lock().unwrap();
    assert_eq!(msgs.len(), 1);
    assert_eq!(msgs[0].id, "mock-1");
    assert_eq!(msgs[0].sender, "tester");
    assert_eq!(msgs[0].content, "hello server");
    assert_eq!(msgs[0].channel, Some("test-channel".to_string()));
}

#[tokio::test]
async fn server_run_handler_can_return_reply() {
    let mut server = MockChatServer::new("test-server");
    let reply_seen: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
    let reply_clone = reply_seen.clone();

    server
        .run(move |_msg| {
            let reply_seen = reply_clone.clone();
            async move {
                let reply = Some("pong".to_string());
                *reply_seen.lock().unwrap() = reply.clone();
                Ok(reply)
            }
        })
        .await
        .unwrap();

    let reply = reply_seen.lock().unwrap();
    assert_eq!(*reply, Some("pong".to_string()));
}

#[tokio::test]
async fn server_run_records_processed_message_internally() {
    let mut server = MockChatServer::new("test-server");

    server.run(|_msg| async move { Ok(None) }).await.unwrap();

    let msgs = server.processed_messages();
    assert_eq!(msgs.len(), 1);
    assert_eq!(msgs[0].content, "hello server");
}

#[tokio::test]
async fn server_run_handler_error_propagates() {
    let mut server = MockChatServer::new("test-server");

    let result = server
        .run(|_msg| async move { Err(anyhow::anyhow!("handler error")) })
        .await;

    assert!(result.is_err());
    assert!(result.unwrap_err().to_string().contains("handler error"));
}

#[tokio::test]
async fn server_run_followed_by_shutdown() {
    let mut server = MockChatServer::new("test-server");

    server.run(|_msg| async move { Ok(None) }).await.unwrap();

    server.shutdown().await.unwrap();
}