Skip to main content

max / makenotwork

13.5 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 =
20 "I create independent music and want to sell my albums directly to fans without middlemen.";
21 let resp = h
22 .client
23 .post_form(
24 "/api/waitlist/apply",
25 &format!("pitch={}", urlencoding::encode(pitch)),
26 )
27 .await;
28 assert!(
29 resp.status.is_success() || resp.status == 204,
30 "Waitlist apply should succeed, got {} {}",
31 resp.status,
32 resp.text
33 );
34
35 // Verify in DB
36 let count: i64 = 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.signup("wshort", "wshort@test.com", "password123").await;
94
95 // Verify email
96 sqlx::query("UPDATE users SET email_verified = true WHERE id = $1")
97 .bind(*user_id)
98 .execute(&h.db)
99 .await
100 .unwrap();
101
102 // Pitch under 20 characters
103 let resp = h
104 .client
105 .post_form("/api/waitlist/apply", "pitch=too+short")
106 .await;
107 assert!(
108 resp.status == 400 || resp.status == 422,
109 "Short pitch should be rejected, got {} {}",
110 resp.status,
111 resp.text
112 );
113 }
114
115 #[tokio::test]
116 async fn waitlist_unverified_email_rejected() {
117 let mut h = TestHarness::new().await;
118 let _user_id = h
119 .signup("wunverified", "wunverified@test.com", "password123")
120 .await;
121
122 // Don't verify email, apply should fail
123 let pitch = "I create podcasts about technology and want a better home for my content.";
124 let resp = h
125 .client
126 .post_form(
127 "/api/waitlist/apply",
128 &format!("pitch={}", urlencoding::encode(pitch)),
129 )
130 .await;
131 assert_eq!(
132 resp.status, 400,
133 "Unverified email should be rejected, got {} {}",
134 resp.status, resp.text
135 );
136 }
137
138 #[tokio::test]
139 async fn waitlist_already_creator_rejected() {
140 let mut h = TestHarness::new().await;
141 let user_id = h
142 .signup("walready", "walready@test.com", "password123")
143 .await;
144
145 // Verify email AND grant creator
146 sqlx::query("UPDATE users SET email_verified = true WHERE id = $1")
147 .bind(*user_id)
148 .execute(&h.db)
149 .await
150 .unwrap();
151 h.grant_creator(user_id).await;
152
153 // Re-login so session reflects can_create_projects = true
154 h.client.post_form("/logout", "").await;
155 h.login("walready", "password123").await;
156
157 let pitch = "I already have creator access but am applying again for some reason.";
158 let resp = h
159 .client
160 .post_form(
161 "/api/waitlist/apply",
162 &format!("pitch={}", urlencoding::encode(pitch)),
163 )
164 .await;
165 assert_eq!(
166 resp.status, 400,
167 "Already-creator should be rejected, got {} {}",
168 resp.status, resp.text
169 );
170 }
171
172 #[tokio::test]
173 async fn waitlist_pitch_too_long() {
174 let mut h = TestHarness::new().await;
175 let user_id = h.signup("wlong", "wlong@test.com", "password123").await;
176
177 sqlx::query("UPDATE users SET email_verified = true WHERE id = $1")
178 .bind(*user_id)
179 .execute(&h.db)
180 .await
181 .unwrap();
182
183 // Pitch > 500 chars
184 let long_pitch = "x".repeat(501);
185 let resp = h
186 .client
187 .post_form(
188 "/api/waitlist/apply",
189 &format!("pitch={}", urlencoding::encode(&long_pitch)),
190 )
191 .await;
192 assert!(
193 resp.status == 400 || resp.status == 422,
194 "Pitch >500 chars should be rejected, got {} {}",
195 resp.status,
196 resp.text
197 );
198 }
199
200 #[tokio::test]
201 async fn waitlist_unauthenticated_rejected() {
202 let mut h = TestHarness::new().await;
203 // Just fetch CSRF, no login
204 h.client.fetch_csrf_token().await;
205
206 let pitch = "I want to sell my art but I am not logged in for some reason right now.";
207 let resp = h
208 .client
209 .post_form(
210 "/api/waitlist/apply",
211 &format!("pitch={}", urlencoding::encode(pitch)),
212 )
213 .await;
214 assert_eq!(
215 resp.status, 401,
216 "Unauthenticated apply should be 401, got {} {}",
217 resp.status, resp.text
218 );
219 }
220
221 #[tokio::test]
222 async fn waitlist_non_admin_gets_404() {
223 let (mut h, _admin_id) = TestHarness::with_admin().await;
224
225 // Sign up a regular user (not the admin)
226 let _user_id = h
227 .signup("wnonadmin", "wnonadmin@test.com", "password123")
228 .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(
241 "/api/admin/waitlist/00000000-0000-0000-0000-000000000000/approve",
242 "",
243 )
244 .await;
245 assert_eq!(
246 resp.status, 404,
247 "Non-admin POST approve should be 404, got {} {}",
248 resp.status, resp.text
249 );
250
251 let resp = h.client.post_form("/api/admin/lottery", "count=1").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
265 .signup("wapprove", "wapprove@test.com", "password123")
266 .await;
267 sqlx::query("UPDATE users SET email_verified = true WHERE id = $1")
268 .bind(*user_id)
269 .execute(&h.db)
270 .await
271 .unwrap();
272
273 let pitch = "I create electronic music and want to sell my albums independently.";
274 let resp = h
275 .client
276 .post_form(
277 "/api/waitlist/apply",
278 &format!("pitch={}", urlencoding::encode(pitch)),
279 )
280 .await;
281 assert!(
282 resp.status.is_success() || resp.status == 204,
283 "Waitlist apply failed: {} {}",
284 resp.status,
285 resp.text
286 );
287
288 // Get waitlist entry ID
289 let entry_id: uuid::Uuid =
290 sqlx::query_scalar("SELECT id FROM creator_waitlist WHERE user_id = $1")
291 .bind(*user_id)
292 .fetch_one(&h.db)
293 .await
294 .unwrap();
295
296 // Log in as admin
297 h.client.post_form("/logout", "").await;
298 h.login("admin", "password123").await;
299
300 // Approve the entry
301 let resp = h
302 .client
303 .post_form(&format!("/api/admin/waitlist/{entry_id}/approve"), "")
304 .await;
305 assert!(
306 resp.status.is_success(),
307 "Admin approve failed: {} {}",
308 resp.status,
309 resp.text
310 );
311
312 // Verify: status=approved, method=hand_picked
313 let (status, method): (String, Option<String>) =
314 sqlx::query_as("SELECT status, selection_method FROM creator_waitlist WHERE id = $1")
315 .bind(entry_id)
316 .fetch_one(&h.db)
317 .await
318 .unwrap();
319 assert_eq!(status, "approved");
320 assert_eq!(method.as_deref(), Some("hand_picked"));
321
322 // Verify: user is now a creator
323 let can_create: bool =
324 sqlx::query_scalar("SELECT can_create_projects FROM users WHERE id = $1")
325 .bind(*user_id)
326 .fetch_one(&h.db)
327 .await
328 .unwrap();
329 assert!(can_create, "Approved user should be a creator");
330 }
331
332 #[tokio::test]
333 async fn waitlist_admin_spam() {
334 let (mut h, _admin_id) = TestHarness::with_admin().await;
335
336 // Create an applicant
337 let user_id = h.signup("wspam", "wspam@test.com", "password123").await;
338 sqlx::query("UPDATE users SET email_verified = true WHERE id = $1")
339 .bind(*user_id)
340 .execute(&h.db)
341 .await
342 .unwrap();
343
344 let pitch = "Buy my crypto course and get rich quick with this one weird trick now.";
345 let resp = h
346 .client
347 .post_form(
348 "/api/waitlist/apply",
349 &format!("pitch={}", urlencoding::encode(pitch)),
350 )
351 .await;
352 assert!(
353 resp.status.is_success() || resp.status == 204,
354 "Waitlist apply failed: {} {}",
355 resp.status,
356 resp.text
357 );
358
359 let entry_id: uuid::Uuid =
360 sqlx::query_scalar("SELECT id FROM creator_waitlist WHERE user_id = $1")
361 .bind(*user_id)
362 .fetch_one(&h.db)
363 .await
364 .unwrap();
365
366 // Log in as admin
367 h.client.post_form("/logout", "").await;
368 h.login("admin", "password123").await;
369
370 // Mark as spam
371 let resp = h
372 .client
373 .post_form(&format!("/api/admin/waitlist/{entry_id}/spam"), "")
374 .await;
375 assert!(
376 resp.status.is_success(),
377 "Admin spam failed: {} {}",
378 resp.status,
379 resp.text
380 );
381
382 // Verify: status=spam
383 let status: String = sqlx::query_scalar("SELECT status FROM creator_waitlist WHERE id = $1")
384 .bind(entry_id)
385 .fetch_one(&h.db)
386 .await
387 .unwrap();
388 assert_eq!(status, "spam");
389
390 // Verify: user is NOT a creator
391 let can_create: bool =
392 sqlx::query_scalar("SELECT can_create_projects FROM users WHERE id = $1")
393 .bind(*user_id)
394 .fetch_one(&h.db)
395 .await
396 .unwrap();
397 assert!(!can_create, "Spammed user should not be a creator");
398 }
399
400 #[tokio::test]
401 async fn waitlist_lottery_flow() {
402 let (mut h, _admin_id) = TestHarness::with_admin().await;
403
404 // Create 3 applicants
405 let mut user_ids = Vec::new();
406 for (name, email) in [
407 ("wlot1", "wlot1@test.com"),
408 ("wlot2", "wlot2@test.com"),
409 ("wlot3", "wlot3@test.com"),
410 ] {
411 let uid = h.signup(name, email, "password123").await;
412 sqlx::query("UPDATE users SET email_verified = true WHERE id = $1")
413 .bind(*uid)
414 .execute(&h.db)
415 .await
416 .unwrap();
417
418 let pitch = format!("I create amazing {name} content and want to share it with the world.");
419 h.client
420 .post_form(
421 "/api/waitlist/apply",
422 &format!("pitch={}", urlencoding::encode(&pitch)),
423 )
424 .await;
425
426 h.client.post_form("/logout", "").await;
427 user_ids.push(uid);
428 }
429
430 // Log in as admin
431 h.login("admin", "password123").await;
432
433 // Run lottery: draw 2 out of 3
434 let resp = h.client.post_form("/api/admin/lottery", "count=2").await;
435 assert!(
436 resp.status.is_success() || resp.status.is_redirection(),
437 "Admin lottery failed: {} {}",
438 resp.status,
439 resp.text
440 );
441
442 // Verify: a wave was created with wave_number=1
443 let (wave_number, lottery_count): (i32, i32) = sqlx::query_as(
444 "SELECT wave_number, lottery_count FROM creator_waves ORDER BY created_at DESC LIMIT 1",
445 )
446 .fetch_one(&h.db)
447 .await
448 .unwrap();
449 assert_eq!(wave_number, 1);
450 assert_eq!(lottery_count, 2);
451
452 // Count approved vs pending
453 let approved: i64 =
454 sqlx::query_scalar("SELECT COUNT(*) FROM creator_waitlist WHERE status = 'approved'")
455 .fetch_one(&h.db)
456 .await
457 .unwrap();
458 let pending: i64 =
459 sqlx::query_scalar("SELECT COUNT(*) FROM creator_waitlist WHERE status = 'pending'")
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