Skip to main content

max / multithreaded

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