Skip to main content

max / makenotwork

7.0 KB · 232 lines History Blame Raw
1 //! Integration tests for admin queries and membership counting.
2 //!
3 //! Covers: list_all_communities, search_users (prefix, limit, LIKE escaping),
4 //! get_user_membership_summary (post counts, suspended exclusion).
5
6 use crate::harness::TestHarness;
7 use uuid::Uuid;
8
9 // ============================================================================
10 // list_all_communities
11 // ============================================================================
12
13 #[tokio::test]
14 async fn test_list_all_communities() {
15 let h = TestHarness::new().await;
16
17 let _c1 = h.create_community("Alpha Forum", "alpha").await;
18 let _c2 = h.create_community("Beta Forum", "beta").await;
19 let _c3 = h.create_community("Gamma Forum", "gamma").await;
20
21 let rows = mt_db::queries::list_all_communities(&h.db)
22 .await
23 .unwrap();
24
25 assert_eq!(rows.len(), 3, "Should return all 3 communities");
26
27 let names: Vec<&str> = rows.iter().map(|r| r.name.as_str()).collect();
28 assert_eq!(
29 names,
30 vec!["Alpha Forum", "Beta Forum", "Gamma Forum"],
31 "Communities should be ordered by name"
32 );
33
34 // Verify slugs are present
35 assert_eq!(rows[0].slug, "alpha");
36 assert_eq!(rows[1].slug, "beta");
37 assert_eq!(rows[2].slug, "gamma");
38
39 // Verify suspended fields default to None
40 assert!(rows[0].suspended_at.is_none());
41 assert!(rows[0].suspension_reason.is_none());
42 }
43
44 // ============================================================================
45 // search_users
46 // ============================================================================
47
48 #[tokio::test]
49 async fn test_search_users_exact_prefix() {
50 let h = TestHarness::new().await;
51
52 // Insert users directly (no login needed for DB-level tests)
53 for (name, display) in [("alice", "Alice"), ("alvin", "Alvin"), ("bob", "Bob")] {
54 let id = Uuid::new_v4();
55 sqlx::query(
56 "INSERT INTO users (mnw_account_id, username, display_name) VALUES ($1, $2, $3)",
57 )
58 .bind(id)
59 .bind(name)
60 .bind(display)
61 .execute(&h.db)
62 .await
63 .unwrap();
64 }
65
66 let results = mt_db::queries::search_users(&h.db, "al").await.unwrap();
67
68 let usernames: Vec<&str> = results.iter().map(|r| r.username.as_str()).collect();
69 assert_eq!(
70 usernames,
71 vec!["alice", "alvin"],
72 "Search for 'al' should match alice and alvin, ordered alphabetically"
73 );
74
75 // Verify bob is excluded
76 assert!(
77 !usernames.contains(&"bob"),
78 "bob should not match prefix 'al'"
79 );
80 }
81
82 #[tokio::test]
83 async fn test_search_users_limit() {
84 let h = TestHarness::new().await;
85
86 // Insert a handful of users to verify the query executes with LIMIT 50
87 for i in 0..5 {
88 let id = Uuid::new_v4();
89 let name = format!("limituser{i}");
90 sqlx::query(
91 "INSERT INTO users (mnw_account_id, username, display_name) VALUES ($1, $2, $2)",
92 )
93 .bind(id)
94 .bind(&name)
95 .execute(&h.db)
96 .await
97 .unwrap();
98 }
99
100 let results = mt_db::queries::search_users(&h.db, "limituser")
101 .await
102 .unwrap();
103
104 assert_eq!(results.len(), 5, "Should return all 5 matching users");
105
106 // Verify they are ordered alphabetically
107 let usernames: Vec<&str> = results.iter().map(|r| r.username.as_str()).collect();
108 let mut sorted = usernames.clone();
109 sorted.sort();
110 assert_eq!(usernames, sorted, "Results should be alphabetically ordered");
111 }
112
113 #[tokio::test]
114 async fn test_search_users_special_chars() {
115 let h = TestHarness::new().await;
116
117 // Insert users: one that looks like a LIKE wildcard match, one normal
118 for (name, display) in [("alice", "Alice"), ("al%pha", "Al%pha")] {
119 let id = Uuid::new_v4();
120 sqlx::query(
121 "INSERT INTO users (mnw_account_id, username, display_name) VALUES ($1, $2, $3)",
122 )
123 .bind(id)
124 .bind(name)
125 .bind(display)
126 .execute(&h.db)
127 .await
128 .unwrap();
129 }
130
131 // Search for literal "al%" -- should only match usernames starting with "al%"
132 let results = mt_db::queries::search_users(&h.db, "al%").await.unwrap();
133
134 let usernames: Vec<&str> = results.iter().map(|r| r.username.as_str()).collect();
135 assert_eq!(
136 usernames,
137 vec!["al%pha"],
138 "Searching 'al%' should match literal percent, not act as wildcard"
139 );
140
141 // alice should NOT be matched -- the % is escaped, so the query is 'al\%%'
142 assert!(
143 !usernames.contains(&"alice"),
144 "alice should not match when searching for literal 'al%'"
145 );
146 }
147
148 // ============================================================================
149 // get_user_membership_summary
150 // ============================================================================
151
152 #[tokio::test]
153 async fn test_membership_summary_post_count() {
154 let mut h = TestHarness::new().await;
155
156 let user_id = h.login_as("postwriter").await;
157 let comm_id = h.create_community("Writers Guild", "writers").await;
158 let cat_id = h.create_category(comm_id, "General", "general").await;
159 h.add_membership(user_id, comm_id, "member").await;
160
161 // Create a thread with an initial post, then add two more posts
162 let thread_id = h
163 .create_thread_with_post(cat_id, user_id, "First Thread", "First post body")
164 .await;
165
166 mt_db::mutations::create_post(
167 &h.db,
168 thread_id,
169 user_id,
170 "Second post",
171 "<p>Second post</p>",
172 true,
173 )
174 .await
175 .unwrap();
176
177 mt_db::mutations::create_post(
178 &h.db,
179 thread_id,
180 user_id,
181 "Third post",
182 "<p>Third post</p>",
183 true,
184 )
185 .await
186 .unwrap();
187
188 let summaries = mt_db::queries::get_user_membership_summary(&h.db, user_id)
189 .await
190 .unwrap();
191
192 assert_eq!(summaries.len(), 1, "Should have one membership");
193 assert_eq!(summaries[0].community_name, "Writers Guild");
194 assert_eq!(summaries[0].community_slug, "writers");
195 assert_eq!(summaries[0].role, mt_core::types::CommunityRole::Member);
196 assert_eq!(
197 summaries[0].post_count, 3,
198 "Should count all 3 posts (initial + 2 replies)"
199 );
200 }
201
202 #[tokio::test]
203 async fn test_membership_summary_excludes_suspended() {
204 let mut h = TestHarness::new().await;
205
206 let user_id = h.login_as("suspendcheck").await;
207
208 let active_id = h.create_community("Active Community", "active").await;
209 h.add_membership(user_id, active_id, "member").await;
210
211 let suspended_id = h
212 .create_community("Suspended Community", "suspended")
213 .await;
214 h.add_membership(user_id, suspended_id, "member").await;
215
216 // Suspend the second community
217 mt_db::mutations::suspend_community(&h.db, suspended_id, Some("policy violation"))
218 .await
219 .unwrap();
220
221 let summaries = mt_db::queries::get_user_membership_summary(&h.db, user_id)
222 .await
223 .unwrap();
224
225 assert_eq!(
226 summaries.len(),
227 1,
228 "Should exclude the suspended community"
229 );
230 assert_eq!(summaries[0].community_name, "Active Community");
231 }
232