Skip to main content

max / makenotwork

7.2 KB · 242 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.client.post_form("/api/domains", "domain=sandbox.example.com").await;
64 assert_eq!(resp.status, 403, "Sandbox: POST /api/domains should be 403, got {}", resp.status);
65
66 // Git repos
67 let resp = h.client.post_json("/api/repos", r#"{"name": "test-repo"}"#).await;
68 assert_eq!(resp.status, 403, "Sandbox: POST /api/repos should be 403, got {}", resp.status);
69
70 // Imports
71 let resp = h.client.post_json(
72 "/api/users/me/import",
73 r#"{"project_id": "00000000-0000-0000-0000-000000000000", "source": "generic_csv", "csv_data": "ZW1haWwKdGVzdEB0ZXN0LmNvbQo=", "column_mapping": {"email": 0}}"#,
74 ).await;
75 assert_eq!(resp.status, 403, "Sandbox: POST /api/users/me/import should be 403, got {}", resp.status);
76
77 // Guest purchase claim
78 let resp = h.client.post_json(
79 "/api/purchases/claim",
80 r#"{"claim_token": "00000000-0000-0000-0000-000000000000"}"#,
81 ).await;
82 assert_eq!(resp.status, 403, "Sandbox: POST /api/purchases/claim should be 403, got {}", resp.status);
83 }
84
85 #[tokio::test]
86 async fn sandbox_content_not_visible_on_item_page() {
87 let mut h = TestHarness::new().await;
88 create_sandbox(&mut h).await;
89
90 // Create a project
91 let resp = h
92 .client
93 .post_form("/api/projects", "slug=sandbox-proj&title=Sandbox+Project")
94 .await;
95 assert!(
96 resp.status.is_success(),
97 "Create project failed: {} {}",
98 resp.status,
99 resp.text
100 );
101 let project: serde_json::Value = resp.json();
102 let project_id = project["id"].as_str().unwrap();
103
104 // Publish project
105 h.client
106 .put_json(
107 &format!("/api/projects/{}", project_id),
108 r#"{"is_public": true}"#,
109 )
110 .await;
111
112 // Create an item
113 let resp = h
114 .client
115 .post_form(
116 &format!("/api/projects/{}/items", project_id),
117 "title=Sandbox+Item&item_type=digital&price_cents=0",
118 )
119 .await;
120 assert!(
121 resp.status.is_success(),
122 "Create item failed: {} {}",
123 resp.status,
124 resp.text
125 );
126 let item: serde_json::Value = resp.json();
127 let item_id = item["id"].as_str().unwrap();
128
129 // Publish the item
130 h.client
131 .put_form(&format!("/api/items/{}", item_id), "is_public=true")
132 .await;
133
134 // Use a second harness (unauthenticated client) to visit the item page
135 let mut h2 = TestHarness::new().await;
136 let resp = h2.client.get(&format!("/i/{}", item_id)).await;
137 assert_eq!(
138 resp.status, 404,
139 "Sandbox item should return 404 to unauthenticated visitor, got {}",
140 resp.status
141 );
142 }
143
144 #[tokio::test]
145 async fn sandbox_rss_returns_404() {
146 let mut h = TestHarness::new().await;
147 create_sandbox(&mut h).await;
148
149 let username = sandbox_username(&h).await;
150
151 let resp = h.client.get(&format!("/u/{}/rss", username)).await;
152 assert_eq!(
153 resp.status, 404,
154 "Sandbox user RSS feed should return 404, got {}",
155 resp.status
156 );
157 }
158
159 #[tokio::test]
160 #[cfg_attr(not(feature = "fast-tests"), ignore)]
161 async fn sandbox_per_ip_cap() {
162 let mut h = TestHarness::new().await;
163
164 // Create SANDBOX_MAX_PER_IP sandboxes without logging out.
165 // The cap counts concurrent active sessions per IP — logout deletes
166 // the session row, which would defeat the count.
167 for i in 0..SANDBOX_MAX_PER_IP {
168 let resp = create_sandbox(&mut h).await;
169 assert!(
170 resp.status.is_redirection(),
171 "Sandbox {} should succeed, got {}",
172 i + 1,
173 resp.status
174 );
175 }
176
177 // Try one more — should be rejected (cap reached)
178 let resp = h.client.get("/sandbox").await;
179 assert!(resp.status.is_success());
180
181 let resp = h.client.post_form("/sandbox", "").await;
182 assert_eq!(
183 resp.status, 400,
184 "Sandbox beyond per-IP cap should return 400, got {}",
185 resp.status
186 );
187 }
188
189 #[tokio::test]
190 async fn sandbox_blog_no_email() {
191 let mut h = TestHarness::with_mocks().await;
192 create_sandbox(&mut h).await;
193
194 // Clear any emails from sandbox creation
195 h.mock_email.as_ref().unwrap().clear();
196
197 // Create a project
198 let resp = h
199 .client
200 .post_form("/api/projects", "slug=sb-blog&title=Sandbox+Blog")
201 .await;
202 assert!(
203 resp.status.is_success(),
204 "Create project failed: {} {}",
205 resp.status,
206 resp.text
207 );
208 let project: serde_json::Value = resp.json();
209 let project_id = project["id"].as_str().unwrap();
210
211 // Publish project
212 h.client
213 .put_json(
214 &format!("/api/projects/{}", project_id),
215 r#"{"is_public": true}"#,
216 )
217 .await;
218
219 // Create and immediately publish a blog post
220 let resp = h
221 .client
222 .post_json(
223 &format!("/api/projects/{}/blog", project_id),
224 r#"{"title": "Sandbox Post", "body_markdown": "Hello from sandbox!", "is_published": true}"#,
225 )
226 .await;
227 assert!(
228 resp.status.is_success(),
229 "Create blog post failed: {} {}",
230 resp.status,
231 resp.text
232 );
233
234 // No emails should have been sent (sandbox skips announcements)
235 let count = h.mock_email.as_ref().unwrap().count();
236 assert_eq!(
237 count, 0,
238 "Sandbox blog publish should send 0 emails, got {}",
239 count
240 );
241 }
242