Skip to main content

max / makenotwork

13.9 KB · 476 lines History Blame Raw
1 //! Waitlist: apply, duplicate rejected, pitch too short, unverified email, already creator.
2
3 use crate::harness::TestHarness;
4
5 #[tokio::test]
6 async fn waitlist_apply_success() {
7 let mut h = TestHarness::new().await;
8 let user_id = h
9 .signup("wapplicant", "wapplicant@test.com", "password123")
10 .await;
11
12 // Verify email
13 sqlx::query("UPDATE users SET email_verified = true WHERE id = $1")
14 .bind(*user_id)
15 .execute(&h.db)
16 .await
17 .unwrap();
18
19 let pitch = "I create independent music and want to sell my albums directly to fans without middlemen.";
20 let resp = h
21 .client
22 .post_form(
23 "/api/waitlist/apply",
24 &format!("pitch={}", urlencoding::encode(pitch)),
25 )
26 .await;
27 assert!(
28 resp.status.is_success() || resp.status == 204,
29 "Waitlist apply should succeed, got {} {}",
30 resp.status,
31 resp.text
32 );
33
34 // Verify in DB
35 let count: i64 =
36 sqlx::query_scalar("SELECT COUNT(*) FROM creator_waitlist WHERE user_id = $1")
37 .bind(*user_id)
38 .fetch_one(&h.db)
39 .await
40 .unwrap();
41 assert_eq!(count, 1, "Waitlist entry should exist in database");
42 }
43
44 #[tokio::test]
45 async fn waitlist_duplicate_rejected() {
46 let mut h = TestHarness::new().await;
47 let user_id = h
48 .signup("wduplicate", "wduplicate@test.com", "password123")
49 .await;
50
51 // Verify email
52 sqlx::query("UPDATE users SET email_verified = true WHERE id = $1")
53 .bind(*user_id)
54 .execute(&h.db)
55 .await
56 .unwrap();
57
58 let pitch = "I want to sell my handcrafted digital art directly to collectors worldwide.";
59
60 // First application
61 let resp = h
62 .client
63 .post_form(
64 "/api/waitlist/apply",
65 &format!("pitch={}", urlencoding::encode(pitch)),
66 )
67 .await;
68 assert!(
69 resp.status.is_success() || resp.status == 204,
70 "First application should succeed, got {} {}",
71 resp.status,
72 resp.text
73 );
74
75 // Second application — should be rejected
76 let resp = h
77 .client
78 .post_form(
79 "/api/waitlist/apply",
80 &format!("pitch={}", urlencoding::encode(pitch)),
81 )
82 .await;
83 assert_eq!(
84 resp.status, 400,
85 "Duplicate application should be rejected, got {} {}",
86 resp.status, resp.text
87 );
88 }
89
90 #[tokio::test]
91 async fn waitlist_pitch_too_short() {
92 let mut h = TestHarness::new().await;
93 let user_id = h
94 .signup("wshort", "wshort@test.com", "password123")
95 .await;
96
97 // Verify email
98 sqlx::query("UPDATE users SET email_verified = true WHERE id = $1")
99 .bind(*user_id)
100 .execute(&h.db)
101 .await
102 .unwrap();
103
104 // Pitch under 20 characters
105 let resp = h
106 .client
107 .post_form("/api/waitlist/apply", "pitch=too+short")
108 .await;
109 assert!(
110 resp.status == 400 || resp.status == 422,
111 "Short pitch should be rejected, got {} {}",
112 resp.status, resp.text
113 );
114 }
115
116 #[tokio::test]
117 async fn waitlist_unverified_email_rejected() {
118 let mut h = TestHarness::new().await;
119 let _user_id = h
120 .signup("wunverified", "wunverified@test.com", "password123")
121 .await;
122
123 // Don't verify email — apply should fail
124 let pitch = "I create podcasts about technology and want a better home for my content.";
125 let resp = h
126 .client
127 .post_form(
128 "/api/waitlist/apply",
129 &format!("pitch={}", urlencoding::encode(pitch)),
130 )
131 .await;
132 assert_eq!(
133 resp.status, 400,
134 "Unverified email should be rejected, got {} {}",
135 resp.status, resp.text
136 );
137 }
138
139 #[tokio::test]
140 async fn waitlist_already_creator_rejected() {
141 let mut h = TestHarness::new().await;
142 let user_id = h
143 .signup("walready", "walready@test.com", "password123")
144 .await;
145
146 // Verify email AND grant creator
147 sqlx::query("UPDATE users SET email_verified = true WHERE id = $1")
148 .bind(*user_id)
149 .execute(&h.db)
150 .await
151 .unwrap();
152 h.grant_creator(user_id).await;
153
154 // Re-login so session reflects can_create_projects = true
155 h.client.post_form("/logout", "").await;
156 h.login("walready", "password123").await;
157
158 let pitch = "I already have creator access but am applying again for some reason.";
159 let resp = h
160 .client
161 .post_form(
162 "/api/waitlist/apply",
163 &format!("pitch={}", urlencoding::encode(pitch)),
164 )
165 .await;
166 assert_eq!(
167 resp.status, 400,
168 "Already-creator should be rejected, got {} {}",
169 resp.status, resp.text
170 );
171 }
172
173 #[tokio::test]
174 async fn waitlist_pitch_too_long() {
175 let mut h = TestHarness::new().await;
176 let user_id = h
177 .signup("wlong", "wlong@test.com", "password123")
178 .await;
179
180 sqlx::query("UPDATE users SET email_verified = true WHERE id = $1")
181 .bind(*user_id)
182 .execute(&h.db)
183 .await
184 .unwrap();
185
186 // Pitch > 500 chars
187 let long_pitch = "x".repeat(501);
188 let resp = h
189 .client
190 .post_form(
191 "/api/waitlist/apply",
192 &format!("pitch={}", urlencoding::encode(&long_pitch)),
193 )
194 .await;
195 assert!(
196 resp.status == 400 || resp.status == 422,
197 "Pitch >500 chars should be rejected, got {} {}",
198 resp.status, resp.text
199 );
200 }
201
202 #[tokio::test]
203 async fn waitlist_unauthenticated_rejected() {
204 let mut h = TestHarness::new().await;
205 // Just fetch CSRF — no login
206 h.client.fetch_csrf_token().await;
207
208 let pitch = "I want to sell my art but I am not logged in for some reason right now.";
209 let resp = h
210 .client
211 .post_form(
212 "/api/waitlist/apply",
213 &format!("pitch={}", urlencoding::encode(pitch)),
214 )
215 .await;
216 assert_eq!(
217 resp.status, 401,
218 "Unauthenticated apply should be 401, got {} {}",
219 resp.status, resp.text
220 );
221 }
222
223 #[tokio::test]
224 async fn waitlist_non_admin_gets_404() {
225 let (mut h, _admin_id) = TestHarness::with_admin().await;
226
227 // Sign up a regular user (not the admin)
228 let _user_id = h.signup("wnonadmin", "wnonadmin@test.com", "password123").await;
229
230 // Regular user tries admin waitlist routes — should get 404 (hidden)
231 let resp = h.client.get("/admin/waitlist").await;
232 assert_eq!(
233 resp.status, 404,
234 "Non-admin GET /admin/waitlist should be 404, got {} {}",
235 resp.status, resp.text
236 );
237
238 let resp = h
239 .client
240 .post_form("/api/admin/waitlist/00000000-0000-0000-0000-000000000000/approve", "")
241 .await;
242 assert_eq!(
243 resp.status, 404,
244 "Non-admin POST approve should be 404, got {} {}",
245 resp.status, resp.text
246 );
247
248 let resp = h
249 .client
250 .post_form("/api/admin/lottery", "count=1")
251 .await;
252 assert_eq!(
253 resp.status, 404,
254 "Non-admin POST lottery should be 404, got {} {}",
255 resp.status, resp.text
256 );
257 }
258
259 #[tokio::test]
260 async fn waitlist_admin_approve() {
261 let (mut h, _admin_id) = TestHarness::with_admin().await;
262
263 // Create an applicant
264 let user_id = h.signup("wapprove", "wapprove@test.com", "password123").await;
265 sqlx::query("UPDATE users SET email_verified = true WHERE id = $1")
266 .bind(*user_id)
267 .execute(&h.db)
268 .await
269 .unwrap();
270
271 let pitch = "I create electronic music and want to sell my albums independently.";
272 let resp = h
273 .client
274 .post_form(
275 "/api/waitlist/apply",
276 &format!("pitch={}", urlencoding::encode(pitch)),
277 )
278 .await;
279 assert!(
280 resp.status.is_success() || resp.status == 204,
281 "Waitlist apply failed: {} {}",
282 resp.status, resp.text
283 );
284
285 // Get waitlist entry ID
286 let entry_id: uuid::Uuid =
287 sqlx::query_scalar("SELECT id FROM creator_waitlist WHERE user_id = $1")
288 .bind(*user_id)
289 .fetch_one(&h.db)
290 .await
291 .unwrap();
292
293 // Log in as admin
294 h.client.post_form("/logout", "").await;
295 h.login("admin", "password123").await;
296
297 // Approve the entry
298 let resp = h
299 .client
300 .post_form(&format!("/api/admin/waitlist/{}/approve", entry_id), "")
301 .await;
302 assert!(
303 resp.status.is_success(),
304 "Admin approve failed: {} {}",
305 resp.status, resp.text
306 );
307
308 // Verify: status=approved, method=hand_picked
309 let (status, method): (String, Option<String>) = sqlx::query_as(
310 "SELECT status, selection_method FROM creator_waitlist WHERE id = $1",
311 )
312 .bind(entry_id)
313 .fetch_one(&h.db)
314 .await
315 .unwrap();
316 assert_eq!(status, "approved");
317 assert_eq!(method.as_deref(), Some("hand_picked"));
318
319 // Verify: user is now a creator
320 let can_create: bool =
321 sqlx::query_scalar("SELECT can_create_projects FROM users WHERE id = $1")
322 .bind(*user_id)
323 .fetch_one(&h.db)
324 .await
325 .unwrap();
326 assert!(can_create, "Approved user should be a creator");
327 }
328
329 #[tokio::test]
330 async fn waitlist_admin_spam() {
331 let (mut h, _admin_id) = TestHarness::with_admin().await;
332
333 // Create an applicant
334 let user_id = h.signup("wspam", "wspam@test.com", "password123").await;
335 sqlx::query("UPDATE users SET email_verified = true WHERE id = $1")
336 .bind(*user_id)
337 .execute(&h.db)
338 .await
339 .unwrap();
340
341 let pitch = "Buy my crypto course and get rich quick with this one weird trick now.";
342 let resp = h
343 .client
344 .post_form(
345 "/api/waitlist/apply",
346 &format!("pitch={}", urlencoding::encode(pitch)),
347 )
348 .await;
349 assert!(
350 resp.status.is_success() || resp.status == 204,
351 "Waitlist apply failed: {} {}",
352 resp.status, resp.text
353 );
354
355 let entry_id: uuid::Uuid =
356 sqlx::query_scalar("SELECT id FROM creator_waitlist WHERE user_id = $1")
357 .bind(*user_id)
358 .fetch_one(&h.db)
359 .await
360 .unwrap();
361
362 // Log in as admin
363 h.client.post_form("/logout", "").await;
364 h.login("admin", "password123").await;
365
366 // Mark as spam
367 let resp = h
368 .client
369 .post_form(&format!("/api/admin/waitlist/{}/spam", entry_id), "")
370 .await;
371 assert!(
372 resp.status.is_success(),
373 "Admin spam failed: {} {}",
374 resp.status, resp.text
375 );
376
377 // Verify: status=spam
378 let status: String =
379 sqlx::query_scalar("SELECT status FROM creator_waitlist WHERE id = $1")
380 .bind(entry_id)
381 .fetch_one(&h.db)
382 .await
383 .unwrap();
384 assert_eq!(status, "spam");
385
386 // Verify: user is NOT a creator
387 let can_create: bool =
388 sqlx::query_scalar("SELECT can_create_projects FROM users WHERE id = $1")
389 .bind(*user_id)
390 .fetch_one(&h.db)
391 .await
392 .unwrap();
393 assert!(!can_create, "Spammed user should not be a creator");
394 }
395
396 #[tokio::test]
397 async fn waitlist_lottery_flow() {
398 let (mut h, _admin_id) = TestHarness::with_admin().await;
399
400 // Create 3 applicants
401 let mut user_ids = Vec::new();
402 for (name, email) in [
403 ("wlot1", "wlot1@test.com"),
404 ("wlot2", "wlot2@test.com"),
405 ("wlot3", "wlot3@test.com"),
406 ] {
407 let uid = h.signup(name, email, "password123").await;
408 sqlx::query("UPDATE users SET email_verified = true WHERE id = $1")
409 .bind(*uid)
410 .execute(&h.db)
411 .await
412 .unwrap();
413
414 let pitch = format!("I create amazing {name} content and want to share it with the world.");
415 h.client
416 .post_form(
417 "/api/waitlist/apply",
418 &format!("pitch={}", urlencoding::encode(&pitch)),
419 )
420 .await;
421
422 h.client.post_form("/logout", "").await;
423 user_ids.push(uid);
424 }
425
426 // Log in as admin
427 h.login("admin", "password123").await;
428
429 // Run lottery: draw 2 out of 3
430 let resp = h
431 .client
432 .post_form("/api/admin/lottery", "count=2")
433 .await;
434 assert!(
435 resp.status.is_success() || resp.status.is_redirection(),
436 "Admin lottery failed: {} {}",
437 resp.status, resp.text
438 );
439
440 // Verify: a wave was created with wave_number=1
441 let (wave_number, lottery_count): (i32, i32) = sqlx::query_as(
442 "SELECT wave_number, lottery_count FROM creator_waves ORDER BY created_at DESC LIMIT 1",
443 )
444 .fetch_one(&h.db)
445 .await
446 .unwrap();
447 assert_eq!(wave_number, 1);
448 assert_eq!(lottery_count, 2);
449
450 // Count approved vs pending
451 let approved: i64 = sqlx::query_scalar(
452 "SELECT COUNT(*) FROM creator_waitlist WHERE status = 'approved'",
453 )
454 .fetch_one(&h.db)
455 .await
456 .unwrap();
457 let pending: i64 = sqlx::query_scalar(
458 "SELECT COUNT(*) FROM creator_waitlist WHERE status = 'pending'",
459 )
460 .fetch_one(&h.db)
461 .await
462 .unwrap();
463 assert_eq!(approved, 2, "2 applicants should be approved");
464 assert_eq!(pending, 1, "1 applicant should still be pending");
465
466 // Count users who got creator access
467 let creators: i64 = sqlx::query_scalar(
468 "SELECT COUNT(*) FROM users WHERE can_create_projects = true AND id = ANY($1)",
469 )
470 .bind(user_ids.iter().map(|id| **id).collect::<Vec<uuid::Uuid>>())
471 .fetch_one(&h.db)
472 .await
473 .unwrap();
474 assert_eq!(creators, 2, "2 winners should have creator access");
475 }
476