Skip to main content

max / makenotwork

11.0 KB · 345 lines History Blame Raw
1 use crate::harness::TestHarness;
2
3 #[tokio::test]
4 async fn directory_counts_categories_and_threads_per_community() {
5 // Pins the scalar-subquery rewrite of list_communities (P1): per-community
6 // category_count and thread_count must match the old COUNT(DISTINCT) join.
7 let mut h = TestHarness::new().await;
8 let comm = h.create_community("Counted", "counted").await;
9 let cat_a = h.create_category(comm, "Alpha", "alpha").await;
10 let cat_b = h.create_category(comm, "Beta", "beta").await;
11 let author = h.login_as("writer").await;
12 h.add_membership(author, comm, "member").await;
13 // 2 threads in alpha, 1 in beta → 3 threads, 2 categories.
14 h.create_thread_with_post(cat_a, author, "T1", "b").await;
15 h.create_thread_with_post(cat_a, author, "T2", "b").await;
16 h.create_thread_with_post(cat_b, author, "T3", "b").await;
17
18 // A second community with no content stays at zero, proves counts are
19 // per-community, not global.
20 h.create_community("Empty", "empty").await;
21
22 let rows = mt_db::queries::list_communities(&h.db, 25, 0)
23 .await
24 .unwrap();
25 let counted = rows
26 .iter()
27 .find(|r| r.slug == "counted")
28 .expect("counted present");
29 assert_eq!(counted.category_count, 2, "two categories");
30 assert_eq!(counted.thread_count, 3, "three threads across categories");
31 let empty = rows
32 .iter()
33 .find(|r| r.slug == "empty")
34 .expect("empty present");
35 assert_eq!(empty.category_count, 0);
36 assert_eq!(empty.thread_count, 0);
37 }
38
39 #[tokio::test]
40 async fn directory_pagination_shows_limited_communities() {
41 let mut h = TestHarness::new().await;
42
43 // Create 30 communities (page size is 25)
44 for i in 0..30 {
45 h.create_community(&format!("Community {i:02}"), &format!("comm-{i:02}"))
46 .await;
47 }
48
49 let resp = h.client.get("/").await;
50 assert!(resp.status.is_success());
51 assert!(
52 resp.text.contains("Page 1 of 2"),
53 "Should show pagination info: {}",
54 &resp.text[..200.min(resp.text.len())]
55 );
56 assert!(
57 resp.text.contains("Next"),
58 "Should show Next link on page 1"
59 );
60 assert!(
61 !resp.text.contains("Previous"),
62 "Should not show Previous link on page 1"
63 );
64
65 let resp = h.client.get("/?page=2").await;
66 assert!(resp.status.is_success());
67 assert!(
68 resp.text.contains("Previous"),
69 "Should show Previous link on page 2"
70 );
71 }
72
73 #[tokio::test]
74 async fn empty_category_shows_no_threads() {
75 let mut h = TestHarness::new().await;
76 let comm_id = h.create_community("Test", "test").await;
77 let _cat_id = h.create_category(comm_id, "General", "general").await;
78
79 let resp = h.client.get("/p/test/general").await;
80
81 assert!(resp.status.is_success());
82 assert!(
83 resp.text.contains("No threads yet"),
84 "expected empty state message"
85 );
86 assert!(
87 !resp.text.contains("pagination"),
88 "pagination should not appear with 0 threads"
89 );
90 }
91
92 #[tokio::test]
93 async fn page_beyond_max_shows_last_page() {
94 let mut h = TestHarness::new().await;
95 let user_id = h.login_as("pager").await;
96 let comm_id = h.create_community("Test", "test").await;
97 let cat_id = h.create_category(comm_id, "General", "general").await;
98 h.add_membership(user_id, comm_id, "member").await;
99
100 h.create_thread_with_post(cat_id, user_id, "Thread One", "body")
101 .await;
102
103 let resp = h.client.get("/p/test/general?page=999").await;
104
105 assert!(resp.status.is_success());
106 assert!(
107 resp.text.contains("Thread One"),
108 "thread should be visible on clamped page"
109 );
110 }
111
112 #[tokio::test]
113 async fn page_zero_treated_as_page_one() {
114 let mut h = TestHarness::new().await;
115 let user_id = h.login_as("pager").await;
116 let comm_id = h.create_community("Test", "test").await;
117 let cat_id = h.create_category(comm_id, "General", "general").await;
118 h.add_membership(user_id, comm_id, "member").await;
119
120 h.create_thread_with_post(cat_id, user_id, "First Thread", "body")
121 .await;
122
123 let resp = h.client.get("/p/test/general?page=0").await;
124
125 assert!(resp.status.is_success());
126 assert!(
127 resp.text.contains("First Thread"),
128 "thread should be visible with page=0"
129 );
130 }
131
132 #[tokio::test]
133 async fn sort_by_replies_works() {
134 let mut h = TestHarness::new().await;
135 let user_id = h.login_as("sorter").await;
136 let comm_id = h.create_community("Test", "test").await;
137 let cat_id = h.create_category(comm_id, "General", "general").await;
138 h.add_membership(user_id, comm_id, "member").await;
139
140 let _t1 = h
141 .create_thread_with_post(cat_id, user_id, "Few Replies", "body")
142 .await;
143 let t2 = h
144 .create_thread_with_post(cat_id, user_id, "Many Replies", "body")
145 .await;
146
147 for i in 0..3 {
148 mt_db::mutations::create_post(
149 &h.db,
150 t2,
151 user_id,
152 &format!("reply {i}"),
153 &format!("<p>reply {i}</p>"),
154 )
155 .await
156 .unwrap();
157 }
158
159 let resp = h
160 .client
161 .get("/p/test/general?sort=replies&order=desc")
162 .await;
163 assert!(resp.status.is_success());
164 let many_pos = resp
165 .text
166 .find("Many Replies")
167 .expect("Many Replies not found");
168 let few_pos = resp
169 .text
170 .find("Few Replies")
171 .expect("Few Replies not found");
172 assert!(
173 many_pos < few_pos,
174 "Many Replies should appear before Few Replies when sorted by replies desc"
175 );
176
177 // Sort by replies asc, "Few Replies" should come first (after pinned)
178 let resp = h.client.get("/p/test/general?sort=replies&order=asc").await;
179 assert!(resp.status.is_success());
180 let many_pos = resp
181 .text
182 .find("Many Replies")
183 .expect("Many Replies not found");
184 let few_pos = resp
185 .text
186 .find("Few Replies")
187 .expect("Few Replies not found");
188 assert!(
189 few_pos < many_pos,
190 "Few Replies should appear before Many Replies when sorted by replies asc"
191 );
192 }
193
194 #[tokio::test]
195 async fn sort_headers_present_in_category() {
196 let mut h = TestHarness::new().await;
197 let comm_id = h.create_community("Test", "test").await;
198 let cat_id = h.create_category(comm_id, "General", "general").await;
199 let user_id = h.login_as("viewer").await;
200 h.add_membership(user_id, comm_id, "member").await;
201 h.create_thread_with_post(cat_id, user_id, "A Thread", "body")
202 .await;
203
204 let resp = h.client.get("/p/test/general").await;
205 assert!(resp.status.is_success());
206
207 // Default sort is activity desc, so activity header should have the arrow
208 assert!(
209 resp.text.contains("sort-active"),
210 "active sort indicator should be present"
211 );
212 assert!(
213 resp.text.contains("?sort=replies"),
214 "replies sort link should be present"
215 );
216 assert!(
217 resp.text.contains("?sort=activity"),
218 "activity sort link should be present"
219 );
220 }
221
222 #[tokio::test]
223 async fn thread_pagination_page_zero() {
224 let mut h = TestHarness::new().await;
225 let user_id = h.login_as("poster").await;
226 let comm_id = h.create_community("Test", "test").await;
227 let cat_id = h.create_category(comm_id, "General", "general").await;
228 h.add_membership(user_id, comm_id, "member").await;
229
230 let thread_id = h
231 .create_thread_with_post(cat_id, user_id, "My Thread", "first post")
232 .await;
233
234 let resp = h
235 .client
236 .get(&format!("/p/test/general/{thread_id}?page=0"))
237 .await;
238
239 assert!(resp.status.is_success());
240 assert!(
241 resp.text.contains("first post"),
242 "post should be visible with page=0"
243 );
244 }
245
246 #[tokio::test]
247 async fn meta_description_present_on_category() {
248 let mut h = TestHarness::new().await;
249 let comm_id = h.create_community("TestProject", "testproject").await;
250 let _cat_id = h.create_category(comm_id, "Bugs", "bugs").await;
251
252 let resp = h.client.get("/p/testproject/bugs").await;
253 assert!(resp.status.is_success());
254 assert!(
255 resp.text.contains(r#"<meta name="description""#),
256 "meta description should be present"
257 );
258 }
259
260 #[tokio::test]
261 async fn meta_description_present_on_thread() {
262 let mut h = TestHarness::new().await;
263 let user_id = h.login_as("metauser").await;
264 let comm_id = h.create_community("Test", "test").await;
265 let cat_id = h.create_category(comm_id, "General", "general").await;
266 h.add_membership(user_id, comm_id, "member").await;
267
268 let thread_id = h
269 .create_thread_with_post(cat_id, user_id, "Meta Thread", "body")
270 .await;
271
272 let resp = h.client.get(&format!("/p/test/general/{thread_id}")).await;
273 assert!(resp.status.is_success());
274 assert!(
275 resp.text.contains(r#"<meta name="description""#),
276 "meta description should be present on thread page"
277 );
278 }
279
280 #[tokio::test]
281 async fn noindex_on_error_page() {
282 let mut h = TestHarness::new().await;
283
284 let resp = h.client.get("/nonexistent-page-xyz").await;
285 assert_eq!(resp.status, axum::http::StatusCode::NOT_FOUND);
286 assert!(
287 resp.text
288 .contains(r#"<meta name="robots" content="noindex">"#),
289 "404 page should have noindex meta"
290 );
291 }
292
293 #[tokio::test]
294 async fn member_list_shows_members_with_roles() {
295 let mut h = TestHarness::new().await;
296 let owner_id = h.login_as("theowner").await;
297 let comm_id = h.create_community("Test", "test").await;
298 h.add_membership(owner_id, comm_id, "owner").await;
299
300 let mod_id = h.login_as("themod").await;
301 h.add_membership(mod_id, comm_id, "moderator").await;
302
303 let member_id = h.login_as("regular").await;
304 h.add_membership(member_id, comm_id, "member").await;
305
306 let resp = h.client.get("/p/test/members").await;
307
308 assert!(resp.status.is_success());
309 assert!(resp.text.contains("theowner"), "owner should be listed");
310 assert!(resp.text.contains("themod"), "mod should be listed");
311 assert!(resp.text.contains("regular"), "member should be listed");
312 // Owner should appear before member in the table (sorted by role)
313 // Search within the data-table to avoid matching the header nav Profile link
314 let table_start = resp.text.find("data-table").unwrap();
315 let table_html = &resp.text[table_start..];
316 let owner_pos = table_html.find("/u/theowner").unwrap();
317 let member_pos = table_html.find("/u/regular").unwrap();
318 assert!(
319 owner_pos < member_pos,
320 "owner should appear before regular member"
321 );
322 }
323
324 #[tokio::test]
325 async fn member_list_nonexistent_community_404() {
326 let mut h = TestHarness::new().await;
327
328 let resp = h.client.get("/p/nonexistent/members").await;
329 assert_eq!(resp.status, axum::http::StatusCode::NOT_FOUND);
330 }
331
332 #[tokio::test]
333 async fn community_page_has_members_link() {
334 let mut h = TestHarness::new().await;
335 let comm_id = h.create_community("Test", "test").await;
336 let _cat_id = h.create_category(comm_id, "General", "general").await;
337
338 let resp = h.client.get("/p/test").await;
339 assert!(resp.status.is_success());
340 assert!(
341 resp.text.contains("/p/test/members"),
342 "community page should have members link"
343 );
344 }
345