Skip to main content

max / makenotwork

7.4 KB · 269 lines History Blame Raw
1 //! Sandbox workflow: ephemeral account creation, feature restrictions, visibility rules.
2
3 use crate::harness::TestHarness;
4 use makenotwork::constants::SANDBOX_MAX_PER_IP;
5
6 /// Helper: create a sandbox account via POST /sandbox.
7 /// The client must have a CSRF token (fetched from GET /sandbox).
8 /// Returns the response from POST /sandbox (should be a 302 redirect).
9 async fn create_sandbox(h: &mut TestHarness) -> crate::harness::client::TestResponse {
10 // GET /sandbox to establish session + extract CSRF token
11 let resp = h.client.get("/sandbox").await;
12 assert!(
13 resp.status.is_success(),
14 "GET /sandbox failed: {} {}",
15 resp.status,
16 resp.text
17 );
18
19 // POST /sandbox to create the account (new session, CSRF regenerated)
20 let resp = h.client.post_form("/sandbox", "").await;
21
22 // Fetch a page to pick up the new CSRF token for the fresh session
23 let _ = h.client.get("/library").await;
24
25 resp
26 }
27
28 /// Look up the sandbox username from the DB (most recently created sandbox_ user).
29 async fn sandbox_username(h: &TestHarness) -> String {
30 sqlx::query_scalar::<_, String>(
31 "SELECT username FROM users WHERE username LIKE 'sandbox_%' ORDER BY created_at DESC LIMIT 1",
32 )
33 .fetch_one(&h.db)
34 .await
35 .expect("No sandbox user found")
36 }
37
38 #[tokio::test]
39 async fn create_sandbox_account() {
40 let mut h = TestHarness::new().await;
41
42 let resp = create_sandbox(&mut h).await;
43 assert!(
44 resp.status.is_redirection(),
45 "POST /sandbox should redirect, got {}",
46 resp.status
47 );
48
49 // Should be able to access dashboard as the sandbox user
50 let resp = h.client.get("/dashboard").await;
51 assert_eq!(
52 resp.status, 200,
53 "Dashboard should be accessible after sandbox creation"
54 );
55 }
56
57 #[tokio::test]
58 async fn sandbox_blocks_restricted_endpoints() {
59 let mut h = TestHarness::new().await;
60 create_sandbox(&mut h).await;
61
62 // Custom domains
63 let resp = h
64 .client
65 .post_form("/api/domains", "domain=sandbox.example.com")
66 .await;
67 assert_eq!(
68 resp.status, 403,
69 "Sandbox: POST /api/domains should be 403, got {}",
70 resp.status
71 );
72
73 // Git repos
74 let resp = h
75 .client
76 .post_json("/api/repos", r#"{"name": "test-repo"}"#)
77 .await;
78 assert_eq!(
79 resp.status, 403,
80 "Sandbox: POST /api/repos should be 403, got {}",
81 resp.status
82 );
83
84 // Imports
85 let resp = h.client.post_json(
86 "/api/users/me/import",
87 r#"{"project_id": "00000000-0000-0000-0000-000000000000", "source": "generic_csv", "csv_data": "ZW1haWwKdGVzdEB0ZXN0LmNvbQo=", "column_mapping": {"email": 0}}"#,
88 ).await;
89 assert_eq!(
90 resp.status, 403,
91 "Sandbox: POST /api/users/me/import should be 403, got {}",
92 resp.status
93 );
94
95 // Guest purchase claim
96 let resp = h
97 .client
98 .post_json(
99 "/api/purchases/claim",
100 r#"{"claim_token": "00000000-0000-0000-0000-000000000000"}"#,
101 )
102 .await;
103 assert_eq!(
104 resp.status, 403,
105 "Sandbox: POST /api/purchases/claim should be 403, got {}",
106 resp.status
107 );
108 }
109
110 #[tokio::test]
111 async fn sandbox_content_not_visible_on_item_page() {
112 let mut h = TestHarness::new().await;
113 create_sandbox(&mut h).await;
114
115 // Create a project
116 let resp = h
117 .client
118 .post_form("/api/projects", "slug=sandbox-proj&title=Sandbox+Project")
119 .await;
120 assert!(
121 resp.status.is_success(),
122 "Create project failed: {} {}",
123 resp.status,
124 resp.text
125 );
126 let project: serde_json::Value = resp.json();
127 let project_id = project["id"].as_str().unwrap();
128
129 // Publish project
130 h.client
131 .put_json(
132 &format!("/api/projects/{project_id}"),
133 r#"{"is_public": true}"#,
134 )
135 .await;
136
137 // Create an item
138 let resp = h
139 .client
140 .post_form(
141 &format!("/api/projects/{project_id}/items"),
142 "title=Sandbox+Item&item_type=digital&price_cents=0",
143 )
144 .await;
145 assert!(
146 resp.status.is_success(),
147 "Create item failed: {} {}",
148 resp.status,
149 resp.text
150 );
151 let item: serde_json::Value = resp.json();
152 let item_id = item["id"].as_str().unwrap();
153
154 // Publish the item
155 h.client
156 .put_form(&format!("/api/items/{item_id}"), "is_public=true")
157 .await;
158
159 // Use a second harness (unauthenticated client) to visit the item page
160 let mut h2 = TestHarness::new().await;
161 let resp = h2.client.get(&format!("/i/{item_id}")).await;
162 assert_eq!(
163 resp.status, 404,
164 "Sandbox item should return 404 to unauthenticated visitor, got {}",
165 resp.status
166 );
167 }
168
169 #[tokio::test]
170 async fn sandbox_rss_returns_404() {
171 let mut h = TestHarness::new().await;
172 create_sandbox(&mut h).await;
173
174 let username = sandbox_username(&h).await;
175
176 let resp = h.client.get(&format!("/u/{username}/rss")).await;
177 assert_eq!(
178 resp.status, 404,
179 "Sandbox user RSS feed should return 404, got {}",
180 resp.status
181 );
182 }
183
184 #[tokio::test]
185 #[cfg_attr(
186 not(feature = "fast-tests"),
187 ignore = "only run under the fast-tests feature"
188 )]
189 async fn sandbox_per_ip_cap() {
190 let mut h = TestHarness::new().await;
191
192 // Create SANDBOX_MAX_PER_IP sandboxes without logging out.
193 // The cap counts concurrent active sessions per IP, logout deletes
194 // the session row, which would defeat the count.
195 for i in 0..SANDBOX_MAX_PER_IP {
196 let resp = create_sandbox(&mut h).await;
197 assert!(
198 resp.status.is_redirection(),
199 "Sandbox {} should succeed, got {}",
200 i + 1,
201 resp.status
202 );
203 }
204
205 // Try one more, should be rejected (cap reached)
206 let resp = h.client.get("/sandbox").await;
207 assert!(resp.status.is_success());
208
209 let resp = h.client.post_form("/sandbox", "").await;
210 assert_eq!(
211 resp.status, 400,
212 "Sandbox beyond per-IP cap should return 400, got {}",
213 resp.status
214 );
215 }
216
217 #[tokio::test]
218 async fn sandbox_blog_no_email() {
219 let mut h = TestHarness::with_mocks().await;
220 create_sandbox(&mut h).await;
221
222 // Clear any emails from sandbox creation
223 h.mock_email.as_ref().unwrap().clear();
224
225 // Create a project
226 let resp = h
227 .client
228 .post_form("/api/projects", "slug=sb-blog&title=Sandbox+Blog")
229 .await;
230 assert!(
231 resp.status.is_success(),
232 "Create project failed: {} {}",
233 resp.status,
234 resp.text
235 );
236 let project: serde_json::Value = resp.json();
237 let project_id = project["id"].as_str().unwrap();
238
239 // Publish project
240 h.client
241 .put_json(
242 &format!("/api/projects/{project_id}"),
243 r#"{"is_public": true}"#,
244 )
245 .await;
246
247 // Create and immediately publish a blog post
248 let resp = h
249 .client
250 .post_json(
251 &format!("/api/projects/{project_id}/blog"),
252 r#"{"title": "Sandbox Post", "body_markdown": "Hello from sandbox!", "is_published": true}"#,
253 )
254 .await;
255 assert!(
256 resp.status.is_success(),
257 "Create blog post failed: {} {}",
258 resp.status,
259 resp.text
260 );
261
262 // No emails should have been sent (sandbox skips announcements)
263 let count = h.mock_email.as_ref().unwrap().count();
264 assert_eq!(
265 count, 0,
266 "Sandbox blog publish should send 0 emails, got {count}"
267 );
268 }
269