Skip to main content

max / makenotwork

7.6 KB · 217 lines History Blame Raw
1 //! Dedicated superadmin community view + clean-slate.
2 //!
3 //! Covers:
4 //! * Admin community detail page renders for superadmin; 404 for others
5 //! * Clean-slate wipes threads/posts, preserves settings/categories/members,
6 //! posts a pinned+locked system thread, logs the mod action
7 //! * Typed-phrase confirmation enforced
8
9 use crate::harness::TestHarness;
10 use axum::http::StatusCode;
11 use uuid::Uuid;
12
13 async fn login_admin(h: &mut TestHarness, admin_id: Uuid) {
14 sqlx::query(
15 "INSERT INTO users (mnw_account_id, username, display_name) \
16 VALUES ($1, 'superadmin', 'superadmin') ON CONFLICT (mnw_account_id) DO NOTHING",
17 )
18 .bind(admin_id)
19 .execute(&h.db)
20 .await
21 .unwrap();
22 h.client.get("/").await;
23 h.client
24 .post_json(
25 "/_test/login",
26 &serde_json::json!({ "user_id": admin_id.to_string(), "username": "superadmin" })
27 .to_string(),
28 )
29 .await;
30 }
31
32 #[tokio::test]
33 async fn admin_community_page_renders_for_superadmin() {
34 let admin_id = Uuid::new_v4();
35 let mut h = TestHarness::new_with_admin(admin_id).await;
36 let comm_id = h.create_community("Test", "test").await;
37 let _cat_id = h.create_category(comm_id, "General", "general").await;
38 login_admin(&mut h, admin_id).await;
39
40 let resp = h.client.get("/_admin/communities/test").await;
41 assert_eq!(resp.status, StatusCode::OK);
42 assert!(resp.text.contains("Community administration"));
43 assert!(resp.text.contains("Clean slate"));
44 assert!(resp.text.contains("Moderation state"));
45 }
46
47 #[tokio::test]
48 async fn admin_community_page_404_for_non_admin() {
49 let mut h = TestHarness::new().await;
50 let comm_id = h.create_community("Test", "test").await;
51 let _cat_id = h.create_category(comm_id, "General", "general").await;
52 let _ = h.login_as("notadmin").await;
53
54 let resp = h.client.get("/_admin/communities/test").await;
55 assert_eq!(resp.status, StatusCode::NOT_FOUND);
56 }
57
58 #[tokio::test]
59 async fn clean_slate_wipes_threads_and_logs_action() {
60 let admin_id = Uuid::new_v4();
61 let mut h = TestHarness::new_with_admin(admin_id).await;
62 let comm_id = h.create_community("Test", "test").await;
63 let cat_id = h.create_category(comm_id, "General", "general").await;
64
65 // Seed an author + threads to wipe.
66 let author_id = h.login_as("seedauthor").await;
67 h.add_membership(author_id, comm_id, "member").await;
68 let _thread_a = h.create_thread_with_post(cat_id, author_id, "First", "body A").await;
69 let _thread_b = h.create_thread_with_post(cat_id, author_id, "Second", "body B").await;
70 h.client.post_form("/auth/logout", "").await;
71
72 login_admin(&mut h, admin_id).await;
73 h.client.get("/_admin/communities/test").await;
74 let resp = h
75 .client
76 .post_form("/_admin/communities/test/clean-slate", "confirm=test")
77 .await;
78 assert!(resp.status.is_redirection(), "status: {}", resp.status);
79
80 // Originals gone; one system thread remains (pinned + locked).
81 let titles: Vec<(String, bool, bool)> = sqlx::query_as(
82 "SELECT t.title, t.pinned, t.locked
83 FROM threads t JOIN categories c ON c.id = t.category_id
84 WHERE c.community_id = $1",
85 )
86 .bind(comm_id)
87 .fetch_all(&h.db)
88 .await
89 .unwrap();
90 assert_eq!(titles.len(), 1, "expected only the system reset thread");
91 let (title, pinned, locked) = &titles[0];
92 assert!(title.starts_with("Community reset by superadmin"));
93 assert!(*pinned, "system thread should be pinned");
94 assert!(*locked, "system thread should be locked");
95
96 // Mod log records the action.
97 let action_count: i64 = sqlx::query_scalar(
98 "SELECT COUNT(*) FROM mod_log
99 WHERE community_id = $1 AND action = 'clean_slate_community'",
100 )
101 .bind(comm_id)
102 .fetch_one(&h.db)
103 .await
104 .unwrap();
105 assert_eq!(action_count, 1);
106 }
107
108 #[tokio::test]
109 async fn clean_slate_preserves_categories_members_tags() {
110 let admin_id = Uuid::new_v4();
111 let mut h = TestHarness::new_with_admin(admin_id).await;
112 let comm_id = h.create_community("Test", "test").await;
113 let cat_id = h.create_category(comm_id, "General", "general").await;
114
115 let author_id = h.login_as("seed2").await;
116 h.add_membership(author_id, comm_id, "member").await;
117 h.create_thread_with_post(cat_id, author_id, "First", "body").await;
118 h.client.post_form("/auth/logout", "").await;
119
120 login_admin(&mut h, admin_id).await;
121 h.client.get("/_admin/communities/test").await;
122 h.client
123 .post_form("/_admin/communities/test/clean-slate", "confirm=test")
124 .await;
125
126 // Category survives.
127 let cat_count: i64 =
128 sqlx::query_scalar("SELECT COUNT(*) FROM categories WHERE community_id = $1")
129 .bind(comm_id)
130 .fetch_one(&h.db)
131 .await
132 .unwrap();
133 assert!(cat_count >= 1);
134
135 // Membership survives.
136 let member_count: i64 = sqlx::query_scalar(
137 "SELECT COUNT(*) FROM memberships WHERE community_id = $1 AND user_id = $2",
138 )
139 .bind(comm_id)
140 .bind(author_id)
141 .fetch_one(&h.db)
142 .await
143 .unwrap();
144 assert_eq!(member_count, 1, "membership should be preserved");
145 }
146
147 #[tokio::test]
148 async fn clean_slate_rejects_wrong_confirmation_phrase() {
149 let admin_id = Uuid::new_v4();
150 let mut h = TestHarness::new_with_admin(admin_id).await;
151 let comm_id = h.create_community("Test", "test").await;
152 let cat_id = h.create_category(comm_id, "General", "general").await;
153 let author_id = h.login_as("seed3").await;
154 h.add_membership(author_id, comm_id, "member").await;
155 h.create_thread_with_post(cat_id, author_id, "Survive me", "body").await;
156 h.client.post_form("/auth/logout", "").await;
157
158 login_admin(&mut h, admin_id).await;
159 h.client.get("/_admin/communities/test").await;
160 let resp = h
161 .client
162 .post_form("/_admin/communities/test/clean-slate", "confirm=nope")
163 .await;
164 assert_eq!(resp.status, StatusCode::UNPROCESSABLE_ENTITY);
165
166 // Threads survive.
167 let count: i64 = sqlx::query_scalar(
168 "SELECT COUNT(*) FROM threads t JOIN categories c ON c.id = t.category_id
169 WHERE c.community_id = $1",
170 )
171 .bind(comm_id)
172 .fetch_one(&h.db)
173 .await
174 .unwrap();
175 assert_eq!(count, 1, "thread should still exist after rejected clean-slate");
176 }
177
178 #[tokio::test]
179 async fn clean_slate_404_for_non_admin() {
180 let mut h = TestHarness::new().await;
181 let comm_id = h.create_community("Test", "test").await;
182 let _cat_id = h.create_category(comm_id, "General", "general").await;
183 let _ = h.login_as("notadmin").await;
184
185 h.client.get("/").await;
186 let resp = h
187 .client
188 .post_form("/_admin/communities/test/clean-slate", "confirm=test")
189 .await;
190 assert_eq!(resp.status, StatusCode::NOT_FOUND);
191 }
192
193 #[tokio::test]
194 async fn clean_slate_with_no_categories_skips_system_thread() {
195 let admin_id = Uuid::new_v4();
196 let mut h = TestHarness::new_with_admin(admin_id).await;
197 let comm_id = h.create_community("Empty", "empty").await;
198 // No category — clean-slate should succeed but skip the system thread.
199 login_admin(&mut h, admin_id).await;
200 h.client.get("/_admin/communities/empty").await;
201 let resp = h
202 .client
203 .post_form("/_admin/communities/empty/clean-slate", "confirm=empty")
204 .await;
205 assert!(resp.status.is_redirection(), "status: {}", resp.status);
206
207 let count: i64 = sqlx::query_scalar(
208 "SELECT COUNT(*) FROM threads t JOIN categories c ON c.id = t.category_id
209 WHERE c.community_id = $1",
210 )
211 .bind(comm_id)
212 .fetch_one(&h.db)
213 .await
214 .unwrap();
215 assert_eq!(count, 0);
216 }
217