Skip to main content

max / makenotwork

8.3 KB · 279 lines History Blame Raw
1 //! Page route tests: public pages, user/project/item detail, RSS feeds, discover.
2 //!
3 //! These test that page routes render successfully and contain expected content.
4 //! Complements the API workflow tests which focus on JSON endpoints.
5
6 use crate::harness::TestHarness;
7 use serde_json::Value;
8
9 #[tokio::test]
10 async fn public_pages_render() {
11 let mut h = TestHarness::new().await;
12
13 // Landing page
14 let resp = h.client.get("/").await;
15 assert_eq!(resp.status, 200, "Landing page should return 200");
16
17 // Login page
18 let resp = h.client.get("/login").await;
19 assert_eq!(resp.status, 200, "Login page should return 200");
20
21 // Join page
22 let resp = h.client.get("/join").await;
23 assert_eq!(resp.status, 200, "Join page should return 200");
24
25 // Pricing page
26 let resp = h.client.get("/pricing").await;
27 assert_eq!(resp.status, 200, "Pricing page should return 200");
28
29 // Discover page
30 let resp = h.client.get("/discover").await;
31 assert_eq!(resp.status, 200, "Discover page should return 200");
32
33 // Discover with filters
34 let resp = h.client.get("/discover?mode=projects").await;
35 assert_eq!(resp.status, 200, "Discover projects mode should return 200");
36
37 // Tag tree browser
38 let resp = h.client.get("/discover/tags").await;
39 assert_eq!(resp.status, 200, "Tag tree should return 200");
40 }
41
42 #[tokio::test]
43 async fn user_and_project_pages() {
44 let mut h = TestHarness::new().await;
45
46 // Setup: creator with public project and item
47 let user_id = h
48 .signup("pagetest", "pagetest@example.com", "password123")
49 .await;
50 h.grant_creator(user_id).await;
51 h.client.post_form("/logout", "").await;
52 h.login("pagetest", "password123").await;
53
54 let resp = h
55 .client
56 .post_form("/api/projects", "slug=test-proj&title=Test+Project")
57 .await;
58 let project: Value = resp.json();
59 let project_id = project["id"].as_str().unwrap();
60
61 // Make project public
62 h.client
63 .put_json(
64 &format!("/api/projects/{project_id}"),
65 r#"{"is_public": true}"#,
66 )
67 .await;
68
69 // Create a text item and make it public
70 let resp = h
71 .client
72 .post_form(
73 &format!("/api/projects/{project_id}/items"),
74 "title=Test+Article&item_type=text&is_public=true&price_cents=0",
75 )
76 .await;
77 assert!(
78 resp.status.is_success(),
79 "Create item failed: {}",
80 resp.text
81 );
82 let item: Value = resp.json();
83 let item_id = item["id"].as_str().unwrap();
84
85 // Add text content. The store page renders an excerpt from the first
86 // paragraph (full body lives on /l/{id}), so keep the asserted phrase
87 // in paragraph one.
88 h.client
89 .put_json(
90 &format!("/api/items/{item_id}/text"),
91 "{\"body\": \"Test content for page rendering.\\n\\n# Hello\\n\\nMore body below.\"}",
92 )
93 .await;
94
95 // Log out to test as anonymous user
96 h.client.post_form("/logout", "").await;
97
98 // ── User profile page ──
99 let resp = h.client.get("/u/pagetest").await;
100 assert_eq!(resp.status, 200, "User page should return 200");
101 assert!(
102 resp.text.contains("Test Project"),
103 "User page should show project title"
104 );
105
106 // ── Project page ──
107 let resp = h.client.get("/p/test-proj").await;
108 assert_eq!(resp.status, 200, "Project page should return 200");
109 assert!(
110 resp.text.contains("Test Article"),
111 "Project page should show item title"
112 );
113
114 // ── Item page (text reader) ──
115 let resp = h.client.get(&format!("/i/{item_id}")).await;
116 assert_eq!(resp.status, 200, "Item page should return 200");
117 assert!(
118 resp.text.contains("Test content"),
119 "Item page should render text content"
120 );
121
122 // ── 404 for nonexistent user/project ──
123 let resp = h.client.get("/u/nonexistent-user-xyz").await;
124 assert_eq!(resp.status, 404, "Nonexistent user should return 404");
125
126 let resp = h.client.get("/p/nonexistent-project-xyz").await;
127 assert_eq!(resp.status, 404, "Nonexistent project should return 404");
128 }
129
130 #[tokio::test]
131 async fn rss_feeds() {
132 let mut h = TestHarness::new().await;
133
134 // Setup: creator with public project and item
135 let user_id = h
136 .signup("rsstest", "rsstest@example.com", "password123")
137 .await;
138 h.grant_creator(user_id).await;
139 h.client.post_form("/logout", "").await;
140 h.login("rsstest", "password123").await;
141
142 let resp = h
143 .client
144 .post_form("/api/projects", "slug=rss-proj&title=RSS+Project")
145 .await;
146 let project: Value = resp.json();
147 let project_id = project["id"].as_str().unwrap();
148
149 // Make project public
150 h.client
151 .put_json(
152 &format!("/api/projects/{project_id}"),
153 r#"{"is_public": true}"#,
154 )
155 .await;
156
157 // Create a public item
158 let resp = h
159 .client
160 .post_form(
161 &format!("/api/projects/{project_id}/items"),
162 "title=RSS+Item&item_type=text&is_public=true&price_cents=0",
163 )
164 .await;
165 assert!(
166 resp.status.is_success(),
167 "Create item failed: {}",
168 resp.text
169 );
170
171 h.client.post_form("/logout", "").await;
172
173 // ── Creator RSS feed ──
174 let resp = h.client.get("/u/rsstest/rss").await;
175 assert_eq!(resp.status, 200, "Creator RSS feed should return 200");
176 assert!(
177 resp.text.contains("<rss") || resp.text.contains("<?xml"),
178 "Creator feed should be valid XML"
179 );
180 assert!(
181 resp.text.contains("RSS Item"),
182 "Creator feed should contain item title"
183 );
184
185 // ── Project RSS feed ──
186 let resp = h.client.get("/p/rss-proj/rss").await;
187 assert_eq!(resp.status, 200, "Project RSS feed should return 200");
188 assert!(
189 resp.text.contains("RSS Item"),
190 "Project feed should contain item title"
191 );
192 }
193
194 #[tokio::test]
195 async fn unpublished_item_visibility() {
196 let mut h = TestHarness::new().await;
197
198 // Setup: creator with project
199 let user_id = h
200 .signup("vistest", "vistest@example.com", "password123")
201 .await;
202 h.grant_creator(user_id).await;
203 h.client.post_form("/logout", "").await;
204 h.login("vistest", "password123").await;
205
206 let resp = h
207 .client
208 .post_form("/api/projects", "slug=vis-proj&title=Vis+Project")
209 .await;
210 let project: Value = resp.json();
211 let project_id = project["id"].as_str().unwrap();
212
213 // Create an item (defaults to public) then make it private
214 let resp = h
215 .client
216 .post_form(
217 &format!("/api/projects/{project_id}/items"),
218 "title=Private+Item&item_type=text",
219 )
220 .await;
221 let item: Value = resp.json();
222 let item_id = item["id"].as_str().unwrap();
223
224 // Make it non-public
225 h.client
226 .put_form(&format!("/api/items/{item_id}"), "is_public=false")
227 .await;
228
229 // Owner can see it
230 let resp = h.client.get(&format!("/i/{item_id}")).await;
231 assert_eq!(resp.status, 200, "Owner should see unpublished item");
232
233 // Log out, anonymous user should get 404
234 h.client.post_form("/logout", "").await;
235
236 let resp = h.client.get(&format!("/i/{item_id}")).await;
237 assert_eq!(
238 resp.status, 404,
239 "Anonymous user should not see unpublished item"
240 );
241
242 // Different user should also get 404
243 h.signup("other", "other@example.com", "password123").await;
244 h.client.post_form("/logout", "").await;
245 h.login("other", "password123").await;
246
247 let resp = h.client.get(&format!("/i/{item_id}")).await;
248 assert_eq!(
249 resp.status, 404,
250 "Non-owner should not see unpublished item"
251 );
252 }
253
254 #[tokio::test]
255 async fn discover_htmx_partial() {
256 let mut h = TestHarness::new().await;
257
258 let resp = h.client.get("/discover/results").await;
259 assert_eq!(
260 resp.status, 200,
261 "Discover results partial should return 200"
262 );
263
264 let resp = h.client.get("/discover/results?mode=projects").await;
265 assert_eq!(
266 resp.status, 200,
267 "Discover results projects mode should return 200"
268 );
269
270 let resp = h
271 .client
272 .get("/discover/results?item_type=audio&sort=newest")
273 .await;
274 assert_eq!(
275 resp.status, 200,
276 "Discover results with filters should return 200"
277 );
278 }
279