use crate::db::models::ChannelMessage;
use crate::db::{ChannelMessageRepository, Database};
fn msg(
chat_id: &str,
msg_id: &str,
thread_id: Option<&str>,
topic_name: Option<&str>,
) -> ChannelMessage {
ChannelMessage::new(
"telegram".into(),
chat_id.into(),
Some("Test Group".into()),
"u1".into(),
"Alice".into(),
"hello".into(),
"text".into(),
Some(msg_id.into()),
)
.with_thread(
thread_id.map(str::to_string),
topic_name.map(str::to_string),
)
}
#[tokio::test]
async fn topics_for_chat_returns_distinct_threads_with_names() {
let db = Database::connect_in_memory().await.unwrap();
db.run_migrations().await.unwrap();
let repo = ChannelMessageRepository::new(db.pool().clone());
repo.insert(&msg("test-chat", "m1", Some("17"), Some("announcements")))
.await
.unwrap();
repo.insert(&msg("test-chat", "m2", Some("17"), Some("announcements")))
.await
.unwrap();
repo.insert(&msg("test-chat", "m3", Some("22"), Some("dev")))
.await
.unwrap();
let topics = repo.topics_for_chat("telegram", "test-chat").await.unwrap();
assert_eq!(topics.len(), 2);
let names: Vec<_> = topics
.iter()
.map(|t| (t.thread_id.as_str(), t.topic_name.as_deref()))
.collect();
assert!(
names.contains(&("17", Some("announcements"))),
"expected #17 announcements; got: {names:?}"
);
assert!(
names.contains(&("22", Some("dev"))),
"expected #22 dev; got: {names:?}"
);
}
#[tokio::test]
async fn topics_for_chat_handles_topics_with_no_captured_name() {
let db = Database::connect_in_memory().await.unwrap();
db.run_migrations().await.unwrap();
let repo = ChannelMessageRepository::new(db.pool().clone());
repo.insert(&msg("test-chat", "m1", Some("33"), None))
.await
.unwrap();
let topics = repo.topics_for_chat("telegram", "test-chat").await.unwrap();
assert_eq!(topics.len(), 1);
assert_eq!(topics[0].thread_id, "33");
assert_eq!(topics[0].topic_name, None);
}
#[tokio::test]
async fn topics_for_chat_ignores_non_topic_messages() {
let db = Database::connect_in_memory().await.unwrap();
db.run_migrations().await.unwrap();
let repo = ChannelMessageRepository::new(db.pool().clone());
repo.insert(&msg("test-chat", "m1", None, None))
.await
.unwrap();
repo.insert(&msg("test-chat", "m2", None, None))
.await
.unwrap();
let topics = repo.topics_for_chat("telegram", "test-chat").await.unwrap();
assert!(topics.is_empty(), "non-topic messages must not surface");
}
#[tokio::test]
async fn topics_for_chat_isolates_per_channel_and_chat() {
let db = Database::connect_in_memory().await.unwrap();
db.run_migrations().await.unwrap();
let repo = ChannelMessageRepository::new(db.pool().clone());
repo.insert(&msg("chat-A", "m1", Some("17"), Some("A-announcements")))
.await
.unwrap();
repo.insert(&msg("chat-B", "m1", Some("17"), Some("B-announcements")))
.await
.unwrap();
let a = repo.topics_for_chat("telegram", "chat-A").await.unwrap();
assert_eq!(a.len(), 1);
assert_eq!(a[0].topic_name.as_deref(), Some("A-announcements"));
let b = repo.topics_for_chat("telegram", "chat-B").await.unwrap();
assert_eq!(b.len(), 1);
assert_eq!(b[0].topic_name.as_deref(), Some("B-announcements"));
}
#[tokio::test]
async fn topics_for_chat_orders_by_most_recent_activity() {
use chrono::{Duration, Utc};
let db = Database::connect_in_memory().await.unwrap();
db.run_migrations().await.unwrap();
let repo = ChannelMessageRepository::new(db.pool().clone());
let mut old = msg("chat", "m1", Some("100"), Some("old-topic"));
old.created_at = Utc::now() - Duration::hours(2);
let new = msg("chat", "m2", Some("200"), Some("new-topic"));
repo.insert(&old).await.unwrap();
repo.insert(&new).await.unwrap();
let topics = repo.topics_for_chat("telegram", "chat").await.unwrap();
assert_eq!(topics.len(), 2);
assert_eq!(
topics[0].thread_id, "200",
"most-recent activity must come first"
);
assert_eq!(topics[1].thread_id, "100");
}
#[tokio::test]
async fn message_count_per_topic_is_correct() {
let db = Database::connect_in_memory().await.unwrap();
db.run_migrations().await.unwrap();
let repo = ChannelMessageRepository::new(db.pool().clone());
for i in 1..=3 {
repo.insert(&msg(
"chat",
&format!("a{i}"),
Some("17"),
Some("announcements"),
))
.await
.unwrap();
}
repo.insert(&msg("chat", "d1", Some("22"), Some("dev")))
.await
.unwrap();
let topics = repo.topics_for_chat("telegram", "chat").await.unwrap();
let by_id: std::collections::HashMap<_, _> = topics
.iter()
.map(|t| (t.thread_id.as_str(), t.message_count))
.collect();
assert_eq!(by_id.get("17"), Some(&3));
assert_eq!(by_id.get("22"), Some(&1));
}