//! Admin workflow tests: suspend/unsuspend, trust/untrust, upload review, appeal decisions. use crate::harness::TestHarness; use makenotwork::db::UserId; // ── User Suspension ── #[tokio::test] async fn admin_suspend_user() { let (mut h, _admin_id) = TestHarness::with_admin().await; let user_id = h.signup("susptarget", "susptarget@test.com", "password123").await; // Log in as admin h.client.post_form("/logout", "").await; h.login("admin", "password123").await; let resp = h .client .post_form( &format!("/api/admin/users/{}/suspend", *user_id), "reason=Violated+terms+of+service", ) .await; assert!( resp.status.is_success(), "Admin suspend failed: {} {}", resp.status, resp.text ); // Verify in DB let (suspended, reason): (bool, Option) = sqlx::query_as( "SELECT (suspended_at IS NOT NULL), suspension_reason FROM users WHERE id = $1", ) .bind(*user_id) .fetch_one(&h.db) .await .unwrap(); assert!(suspended, "User should be suspended"); assert_eq!(reason.as_deref(), Some("Violated terms of service")); } #[tokio::test] async fn admin_suspend_empty_reason_rejected() { let (mut h, _admin_id) = TestHarness::with_admin().await; let user_id = h.signup("suspempty", "suspempty@test.com", "password123").await; h.client.post_form("/logout", "").await; h.login("admin", "password123").await; let resp = h .client .post_form( &format!("/api/admin/users/{}/suspend", *user_id), "reason=", ) .await; assert!( resp.status.is_client_error(), "Empty reason should be rejected: {} {}", resp.status, resp.text ); } #[tokio::test] async fn admin_unsuspend_user() { let (mut h, _admin_id) = TestHarness::with_admin().await; let user_id = h.signup("unsusptarget", "unsusptarget@test.com", "password123").await; h.suspend_user(user_id).await; h.client.post_form("/logout", "").await; h.login("admin", "password123").await; let resp = h .client .post_form(&format!("/api/admin/users/{}/unsuspend", *user_id), "") .await; assert!( resp.status.is_success(), "Admin unsuspend failed: {} {}", resp.status, resp.text ); // Verify cleared let suspended: bool = sqlx::query_scalar( "SELECT (suspended_at IS NOT NULL) FROM users WHERE id = $1", ) .bind(*user_id) .fetch_one(&h.db) .await .unwrap(); assert!(!suspended, "User should no longer be suspended"); } // ── Trust Management ── #[tokio::test] async fn admin_trust_user() { let (mut h, _admin_id) = TestHarness::with_admin().await; let user_id = h.signup("trustme", "trustme@test.com", "password123").await; h.client.post_form("/logout", "").await; h.login("admin", "password123").await; let resp = h .client .post_form(&format!("/api/admin/users/{}/trust", *user_id), "") .await; assert!( resp.status.is_success(), "Admin trust failed: {} {}", resp.status, resp.text ); let trusted: bool = sqlx::query_scalar("SELECT upload_trusted FROM users WHERE id = $1") .bind(*user_id) .fetch_one(&h.db) .await .unwrap(); assert!(trusted, "User should be trusted for uploads"); } #[tokio::test] async fn admin_untrust_user() { let (mut h, _admin_id) = TestHarness::with_admin().await; let user_id = h.signup("untrustme", "untrustme@test.com", "password123").await; h.trust_user(user_id).await; h.client.post_form("/logout", "").await; h.login("admin", "password123").await; let resp = h .client .post_form(&format!("/api/admin/users/{}/untrust", *user_id), "") .await; assert!( resp.status.is_success(), "Admin untrust failed: {} {}", resp.status, resp.text ); let trusted: bool = sqlx::query_scalar("SELECT upload_trusted FROM users WHERE id = $1") .bind(*user_id) .fetch_one(&h.db) .await .unwrap(); assert!(!trusted, "User should no longer be trusted for uploads"); } // ── Upload Review ── /// Helper: create a project and item via SQL, set item to held_for_review. async fn create_held_item(db: &sqlx::PgPool, user_id: UserId) -> uuid::Uuid { let project_id: uuid::Uuid = sqlx::query_scalar( "INSERT INTO projects (user_id, slug, title) VALUES ($1, 'held-proj', 'Held Project') RETURNING id", ) .bind(*user_id) .fetch_one(db) .await .unwrap(); let item_id: uuid::Uuid = sqlx::query_scalar( "INSERT INTO items (project_id, title, price_cents, item_type, scan_status, slug) \ VALUES ($1, 'Held Item', 0, 'audio', 'held_for_review', 'held-item-' || $1::text) RETURNING id", ) .bind(project_id) .fetch_one(db) .await .unwrap(); item_id } /// Helper: create a version for an item, set to held_for_review. async fn create_held_version(db: &sqlx::PgPool, item_id: uuid::Uuid) -> uuid::Uuid { let version_id: uuid::Uuid = sqlx::query_scalar( "INSERT INTO versions (item_id, version_number, scan_status) \ VALUES ($1, '1.0', 'held_for_review') RETURNING id", ) .bind(item_id) .fetch_one(db) .await .unwrap(); version_id } #[tokio::test] async fn admin_approve_item_upload() { let (mut h, _admin_id) = TestHarness::with_admin().await; let user_id = h.signup("itemapprove", "itemapprove@test.com", "password123").await; h.grant_creator(user_id).await; let item_id = create_held_item(&h.db, user_id).await; h.client.post_form("/logout", "").await; h.login("admin", "password123").await; let resp = h .client .post_form(&format!("/api/admin/uploads/items/{}/promote", item_id), "") .await; assert!( resp.status.is_success(), "Approve item failed: {} {}", resp.status, resp.text ); let status: String = sqlx::query_scalar("SELECT scan_status FROM items WHERE id = $1") .bind(item_id) .fetch_one(&h.db) .await .unwrap(); assert_eq!(status, "clean"); } #[tokio::test] async fn admin_reject_item_upload() { let (mut h, _admin_id) = TestHarness::with_admin().await; let user_id = h.signup("itemreject", "itemreject@test.com", "password123").await; h.grant_creator(user_id).await; let item_id = create_held_item(&h.db, user_id).await; h.client.post_form("/logout", "").await; h.login("admin", "password123").await; let resp = h .client .post_form(&format!("/api/admin/uploads/items/{}/quarantine", item_id), "") .await; assert!( resp.status.is_success(), "Reject item failed: {} {}", resp.status, resp.text ); let status: String = sqlx::query_scalar("SELECT scan_status FROM items WHERE id = $1") .bind(item_id) .fetch_one(&h.db) .await .unwrap(); assert_eq!(status, "quarantined"); } #[tokio::test] async fn admin_approve_version_upload() { let (mut h, _admin_id) = TestHarness::with_admin().await; let user_id = h.signup("verapprove", "verapprove@test.com", "password123").await; h.grant_creator(user_id).await; let item_id = create_held_item(&h.db, user_id).await; let version_id = create_held_version(&h.db, item_id).await; h.client.post_form("/logout", "").await; h.login("admin", "password123").await; let resp = h .client .post_form( &format!("/api/admin/uploads/versions/{}/promote", version_id), "", ) .await; assert!( resp.status.is_success(), "Approve version failed: {} {}", resp.status, resp.text ); let status: String = sqlx::query_scalar("SELECT scan_status FROM versions WHERE id = $1") .bind(version_id) .fetch_one(&h.db) .await .unwrap(); assert_eq!(status, "clean"); } #[tokio::test] async fn admin_reject_version_upload() { let (mut h, _admin_id) = TestHarness::with_admin().await; let user_id = h.signup("verreject", "verreject@test.com", "password123").await; h.grant_creator(user_id).await; let item_id = create_held_item(&h.db, user_id).await; let version_id = create_held_version(&h.db, item_id).await; h.client.post_form("/logout", "").await; h.login("admin", "password123").await; let resp = h .client .post_form( &format!("/api/admin/uploads/versions/{}/quarantine", version_id), "", ) .await; assert!( resp.status.is_success(), "Reject version failed: {} {}", resp.status, resp.text ); let status: String = sqlx::query_scalar("SELECT scan_status FROM versions WHERE id = $1") .bind(version_id) .fetch_one(&h.db) .await .unwrap(); assert_eq!(status, "quarantined"); } // ── Appeal Decisions ── #[tokio::test] async fn admin_approve_appeal() { let (mut h, _admin_id) = TestHarness::with_admin().await; let user_id = h.signup("appealapprove", "appealapprove@test.com", "password123").await; h.suspend_user(user_id).await; // User submits appeal h.client.post_form("/logout", "").await; h.login("appealapprove", "password123").await; let resp = h .client .post_form( "/api/users/me/appeal", "appeal_text=I+believe+this+was+a+mistake", ) .await; assert!( resp.status.is_success() || resp.status == 204, "Appeal submission failed: {} {}", resp.status, resp.text ); // Admin decides h.client.post_form("/logout", "").await; h.login("admin", "password123").await; let resp = h .client .post_form( &format!("/api/admin/appeals/{}/decide", *user_id), "decision=approved&response=Suspension+was+a+mistake", ) .await; assert!( resp.status.is_success(), "Admin approve appeal failed: {} {}", resp.status, resp.text ); // Verify: user is unsuspended and appeal decision recorded let (suspended, decision): (bool, Option) = sqlx::query_as( "SELECT (suspended_at IS NOT NULL), appeal_decision FROM users WHERE id = $1", ) .bind(*user_id) .fetch_one(&h.db) .await .unwrap(); assert!(!suspended, "User should be unsuspended after approved appeal"); assert_eq!(decision.as_deref(), Some("approved")); } #[tokio::test] async fn admin_deny_appeal() { let (mut h, _admin_id) = TestHarness::with_admin().await; let user_id = h.signup("appealdeny", "appealdeny@test.com", "password123").await; h.suspend_user(user_id).await; // User submits appeal h.client.post_form("/logout", "").await; h.login("appealdeny", "password123").await; h.client .post_form( "/api/users/me/appeal", "appeal_text=Please+reconsider+my+case", ) .await; // Admin denies h.client.post_form("/logout", "").await; h.login("admin", "password123").await; let resp = h .client .post_form( &format!("/api/admin/appeals/{}/decide", *user_id), "decision=denied&response=Violation+confirmed", ) .await; assert!( resp.status.is_success(), "Admin deny appeal failed: {} {}", resp.status, resp.text ); // Verify: user stays suspended, decision recorded let (suspended, decision): (bool, Option) = sqlx::query_as( "SELECT (suspended_at IS NOT NULL), appeal_decision FROM users WHERE id = $1", ) .bind(*user_id) .fetch_one(&h.db) .await .unwrap(); assert!(suspended, "User should remain suspended after denied appeal"); assert_eq!(decision.as_deref(), Some("denied")); } #[tokio::test] async fn admin_appeal_empty_response_rejected() { let (mut h, _admin_id) = TestHarness::with_admin().await; let user_id = h.signup("appealemptyresp", "appealemptyresp@test.com", "password123").await; h.suspend_user(user_id).await; // User submits appeal h.client.post_form("/logout", "").await; h.login("appealemptyresp", "password123").await; h.client .post_form( "/api/users/me/appeal", "appeal_text=Please+reconsider", ) .await; // Admin tries to decide with empty response h.client.post_form("/logout", "").await; h.login("admin", "password123").await; let resp = h .client .post_form( &format!("/api/admin/appeals/{}/decide", *user_id), "decision=approved&response=", ) .await; assert!( resp.status.is_client_error(), "Empty response should be rejected: {} {}", resp.status, resp.text ); } // ── Non-Admin Access ── #[tokio::test] async fn non_admin_suspend_gets_404() { let (mut h, _admin_id) = TestHarness::with_admin().await; let user_id = h.signup("nonadminsus", "nonadminsus@test.com", "password123").await; // Regular user tries admin suspend route let resp = h .client .post_form( &format!("/api/admin/users/{}/suspend", *user_id), "reason=hacking", ) .await; assert_eq!( resp.status, 404, "Non-admin suspend should be 404, got {} {}", resp.status, resp.text ); } #[tokio::test] async fn non_admin_upload_review_gets_404() { let (mut h, _admin_id) = TestHarness::with_admin().await; let _user_id = h.signup("nonadminupload", "nonadminupload@test.com", "password123").await; let fake_id = uuid::Uuid::new_v4(); let resp = h .client .post_form(&format!("/api/admin/uploads/items/{}/promote", fake_id), "") .await; assert_eq!( resp.status, 404, "Non-admin item approve should be 404, got {} {}", resp.status, resp.text ); }