//! Waitlist: apply, duplicate rejected, pitch too short, unverified email, already creator. use crate::harness::TestHarness; #[tokio::test] async fn waitlist_apply_success() { let mut h = TestHarness::new().await; let user_id = h .signup("wapplicant", "wapplicant@test.com", "password123") .await; // Verify email sqlx::query("UPDATE users SET email_verified = true WHERE id = $1") .bind(*user_id) .execute(&h.db) .await .unwrap(); let pitch = "I create independent music and want to sell my albums directly to fans without middlemen."; let resp = h .client .post_form( "/api/waitlist/apply", &format!("pitch={}", urlencoding::encode(pitch)), ) .await; assert!( resp.status.is_success() || resp.status == 204, "Waitlist apply should succeed, got {} {}", resp.status, resp.text ); // Verify in DB let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM creator_waitlist WHERE user_id = $1") .bind(*user_id) .fetch_one(&h.db) .await .unwrap(); assert_eq!(count, 1, "Waitlist entry should exist in database"); } #[tokio::test] async fn waitlist_duplicate_rejected() { let mut h = TestHarness::new().await; let user_id = h .signup("wduplicate", "wduplicate@test.com", "password123") .await; // Verify email sqlx::query("UPDATE users SET email_verified = true WHERE id = $1") .bind(*user_id) .execute(&h.db) .await .unwrap(); let pitch = "I want to sell my handcrafted digital art directly to collectors worldwide."; // First application let resp = h .client .post_form( "/api/waitlist/apply", &format!("pitch={}", urlencoding::encode(pitch)), ) .await; assert!( resp.status.is_success() || resp.status == 204, "First application should succeed, got {} {}", resp.status, resp.text ); // Second application — should be rejected let resp = h .client .post_form( "/api/waitlist/apply", &format!("pitch={}", urlencoding::encode(pitch)), ) .await; assert_eq!( resp.status, 400, "Duplicate application should be rejected, got {} {}", resp.status, resp.text ); } #[tokio::test] async fn waitlist_pitch_too_short() { let mut h = TestHarness::new().await; let user_id = h .signup("wshort", "wshort@test.com", "password123") .await; // Verify email sqlx::query("UPDATE users SET email_verified = true WHERE id = $1") .bind(*user_id) .execute(&h.db) .await .unwrap(); // Pitch under 20 characters let resp = h .client .post_form("/api/waitlist/apply", "pitch=too+short") .await; assert!( resp.status == 400 || resp.status == 422, "Short pitch should be rejected, got {} {}", resp.status, resp.text ); } #[tokio::test] async fn waitlist_unverified_email_rejected() { let mut h = TestHarness::new().await; let _user_id = h .signup("wunverified", "wunverified@test.com", "password123") .await; // Don't verify email — apply should fail let pitch = "I create podcasts about technology and want a better home for my content."; let resp = h .client .post_form( "/api/waitlist/apply", &format!("pitch={}", urlencoding::encode(pitch)), ) .await; assert_eq!( resp.status, 400, "Unverified email should be rejected, got {} {}", resp.status, resp.text ); } #[tokio::test] async fn waitlist_already_creator_rejected() { let mut h = TestHarness::new().await; let user_id = h .signup("walready", "walready@test.com", "password123") .await; // Verify email AND grant creator sqlx::query("UPDATE users SET email_verified = true WHERE id = $1") .bind(*user_id) .execute(&h.db) .await .unwrap(); h.grant_creator(user_id).await; // Re-login so session reflects can_create_projects = true h.client.post_form("/logout", "").await; h.login("walready", "password123").await; let pitch = "I already have creator access but am applying again for some reason."; let resp = h .client .post_form( "/api/waitlist/apply", &format!("pitch={}", urlencoding::encode(pitch)), ) .await; assert_eq!( resp.status, 400, "Already-creator should be rejected, got {} {}", resp.status, resp.text ); } #[tokio::test] async fn waitlist_pitch_too_long() { let mut h = TestHarness::new().await; let user_id = h .signup("wlong", "wlong@test.com", "password123") .await; sqlx::query("UPDATE users SET email_verified = true WHERE id = $1") .bind(*user_id) .execute(&h.db) .await .unwrap(); // Pitch > 500 chars let long_pitch = "x".repeat(501); let resp = h .client .post_form( "/api/waitlist/apply", &format!("pitch={}", urlencoding::encode(&long_pitch)), ) .await; assert!( resp.status == 400 || resp.status == 422, "Pitch >500 chars should be rejected, got {} {}", resp.status, resp.text ); } #[tokio::test] async fn waitlist_unauthenticated_rejected() { let mut h = TestHarness::new().await; // Just fetch CSRF — no login h.client.fetch_csrf_token().await; let pitch = "I want to sell my art but I am not logged in for some reason right now."; let resp = h .client .post_form( "/api/waitlist/apply", &format!("pitch={}", urlencoding::encode(pitch)), ) .await; assert_eq!( resp.status, 401, "Unauthenticated apply should be 401, got {} {}", resp.status, resp.text ); } #[tokio::test] async fn waitlist_non_admin_gets_404() { let (mut h, _admin_id) = TestHarness::with_admin().await; // Sign up a regular user (not the admin) let _user_id = h.signup("wnonadmin", "wnonadmin@test.com", "password123").await; // Regular user tries admin waitlist routes — should get 404 (hidden) let resp = h.client.get("/admin/waitlist").await; assert_eq!( resp.status, 404, "Non-admin GET /admin/waitlist should be 404, got {} {}", resp.status, resp.text ); let resp = h .client .post_form("/api/admin/waitlist/00000000-0000-0000-0000-000000000000/approve", "") .await; assert_eq!( resp.status, 404, "Non-admin POST approve should be 404, got {} {}", resp.status, resp.text ); let resp = h .client .post_form("/api/admin/lottery", "count=1") .await; assert_eq!( resp.status, 404, "Non-admin POST lottery should be 404, got {} {}", resp.status, resp.text ); } #[tokio::test] async fn waitlist_admin_approve() { let (mut h, _admin_id) = TestHarness::with_admin().await; // Create an applicant let user_id = h.signup("wapprove", "wapprove@test.com", "password123").await; sqlx::query("UPDATE users SET email_verified = true WHERE id = $1") .bind(*user_id) .execute(&h.db) .await .unwrap(); let pitch = "I create electronic music and want to sell my albums independently."; let resp = h .client .post_form( "/api/waitlist/apply", &format!("pitch={}", urlencoding::encode(pitch)), ) .await; assert!( resp.status.is_success() || resp.status == 204, "Waitlist apply failed: {} {}", resp.status, resp.text ); // Get waitlist entry ID let entry_id: uuid::Uuid = sqlx::query_scalar("SELECT id FROM creator_waitlist WHERE user_id = $1") .bind(*user_id) .fetch_one(&h.db) .await .unwrap(); // Log in as admin h.client.post_form("/logout", "").await; h.login("admin", "password123").await; // Approve the entry let resp = h .client .post_form(&format!("/api/admin/waitlist/{}/approve", entry_id), "") .await; assert!( resp.status.is_success(), "Admin approve failed: {} {}", resp.status, resp.text ); // Verify: status=approved, method=hand_picked let (status, method): (String, Option) = sqlx::query_as( "SELECT status, selection_method FROM creator_waitlist WHERE id = $1", ) .bind(entry_id) .fetch_one(&h.db) .await .unwrap(); assert_eq!(status, "approved"); assert_eq!(method.as_deref(), Some("hand_picked")); // Verify: user is now a creator let can_create: bool = sqlx::query_scalar("SELECT can_create_projects FROM users WHERE id = $1") .bind(*user_id) .fetch_one(&h.db) .await .unwrap(); assert!(can_create, "Approved user should be a creator"); } #[tokio::test] async fn waitlist_admin_spam() { let (mut h, _admin_id) = TestHarness::with_admin().await; // Create an applicant let user_id = h.signup("wspam", "wspam@test.com", "password123").await; sqlx::query("UPDATE users SET email_verified = true WHERE id = $1") .bind(*user_id) .execute(&h.db) .await .unwrap(); let pitch = "Buy my crypto course and get rich quick with this one weird trick now."; let resp = h .client .post_form( "/api/waitlist/apply", &format!("pitch={}", urlencoding::encode(pitch)), ) .await; assert!( resp.status.is_success() || resp.status == 204, "Waitlist apply failed: {} {}", resp.status, resp.text ); let entry_id: uuid::Uuid = sqlx::query_scalar("SELECT id FROM creator_waitlist WHERE user_id = $1") .bind(*user_id) .fetch_one(&h.db) .await .unwrap(); // Log in as admin h.client.post_form("/logout", "").await; h.login("admin", "password123").await; // Mark as spam let resp = h .client .post_form(&format!("/api/admin/waitlist/{}/spam", entry_id), "") .await; assert!( resp.status.is_success(), "Admin spam failed: {} {}", resp.status, resp.text ); // Verify: status=spam let status: String = sqlx::query_scalar("SELECT status FROM creator_waitlist WHERE id = $1") .bind(entry_id) .fetch_one(&h.db) .await .unwrap(); assert_eq!(status, "spam"); // Verify: user is NOT a creator let can_create: bool = sqlx::query_scalar("SELECT can_create_projects FROM users WHERE id = $1") .bind(*user_id) .fetch_one(&h.db) .await .unwrap(); assert!(!can_create, "Spammed user should not be a creator"); } #[tokio::test] async fn waitlist_lottery_flow() { let (mut h, _admin_id) = TestHarness::with_admin().await; // Create 3 applicants let mut user_ids = Vec::new(); for (name, email) in [ ("wlot1", "wlot1@test.com"), ("wlot2", "wlot2@test.com"), ("wlot3", "wlot3@test.com"), ] { let uid = h.signup(name, email, "password123").await; sqlx::query("UPDATE users SET email_verified = true WHERE id = $1") .bind(*uid) .execute(&h.db) .await .unwrap(); let pitch = format!("I create amazing {name} content and want to share it with the world."); h.client .post_form( "/api/waitlist/apply", &format!("pitch={}", urlencoding::encode(&pitch)), ) .await; h.client.post_form("/logout", "").await; user_ids.push(uid); } // Log in as admin h.login("admin", "password123").await; // Run lottery: draw 2 out of 3 let resp = h .client .post_form("/api/admin/lottery", "count=2") .await; assert!( resp.status.is_success() || resp.status.is_redirection(), "Admin lottery failed: {} {}", resp.status, resp.text ); // Verify: a wave was created with wave_number=1 let (wave_number, lottery_count): (i32, i32) = sqlx::query_as( "SELECT wave_number, lottery_count FROM creator_waves ORDER BY created_at DESC LIMIT 1", ) .fetch_one(&h.db) .await .unwrap(); assert_eq!(wave_number, 1); assert_eq!(lottery_count, 2); // Count approved vs pending let approved: i64 = sqlx::query_scalar( "SELECT COUNT(*) FROM creator_waitlist WHERE status = 'approved'", ) .fetch_one(&h.db) .await .unwrap(); let pending: i64 = sqlx::query_scalar( "SELECT COUNT(*) FROM creator_waitlist WHERE status = 'pending'", ) .fetch_one(&h.db) .await .unwrap(); assert_eq!(approved, 2, "2 applicants should be approved"); assert_eq!(pending, 1, "1 applicant should still be pending"); // Count users who got creator access let creators: i64 = sqlx::query_scalar( "SELECT COUNT(*) FROM users WHERE can_create_projects = true AND id = ANY($1)", ) .bind(user_ids.iter().map(|id| **id).collect::>()) .fetch_one(&h.db) .await .unwrap(); assert_eq!(creators, 2, "2 winners should have creator access"); }