Skip to main content

max / makenotwork

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