//! Integration tests for admin queries and membership counting. //! //! Covers: list_all_communities, search_users (prefix, limit, LIKE escaping), //! get_user_membership_summary (post counts, suspended exclusion). use crate::harness::TestHarness; use uuid::Uuid; // ============================================================================ // list_all_communities // ============================================================================ #[tokio::test] async fn test_list_all_communities() { let h = TestHarness::new().await; let _c1 = h.create_community("Alpha Forum", "alpha").await; let _c2 = h.create_community("Beta Forum", "beta").await; let _c3 = h.create_community("Gamma Forum", "gamma").await; let rows = mt_db::queries::list_all_communities(&h.db) .await .unwrap(); assert_eq!(rows.len(), 3, "Should return all 3 communities"); let names: Vec<&str> = rows.iter().map(|r| r.name.as_str()).collect(); assert_eq!( names, vec!["Alpha Forum", "Beta Forum", "Gamma Forum"], "Communities should be ordered by name" ); // Verify slugs are present assert_eq!(rows[0].slug, "alpha"); assert_eq!(rows[1].slug, "beta"); assert_eq!(rows[2].slug, "gamma"); // Verify suspended fields default to None assert!(rows[0].suspended_at.is_none()); assert!(rows[0].suspension_reason.is_none()); } // ============================================================================ // search_users // ============================================================================ #[tokio::test] async fn test_search_users_exact_prefix() { let h = TestHarness::new().await; // Insert users directly (no login needed for DB-level tests) for (name, display) in [("alice", "Alice"), ("alvin", "Alvin"), ("bob", "Bob")] { let id = Uuid::new_v4(); sqlx::query( "INSERT INTO users (mnw_account_id, username, display_name) VALUES ($1, $2, $3)", ) .bind(id) .bind(name) .bind(display) .execute(&h.db) .await .unwrap(); } let results = mt_db::queries::search_users(&h.db, "al").await.unwrap(); let usernames: Vec<&str> = results.iter().map(|r| r.username.as_str()).collect(); assert_eq!( usernames, vec!["alice", "alvin"], "Search for 'al' should match alice and alvin, ordered alphabetically" ); // Verify bob is excluded assert!( !usernames.contains(&"bob"), "bob should not match prefix 'al'" ); } #[tokio::test] async fn test_search_users_limit() { let h = TestHarness::new().await; // Insert a handful of users to verify the query executes with LIMIT 50 for i in 0..5 { let id = Uuid::new_v4(); let name = format!("limituser{i}"); sqlx::query( "INSERT INTO users (mnw_account_id, username, display_name) VALUES ($1, $2, $2)", ) .bind(id) .bind(&name) .execute(&h.db) .await .unwrap(); } let results = mt_db::queries::search_users(&h.db, "limituser") .await .unwrap(); assert_eq!(results.len(), 5, "Should return all 5 matching users"); // Verify they are ordered alphabetically let usernames: Vec<&str> = results.iter().map(|r| r.username.as_str()).collect(); let mut sorted = usernames.clone(); sorted.sort(); assert_eq!(usernames, sorted, "Results should be alphabetically ordered"); } #[tokio::test] async fn test_search_users_special_chars() { let h = TestHarness::new().await; // Insert users: one that looks like a LIKE wildcard match, one normal for (name, display) in [("alice", "Alice"), ("al%pha", "Al%pha")] { let id = Uuid::new_v4(); sqlx::query( "INSERT INTO users (mnw_account_id, username, display_name) VALUES ($1, $2, $3)", ) .bind(id) .bind(name) .bind(display) .execute(&h.db) .await .unwrap(); } // Search for literal "al%" -- should only match usernames starting with "al%" let results = mt_db::queries::search_users(&h.db, "al%").await.unwrap(); let usernames: Vec<&str> = results.iter().map(|r| r.username.as_str()).collect(); assert_eq!( usernames, vec!["al%pha"], "Searching 'al%' should match literal percent, not act as wildcard" ); // alice should NOT be matched -- the % is escaped, so the query is 'al\%%' assert!( !usernames.contains(&"alice"), "alice should not match when searching for literal 'al%'" ); } // ============================================================================ // get_user_membership_summary // ============================================================================ #[tokio::test] async fn test_membership_summary_post_count() { let mut h = TestHarness::new().await; let user_id = h.login_as("postwriter").await; let comm_id = h.create_community("Writers Guild", "writers").await; let cat_id = h.create_category(comm_id, "General", "general").await; h.add_membership(user_id, comm_id, "member").await; // Create a thread with an initial post, then add two more posts let thread_id = h .create_thread_with_post(cat_id, user_id, "First Thread", "First post body") .await; mt_db::mutations::create_post( &h.db, thread_id, user_id, "Second post", "
Second post
", true, ) .await .unwrap(); mt_db::mutations::create_post( &h.db, thread_id, user_id, "Third post", "Third post
", true, ) .await .unwrap(); let summaries = mt_db::queries::get_user_membership_summary(&h.db, user_id) .await .unwrap(); assert_eq!(summaries.len(), 1, "Should have one membership"); assert_eq!(summaries[0].community_name, "Writers Guild"); assert_eq!(summaries[0].community_slug, "writers"); assert_eq!(summaries[0].role, mt_core::types::CommunityRole::Member); assert_eq!( summaries[0].post_count, 3, "Should count all 3 posts (initial + 2 replies)" ); } #[tokio::test] async fn test_membership_summary_excludes_suspended() { let mut h = TestHarness::new().await; let user_id = h.login_as("suspendcheck").await; let active_id = h.create_community("Active Community", "active").await; h.add_membership(user_id, active_id, "member").await; let suspended_id = h .create_community("Suspended Community", "suspended") .await; h.add_membership(user_id, suspended_id, "member").await; // Suspend the second community mt_db::mutations::suspend_community(&h.db, suspended_id, Some("policy violation")) .await .unwrap(); let summaries = mt_db::queries::get_user_membership_summary(&h.db, user_id) .await .unwrap(); assert_eq!( summaries.len(), 1, "Should exclude the suspended community" ); assert_eq!(summaries[0].community_name, "Active Community"); }