//! Dedicated superadmin community view + clean-slate. //! //! Covers: //! * Admin community detail page renders for superadmin; 404 for others //! * Clean-slate wipes threads/posts, preserves settings/categories/members, //! posts a pinned+locked system thread, logs the mod action //! * Typed-phrase confirmation enforced use crate::harness::TestHarness; use axum::http::StatusCode; use uuid::Uuid; async fn login_admin(h: &mut TestHarness, admin_id: Uuid) { sqlx::query( "INSERT INTO users (mnw_account_id, username, display_name) \ VALUES ($1, 'superadmin', 'superadmin') ON CONFLICT (mnw_account_id) DO NOTHING", ) .bind(admin_id) .execute(&h.db) .await .unwrap(); h.client.get("/").await; h.client .post_json( "/_test/login", &serde_json::json!({ "user_id": admin_id.to_string(), "username": "superadmin" }) .to_string(), ) .await; } #[tokio::test] async fn admin_community_page_renders_for_superadmin() { let admin_id = Uuid::new_v4(); let mut h = TestHarness::new_with_admin(admin_id).await; let comm_id = h.create_community("Test", "test").await; let _cat_id = h.create_category(comm_id, "General", "general").await; login_admin(&mut h, admin_id).await; let resp = h.client.get("/_admin/communities/test").await; assert_eq!(resp.status, StatusCode::OK); assert!(resp.text.contains("Community administration")); assert!(resp.text.contains("Clean slate")); assert!(resp.text.contains("Moderation state")); } #[tokio::test] async fn admin_community_page_404_for_non_admin() { let mut h = TestHarness::new().await; let comm_id = h.create_community("Test", "test").await; let _cat_id = h.create_category(comm_id, "General", "general").await; let _ = h.login_as("notadmin").await; let resp = h.client.get("/_admin/communities/test").await; assert_eq!(resp.status, StatusCode::NOT_FOUND); } #[tokio::test] async fn clean_slate_wipes_threads_and_logs_action() { let admin_id = Uuid::new_v4(); let mut h = TestHarness::new_with_admin(admin_id).await; let comm_id = h.create_community("Test", "test").await; let cat_id = h.create_category(comm_id, "General", "general").await; // Seed an author + threads to wipe. let author_id = h.login_as("seedauthor").await; h.add_membership(author_id, comm_id, "member").await; let _thread_a = h.create_thread_with_post(cat_id, author_id, "First", "body A").await; let _thread_b = h.create_thread_with_post(cat_id, author_id, "Second", "body B").await; h.client.post_form("/auth/logout", "").await; login_admin(&mut h, admin_id).await; h.client.get("/_admin/communities/test").await; let resp = h .client .post_form("/_admin/communities/test/clean-slate", "confirm=test") .await; assert!(resp.status.is_redirection(), "status: {}", resp.status); // Originals gone; one system thread remains (pinned + locked). let titles: Vec<(String, bool, bool)> = sqlx::query_as( "SELECT t.title, t.pinned, t.locked FROM threads t JOIN categories c ON c.id = t.category_id WHERE c.community_id = $1", ) .bind(comm_id) .fetch_all(&h.db) .await .unwrap(); assert_eq!(titles.len(), 1, "expected only the system reset thread"); let (title, pinned, locked) = &titles[0]; assert!(title.starts_with("Community reset by superadmin")); assert!(*pinned, "system thread should be pinned"); assert!(*locked, "system thread should be locked"); // Mod log records the action. let action_count: i64 = sqlx::query_scalar( "SELECT COUNT(*) FROM mod_log WHERE community_id = $1 AND action = 'clean_slate_community'", ) .bind(comm_id) .fetch_one(&h.db) .await .unwrap(); assert_eq!(action_count, 1); } #[tokio::test] async fn clean_slate_preserves_categories_members_tags() { let admin_id = Uuid::new_v4(); let mut h = TestHarness::new_with_admin(admin_id).await; let comm_id = h.create_community("Test", "test").await; let cat_id = h.create_category(comm_id, "General", "general").await; let author_id = h.login_as("seed2").await; h.add_membership(author_id, comm_id, "member").await; h.create_thread_with_post(cat_id, author_id, "First", "body").await; h.client.post_form("/auth/logout", "").await; login_admin(&mut h, admin_id).await; h.client.get("/_admin/communities/test").await; h.client .post_form("/_admin/communities/test/clean-slate", "confirm=test") .await; // Category survives. let cat_count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM categories WHERE community_id = $1") .bind(comm_id) .fetch_one(&h.db) .await .unwrap(); assert!(cat_count >= 1); // Membership survives. let member_count: i64 = sqlx::query_scalar( "SELECT COUNT(*) FROM memberships WHERE community_id = $1 AND user_id = $2", ) .bind(comm_id) .bind(author_id) .fetch_one(&h.db) .await .unwrap(); assert_eq!(member_count, 1, "membership should be preserved"); } #[tokio::test] async fn clean_slate_rejects_wrong_confirmation_phrase() { let admin_id = Uuid::new_v4(); let mut h = TestHarness::new_with_admin(admin_id).await; let comm_id = h.create_community("Test", "test").await; let cat_id = h.create_category(comm_id, "General", "general").await; let author_id = h.login_as("seed3").await; h.add_membership(author_id, comm_id, "member").await; h.create_thread_with_post(cat_id, author_id, "Survive me", "body").await; h.client.post_form("/auth/logout", "").await; login_admin(&mut h, admin_id).await; h.client.get("/_admin/communities/test").await; let resp = h .client .post_form("/_admin/communities/test/clean-slate", "confirm=nope") .await; assert_eq!(resp.status, StatusCode::UNPROCESSABLE_ENTITY); // Threads survive. let count: i64 = sqlx::query_scalar( "SELECT COUNT(*) FROM threads t JOIN categories c ON c.id = t.category_id WHERE c.community_id = $1", ) .bind(comm_id) .fetch_one(&h.db) .await .unwrap(); assert_eq!(count, 1, "thread should still exist after rejected clean-slate"); } #[tokio::test] async fn clean_slate_404_for_non_admin() { let mut h = TestHarness::new().await; let comm_id = h.create_community("Test", "test").await; let _cat_id = h.create_category(comm_id, "General", "general").await; let _ = h.login_as("notadmin").await; h.client.get("/").await; let resp = h .client .post_form("/_admin/communities/test/clean-slate", "confirm=test") .await; assert_eq!(resp.status, StatusCode::NOT_FOUND); } #[tokio::test] async fn clean_slate_with_no_categories_skips_system_thread() { let admin_id = Uuid::new_v4(); let mut h = TestHarness::new_with_admin(admin_id).await; let comm_id = h.create_community("Empty", "empty").await; // No category — clean-slate should succeed but skip the system thread. login_admin(&mut h, admin_id).await; h.client.get("/_admin/communities/empty").await; let resp = h .client .post_form("/_admin/communities/empty/clean-slate", "confirm=empty") .await; assert!(resp.status.is_redirection(), "status: {}", resp.status); let count: i64 = sqlx::query_scalar( "SELECT COUNT(*) FROM threads t JOIN categories c ON c.id = t.category_id WHERE c.community_id = $1", ) .bind(comm_id) .fetch_one(&h.db) .await .unwrap(); assert_eq!(count, 0); }