Skip to main content

max / makenotwork

9.9 KB · 329 lines History Blame Raw
1 //! Tests for platform admin routes.
2
3 use crate::harness::TestHarness;
4 use uuid::Uuid;
5
6 #[sqlx::test]
7 async fn non_admin_gets_404(_pool: sqlx::PgPool) {
8 let mut h = TestHarness::new().await;
9 let _user = h.login_as("regular").await;
10
11 let resp = h.client.get("/_admin").await;
12 assert_eq!(resp.status, axum::http::StatusCode::NOT_FOUND);
13 }
14
15 #[sqlx::test]
16 async fn admin_can_see_dashboard(_pool: sqlx::PgPool) {
17 let admin_id = Uuid::new_v4();
18 let mut h = TestHarness::new_with_admin(admin_id).await;
19 let _admin = h.login_as("admin").await;
20
21 // Re-login with the correct admin_id since login_as generates a random UUID
22 sqlx::query("UPDATE users SET mnw_account_id = $1 WHERE username = 'admin'")
23 .bind(admin_id)
24 .execute(&h.db)
25 .await
26 .unwrap();
27
28 // Re-establish session with correct user_id
29 h.client.get("/").await;
30 let body = serde_json::json!({
31 "user_id": admin_id.to_string(),
32 "username": "admin",
33 });
34 h.client.post_json("/_test/login", &body.to_string()).await;
35
36 let resp = h.client.get("/_admin").await;
37 assert_eq!(resp.status, axum::http::StatusCode::OK);
38 assert!(resp.text.contains("Platform Admin"));
39 }
40
41 #[sqlx::test]
42 async fn admin_can_suspend_community(_pool: sqlx::PgPool) {
43 let admin_id = Uuid::new_v4();
44 let mut h = TestHarness::new_with_admin(admin_id).await;
45
46 // Set up admin session
47 sqlx::query(
48 "INSERT INTO users (mnw_account_id, username, display_name)
49 VALUES ($1, 'admin', 'Admin') ON CONFLICT DO NOTHING",
50 )
51 .bind(admin_id)
52 .execute(&h.db)
53 .await
54 .unwrap();
55 h.client.get("/").await;
56 let body = serde_json::json!({ "user_id": admin_id.to_string(), "username": "admin" });
57 h.client.post_json("/_test/login", &body.to_string()).await;
58
59 let community_id = h.create_community("Test Community", "test").await;
60
61 let resp = h
62 .client
63 .post_form(
64 &format!("/_admin/communities/{community_id}/suspend"),
65 "reason=policy+violation",
66 )
67 .await;
68 assert!(resp.status.is_redirection() || resp.status == axum::http::StatusCode::OK);
69
70 // Verify community is now suspended (returns 403)
71 // Verify the suspension stuck in the DB
72 let suspended: bool =
73 sqlx::query_scalar("SELECT suspended_at IS NOT NULL FROM communities WHERE id = $1")
74 .bind(community_id)
75 .fetch_one(&h.db)
76 .await
77 .unwrap();
78 assert!(suspended);
79 }
80
81 #[sqlx::test]
82 async fn admin_can_unsuspend_community(_pool: sqlx::PgPool) {
83 let admin_id = Uuid::new_v4();
84 let mut h = TestHarness::new_with_admin(admin_id).await;
85
86 sqlx::query(
87 "INSERT INTO users (mnw_account_id, username, display_name)
88 VALUES ($1, 'admin', 'Admin') ON CONFLICT DO NOTHING",
89 )
90 .bind(admin_id)
91 .execute(&h.db)
92 .await
93 .unwrap();
94 h.client.get("/").await;
95 let body = serde_json::json!({ "user_id": admin_id.to_string(), "username": "admin" });
96 h.client.post_json("/_test/login", &body.to_string()).await;
97
98 let community_id = h.create_community("Test", "test").await;
99
100 // Suspend it
101 sqlx::query(
102 "UPDATE communities SET suspended_at = now(), suspension_reason = 'test' WHERE id = $1",
103 )
104 .bind(community_id)
105 .execute(&h.db)
106 .await
107 .unwrap();
108
109 let resp = h
110 .client
111 .post_form(&format!("/_admin/communities/{community_id}/unsuspend"), "")
112 .await;
113 assert!(resp.status.is_redirection() || resp.status == axum::http::StatusCode::OK);
114
115 let suspended: bool =
116 sqlx::query_scalar("SELECT suspended_at IS NOT NULL FROM communities WHERE id = $1")
117 .bind(community_id)
118 .fetch_one(&h.db)
119 .await
120 .unwrap();
121 assert!(!suspended);
122 }
123
124 #[sqlx::test]
125 async fn admin_can_suspend_user(_pool: sqlx::PgPool) {
126 let admin_id = Uuid::new_v4();
127 let mut h = TestHarness::new_with_admin(admin_id).await;
128
129 sqlx::query(
130 "INSERT INTO users (mnw_account_id, username, display_name)
131 VALUES ($1, 'admin', 'Admin') ON CONFLICT DO NOTHING",
132 )
133 .bind(admin_id)
134 .execute(&h.db)
135 .await
136 .unwrap();
137 h.client.get("/").await;
138 let body = serde_json::json!({ "user_id": admin_id.to_string(), "username": "admin" });
139 h.client.post_json("/_test/login", &body.to_string()).await;
140
141 // Create a user to suspend
142 let target_id = Uuid::new_v4();
143 sqlx::query(
144 "INSERT INTO users (mnw_account_id, username, display_name) VALUES ($1, 'baduser', 'Bad User')",
145 )
146 .bind(target_id)
147 .execute(&h.db)
148 .await
149 .unwrap();
150
151 let resp = h
152 .client
153 .post_form(
154 &format!("/_admin/users/{target_id}/suspend"),
155 "reason=abuse",
156 )
157 .await;
158 assert!(resp.status.is_redirection() || resp.status == axum::http::StatusCode::OK);
159
160 let suspended: bool =
161 sqlx::query_scalar("SELECT suspended_at IS NOT NULL FROM users WHERE mnw_account_id = $1")
162 .bind(target_id)
163 .fetch_one(&h.db)
164 .await
165 .unwrap();
166 assert!(suspended);
167 }
168
169 #[sqlx::test]
170 async fn admin_can_unsuspend_user(_pool: sqlx::PgPool) {
171 let admin_id = Uuid::new_v4();
172 let mut h = TestHarness::new_with_admin(admin_id).await;
173
174 sqlx::query(
175 "INSERT INTO users (mnw_account_id, username, display_name)
176 VALUES ($1, 'admin', 'Admin') ON CONFLICT DO NOTHING",
177 )
178 .bind(admin_id)
179 .execute(&h.db)
180 .await
181 .unwrap();
182 h.client.get("/").await;
183 let body = serde_json::json!({ "user_id": admin_id.to_string(), "username": "admin" });
184 h.client.post_json("/_test/login", &body.to_string()).await;
185
186 let target_id = Uuid::new_v4();
187 sqlx::query(
188 "INSERT INTO users (mnw_account_id, username, display_name, suspended_at, suspension_reason)
189 VALUES ($1, 'baduser', 'Bad User', now(), 'abuse')",
190 )
191 .bind(target_id)
192 .execute(&h.db)
193 .await
194 .unwrap();
195
196 let resp = h
197 .client
198 .post_form(&format!("/_admin/users/{target_id}/unsuspend"), "")
199 .await;
200 assert!(resp.status.is_redirection() || resp.status == axum::http::StatusCode::OK);
201
202 let suspended: bool =
203 sqlx::query_scalar("SELECT suspended_at IS NOT NULL FROM users WHERE mnw_account_id = $1")
204 .bind(target_id)
205 .fetch_one(&h.db)
206 .await
207 .unwrap();
208 assert!(!suspended);
209 }
210
211 #[sqlx::test]
212 async fn admin_search_finds_users(_pool: sqlx::PgPool) {
213 let admin_id = Uuid::new_v4();
214 let mut h = TestHarness::new_with_admin(admin_id).await;
215
216 sqlx::query(
217 "INSERT INTO users (mnw_account_id, username, display_name)
218 VALUES ($1, 'admin', 'Admin') ON CONFLICT DO NOTHING",
219 )
220 .bind(admin_id)
221 .execute(&h.db)
222 .await
223 .unwrap();
224 h.client.get("/").await;
225 let body = serde_json::json!({ "user_id": admin_id.to_string(), "username": "admin" });
226 h.client.post_json("/_test/login", &body.to_string()).await;
227
228 // Create a searchable user
229 let target_id = Uuid::new_v4();
230 sqlx::query(
231 "INSERT INTO users (mnw_account_id, username, display_name)
232 VALUES ($1, 'findableuser', 'Findable User')",
233 )
234 .bind(target_id)
235 .execute(&h.db)
236 .await
237 .unwrap();
238
239 let resp = h.client.get("/_admin?q=findableuser").await;
240 assert_eq!(resp.status, axum::http::StatusCode::OK);
241 assert!(
242 resp.text.contains("findableuser"),
243 "Search results should include matching user"
244 );
245 }
246
247 #[sqlx::test]
248 async fn admin_invalid_uuid_returns_400(_pool: sqlx::PgPool) {
249 let admin_id = Uuid::new_v4();
250 let mut h = TestHarness::new_with_admin(admin_id).await;
251
252 sqlx::query(
253 "INSERT INTO users (mnw_account_id, username, display_name)
254 VALUES ($1, 'admin', 'Admin') ON CONFLICT DO NOTHING",
255 )
256 .bind(admin_id)
257 .execute(&h.db)
258 .await
259 .unwrap();
260 h.client.get("/").await;
261 let body = serde_json::json!({ "user_id": admin_id.to_string(), "username": "admin" });
262 h.client.post_json("/_test/login", &body.to_string()).await;
263
264 let resp = h
265 .client
266 .post_form("/_admin/communities/not-a-uuid/suspend", "reason=test")
267 .await;
268 // parse_uuid returns 404 (hides admin routes from probing)
269 assert_eq!(resp.status, axum::http::StatusCode::NOT_FOUND);
270 }
271
272 #[sqlx::test]
273 async fn admin_suspend_creates_mod_log_entry(_pool: sqlx::PgPool) {
274 let admin_id = Uuid::new_v4();
275 let mut h = TestHarness::new_with_admin(admin_id).await;
276
277 sqlx::query(
278 "INSERT INTO users (mnw_account_id, username, display_name)
279 VALUES ($1, 'admin', 'Admin') ON CONFLICT DO NOTHING",
280 )
281 .bind(admin_id)
282 .execute(&h.db)
283 .await
284 .unwrap();
285 h.client.get("/").await;
286 let body = serde_json::json!({ "user_id": admin_id.to_string(), "username": "admin" });
287 h.client.post_json("/_test/login", &body.to_string()).await;
288
289 let community_id = h.create_community("Test", "test").await;
290
291 h.client
292 .post_form(
293 &format!("/_admin/communities/{community_id}/suspend"),
294 "reason=policy+violation",
295 )
296 .await;
297
298 // Verify mod_log entry exists
299 let count: i64 = sqlx::query_scalar(
300 "SELECT COUNT(*) FROM mod_log WHERE action = 'suspend_community' AND actor_id = $1",
301 )
302 .bind(admin_id)
303 .fetch_one(&h.db)
304 .await
305 .unwrap();
306 assert_eq!(
307 count, 1,
308 "Should have a mod_log entry for suspend_community"
309 );
310 }
311
312 #[sqlx::test]
313 async fn non_admin_post_to_suspend_returns_404(_pool: sqlx::PgPool) {
314 let admin_id = Uuid::new_v4();
315 let mut h = TestHarness::new_with_admin(admin_id).await;
316 let _user = h.login_as("regular").await;
317
318 let community_id = h.create_community("Test", "test").await;
319
320 let resp = h
321 .client
322 .post_form(
323 &format!("/_admin/communities/{community_id}/suspend"),
324 "reason=test",
325 )
326 .await;
327 assert_eq!(resp.status, axum::http::StatusCode::NOT_FOUND);
328 }
329