Skip to main content

max / makenotwork

14.0 KB · 494 lines History Blame Raw
1 //! Admin workflow tests: suspend/unsuspend, trust/untrust, upload review, appeal decisions.
2
3 use crate::harness::TestHarness;
4 use makenotwork::db::UserId;
5
6 // ── User Suspension ──
7
8 #[tokio::test]
9 async fn admin_suspend_user() {
10 let (mut h, _admin_id) = TestHarness::with_admin().await;
11
12 let user_id = h.signup("susptarget", "susptarget@test.com", "password123").await;
13
14 // Log in as admin
15 h.client.post_form("/logout", "").await;
16 h.login("admin", "password123").await;
17
18 let resp = h
19 .client
20 .post_form(
21 &format!("/api/admin/users/{}/suspend", *user_id),
22 "reason=Violated+terms+of+service",
23 )
24 .await;
25 assert!(
26 resp.status.is_success(),
27 "Admin suspend failed: {} {}",
28 resp.status, resp.text
29 );
30
31 // Verify in DB
32 let (suspended, reason): (bool, Option<String>) = sqlx::query_as(
33 "SELECT (suspended_at IS NOT NULL), suspension_reason FROM users WHERE id = $1",
34 )
35 .bind(*user_id)
36 .fetch_one(&h.db)
37 .await
38 .unwrap();
39 assert!(suspended, "User should be suspended");
40 assert_eq!(reason.as_deref(), Some("Violated terms of service"));
41 }
42
43 #[tokio::test]
44 async fn admin_suspend_empty_reason_rejected() {
45 let (mut h, _admin_id) = TestHarness::with_admin().await;
46
47 let user_id = h.signup("suspempty", "suspempty@test.com", "password123").await;
48
49 h.client.post_form("/logout", "").await;
50 h.login("admin", "password123").await;
51
52 let resp = h
53 .client
54 .post_form(
55 &format!("/api/admin/users/{}/suspend", *user_id),
56 "reason=",
57 )
58 .await;
59 assert!(
60 resp.status.is_client_error(),
61 "Empty reason should be rejected: {} {}",
62 resp.status, resp.text
63 );
64 }
65
66 #[tokio::test]
67 async fn admin_unsuspend_user() {
68 let (mut h, _admin_id) = TestHarness::with_admin().await;
69
70 let user_id = h.signup("unsusptarget", "unsusptarget@test.com", "password123").await;
71 h.suspend_user(user_id).await;
72
73 h.client.post_form("/logout", "").await;
74 h.login("admin", "password123").await;
75
76 let resp = h
77 .client
78 .post_form(&format!("/api/admin/users/{}/unsuspend", *user_id), "")
79 .await;
80 assert!(
81 resp.status.is_success(),
82 "Admin unsuspend failed: {} {}",
83 resp.status, resp.text
84 );
85
86 // Verify cleared
87 let suspended: bool = sqlx::query_scalar(
88 "SELECT (suspended_at IS NOT NULL) FROM users WHERE id = $1",
89 )
90 .bind(*user_id)
91 .fetch_one(&h.db)
92 .await
93 .unwrap();
94 assert!(!suspended, "User should no longer be suspended");
95 }
96
97 // ── Trust Management ──
98
99 #[tokio::test]
100 async fn admin_trust_user() {
101 let (mut h, _admin_id) = TestHarness::with_admin().await;
102
103 let user_id = h.signup("trustme", "trustme@test.com", "password123").await;
104
105 h.client.post_form("/logout", "").await;
106 h.login("admin", "password123").await;
107
108 let resp = h
109 .client
110 .post_form(&format!("/api/admin/users/{}/trust", *user_id), "")
111 .await;
112 assert!(
113 resp.status.is_success(),
114 "Admin trust failed: {} {}",
115 resp.status, resp.text
116 );
117
118 let trusted: bool =
119 sqlx::query_scalar("SELECT upload_trusted FROM users WHERE id = $1")
120 .bind(*user_id)
121 .fetch_one(&h.db)
122 .await
123 .unwrap();
124 assert!(trusted, "User should be trusted for uploads");
125 }
126
127 #[tokio::test]
128 async fn admin_untrust_user() {
129 let (mut h, _admin_id) = TestHarness::with_admin().await;
130
131 let user_id = h.signup("untrustme", "untrustme@test.com", "password123").await;
132 h.trust_user(user_id).await;
133
134 h.client.post_form("/logout", "").await;
135 h.login("admin", "password123").await;
136
137 let resp = h
138 .client
139 .post_form(&format!("/api/admin/users/{}/untrust", *user_id), "")
140 .await;
141 assert!(
142 resp.status.is_success(),
143 "Admin untrust failed: {} {}",
144 resp.status, resp.text
145 );
146
147 let trusted: bool =
148 sqlx::query_scalar("SELECT upload_trusted FROM users WHERE id = $1")
149 .bind(*user_id)
150 .fetch_one(&h.db)
151 .await
152 .unwrap();
153 assert!(!trusted, "User should no longer be trusted for uploads");
154 }
155
156 // ── Upload Review ──
157
158 /// Helper: create a project and item via SQL, set item to held_for_review.
159 async fn create_held_item(db: &sqlx::PgPool, user_id: UserId) -> uuid::Uuid {
160 let project_id: uuid::Uuid = sqlx::query_scalar(
161 "INSERT INTO projects (user_id, slug, title) VALUES ($1, 'held-proj', 'Held Project') RETURNING id",
162 )
163 .bind(*user_id)
164 .fetch_one(db)
165 .await
166 .unwrap();
167
168 let item_id: uuid::Uuid = sqlx::query_scalar(
169 "INSERT INTO items (project_id, title, price_cents, item_type, scan_status, slug) \
170 VALUES ($1, 'Held Item', 0, 'audio', 'held_for_review', 'held-item-' || $1::text) RETURNING id",
171 )
172 .bind(project_id)
173 .fetch_one(db)
174 .await
175 .unwrap();
176
177 item_id
178 }
179
180 /// Helper: create a version for an item, set to held_for_review.
181 async fn create_held_version(db: &sqlx::PgPool, item_id: uuid::Uuid) -> uuid::Uuid {
182 let version_id: uuid::Uuid = sqlx::query_scalar(
183 "INSERT INTO versions (item_id, version_number, scan_status) \
184 VALUES ($1, '1.0', 'held_for_review') RETURNING id",
185 )
186 .bind(item_id)
187 .fetch_one(db)
188 .await
189 .unwrap();
190
191 version_id
192 }
193
194 #[tokio::test]
195 async fn admin_approve_item_upload() {
196 let (mut h, _admin_id) = TestHarness::with_admin().await;
197
198 let user_id = h.signup("itemapprove", "itemapprove@test.com", "password123").await;
199 h.grant_creator(user_id).await;
200 let item_id = create_held_item(&h.db, user_id).await;
201
202 h.client.post_form("/logout", "").await;
203 h.login("admin", "password123").await;
204
205 let resp = h
206 .client
207 .post_form(&format!("/api/admin/uploads/items/{}/promote", item_id), "")
208 .await;
209 assert!(
210 resp.status.is_success(),
211 "Approve item failed: {} {}",
212 resp.status, resp.text
213 );
214
215 let status: String =
216 sqlx::query_scalar("SELECT scan_status FROM items WHERE id = $1")
217 .bind(item_id)
218 .fetch_one(&h.db)
219 .await
220 .unwrap();
221 assert_eq!(status, "clean");
222 }
223
224 #[tokio::test]
225 async fn admin_reject_item_upload() {
226 let (mut h, _admin_id) = TestHarness::with_admin().await;
227
228 let user_id = h.signup("itemreject", "itemreject@test.com", "password123").await;
229 h.grant_creator(user_id).await;
230 let item_id = create_held_item(&h.db, user_id).await;
231
232 h.client.post_form("/logout", "").await;
233 h.login("admin", "password123").await;
234
235 let resp = h
236 .client
237 .post_form(&format!("/api/admin/uploads/items/{}/quarantine", item_id), "")
238 .await;
239 assert!(
240 resp.status.is_success(),
241 "Reject item failed: {} {}",
242 resp.status, resp.text
243 );
244
245 let status: String =
246 sqlx::query_scalar("SELECT scan_status FROM items WHERE id = $1")
247 .bind(item_id)
248 .fetch_one(&h.db)
249 .await
250 .unwrap();
251 assert_eq!(status, "quarantined");
252 }
253
254 #[tokio::test]
255 async fn admin_approve_version_upload() {
256 let (mut h, _admin_id) = TestHarness::with_admin().await;
257
258 let user_id = h.signup("verapprove", "verapprove@test.com", "password123").await;
259 h.grant_creator(user_id).await;
260 let item_id = create_held_item(&h.db, user_id).await;
261 let version_id = create_held_version(&h.db, item_id).await;
262
263 h.client.post_form("/logout", "").await;
264 h.login("admin", "password123").await;
265
266 let resp = h
267 .client
268 .post_form(
269 &format!("/api/admin/uploads/versions/{}/promote", version_id),
270 "",
271 )
272 .await;
273 assert!(
274 resp.status.is_success(),
275 "Approve version failed: {} {}",
276 resp.status, resp.text
277 );
278
279 let status: String =
280 sqlx::query_scalar("SELECT scan_status FROM versions WHERE id = $1")
281 .bind(version_id)
282 .fetch_one(&h.db)
283 .await
284 .unwrap();
285 assert_eq!(status, "clean");
286 }
287
288 #[tokio::test]
289 async fn admin_reject_version_upload() {
290 let (mut h, _admin_id) = TestHarness::with_admin().await;
291
292 let user_id = h.signup("verreject", "verreject@test.com", "password123").await;
293 h.grant_creator(user_id).await;
294 let item_id = create_held_item(&h.db, user_id).await;
295 let version_id = create_held_version(&h.db, item_id).await;
296
297 h.client.post_form("/logout", "").await;
298 h.login("admin", "password123").await;
299
300 let resp = h
301 .client
302 .post_form(
303 &format!("/api/admin/uploads/versions/{}/quarantine", version_id),
304 "",
305 )
306 .await;
307 assert!(
308 resp.status.is_success(),
309 "Reject version failed: {} {}",
310 resp.status, resp.text
311 );
312
313 let status: String =
314 sqlx::query_scalar("SELECT scan_status FROM versions WHERE id = $1")
315 .bind(version_id)
316 .fetch_one(&h.db)
317 .await
318 .unwrap();
319 assert_eq!(status, "quarantined");
320 }
321
322 // ── Appeal Decisions ──
323
324 #[tokio::test]
325 async fn admin_approve_appeal() {
326 let (mut h, _admin_id) = TestHarness::with_admin().await;
327
328 let user_id = h.signup("appealapprove", "appealapprove@test.com", "password123").await;
329 h.suspend_user(user_id).await;
330
331 // User submits appeal
332 h.client.post_form("/logout", "").await;
333 h.login("appealapprove", "password123").await;
334 let resp = h
335 .client
336 .post_form(
337 "/api/users/me/appeal",
338 "appeal_text=I+believe+this+was+a+mistake",
339 )
340 .await;
341 assert!(
342 resp.status.is_success() || resp.status == 204,
343 "Appeal submission failed: {} {}",
344 resp.status, resp.text
345 );
346
347 // Admin decides
348 h.client.post_form("/logout", "").await;
349 h.login("admin", "password123").await;
350 let resp = h
351 .client
352 .post_form(
353 &format!("/api/admin/appeals/{}/decide", *user_id),
354 "decision=approved&response=Suspension+was+a+mistake",
355 )
356 .await;
357 assert!(
358 resp.status.is_success(),
359 "Admin approve appeal failed: {} {}",
360 resp.status, resp.text
361 );
362
363 // Verify: user is unsuspended and appeal decision recorded
364 let (suspended, decision): (bool, Option<String>) = sqlx::query_as(
365 "SELECT (suspended_at IS NOT NULL), appeal_decision FROM users WHERE id = $1",
366 )
367 .bind(*user_id)
368 .fetch_one(&h.db)
369 .await
370 .unwrap();
371 assert!(!suspended, "User should be unsuspended after approved appeal");
372 assert_eq!(decision.as_deref(), Some("approved"));
373 }
374
375 #[tokio::test]
376 async fn admin_deny_appeal() {
377 let (mut h, _admin_id) = TestHarness::with_admin().await;
378
379 let user_id = h.signup("appealdeny", "appealdeny@test.com", "password123").await;
380 h.suspend_user(user_id).await;
381
382 // User submits appeal
383 h.client.post_form("/logout", "").await;
384 h.login("appealdeny", "password123").await;
385 h.client
386 .post_form(
387 "/api/users/me/appeal",
388 "appeal_text=Please+reconsider+my+case",
389 )
390 .await;
391
392 // Admin denies
393 h.client.post_form("/logout", "").await;
394 h.login("admin", "password123").await;
395 let resp = h
396 .client
397 .post_form(
398 &format!("/api/admin/appeals/{}/decide", *user_id),
399 "decision=denied&response=Violation+confirmed",
400 )
401 .await;
402 assert!(
403 resp.status.is_success(),
404 "Admin deny appeal failed: {} {}",
405 resp.status, resp.text
406 );
407
408 // Verify: user stays suspended, decision recorded
409 let (suspended, decision): (bool, Option<String>) = sqlx::query_as(
410 "SELECT (suspended_at IS NOT NULL), appeal_decision FROM users WHERE id = $1",
411 )
412 .bind(*user_id)
413 .fetch_one(&h.db)
414 .await
415 .unwrap();
416 assert!(suspended, "User should remain suspended after denied appeal");
417 assert_eq!(decision.as_deref(), Some("denied"));
418 }
419
420 #[tokio::test]
421 async fn admin_appeal_empty_response_rejected() {
422 let (mut h, _admin_id) = TestHarness::with_admin().await;
423
424 let user_id = h.signup("appealemptyresp", "appealemptyresp@test.com", "password123").await;
425 h.suspend_user(user_id).await;
426
427 // User submits appeal
428 h.client.post_form("/logout", "").await;
429 h.login("appealemptyresp", "password123").await;
430 h.client
431 .post_form(
432 "/api/users/me/appeal",
433 "appeal_text=Please+reconsider",
434 )
435 .await;
436
437 // Admin tries to decide with empty response
438 h.client.post_form("/logout", "").await;
439 h.login("admin", "password123").await;
440 let resp = h
441 .client
442 .post_form(
443 &format!("/api/admin/appeals/{}/decide", *user_id),
444 "decision=approved&response=",
445 )
446 .await;
447 assert!(
448 resp.status.is_client_error(),
449 "Empty response should be rejected: {} {}",
450 resp.status, resp.text
451 );
452 }
453
454 // ── Non-Admin Access ──
455
456 #[tokio::test]
457 async fn non_admin_suspend_gets_404() {
458 let (mut h, _admin_id) = TestHarness::with_admin().await;
459
460 let user_id = h.signup("nonadminsus", "nonadminsus@test.com", "password123").await;
461
462 // Regular user tries admin suspend route
463 let resp = h
464 .client
465 .post_form(
466 &format!("/api/admin/users/{}/suspend", *user_id),
467 "reason=hacking",
468 )
469 .await;
470 assert_eq!(
471 resp.status, 404,
472 "Non-admin suspend should be 404, got {} {}",
473 resp.status, resp.text
474 );
475 }
476
477 #[tokio::test]
478 async fn non_admin_upload_review_gets_404() {
479 let (mut h, _admin_id) = TestHarness::with_admin().await;
480
481 let _user_id = h.signup("nonadminupload", "nonadminupload@test.com", "password123").await;
482
483 let fake_id = uuid::Uuid::new_v4();
484 let resp = h
485 .client
486 .post_form(&format!("/api/admin/uploads/items/{}/promote", fake_id), "")
487 .await;
488 assert_eq!(
489 resp.status, 404,
490 "Non-admin item approve should be 404, got {} {}",
491 resp.status, resp.text
492 );
493 }
494