//! Invite code creation, redemption, and limit enforcement tests. use crate::harness::TestHarness; #[tokio::test] async fn invite_create_as_creator() { let mut h = TestHarness::new().await; let user_id = h.signup("invcreator", "invcreator@test.com", "password123").await; h.grant_creator(user_id).await; h.client.post_form("/logout", "").await; h.login("invcreator", "password123").await; let resp = h.client.post_form("/api/invites/create", "").await; assert!(resp.status.is_success(), "Create invite failed: {} {}", resp.status, resp.text); assert!(resp.text.contains("Invite code:"), "Response should contain 'Invite code:', got: {}", resp.text); assert!(resp.text.contains("makenot.work/join?invite="), "Response should contain invite link"); } #[tokio::test] async fn invite_non_creator_blocked() { let mut h = TestHarness::new().await; let _user_id = h.signup("invnoncreator", "invnoncreator@test.com", "password123").await; let resp = h.client.post_form("/api/invites/create", "").await; assert!(resp.text.contains("creator access"), "Non-creator should see 'creator access' error, got: {}", resp.text); } #[tokio::test] async fn invite_redeem_on_signup() { let mut h = TestHarness::new().await; // Creator generates an invite let creator_id = h.signup("inviter", "inviter@test.com", "password123").await; h.grant_creator(creator_id).await; h.client.post_form("/logout", "").await; h.login("inviter", "password123").await; let resp = h.client.post_form("/api/invites/create", "").await; assert!(resp.status.is_success()); // Extract the formatted code (XXXX-XXXX-XXXX) from response let text = &resp.text; let code_start = text.find("Invite code: ").expect("should contain 'Invite code: '") + "Invite code: ".len(); let code_end = text[code_start..].find(" ").map(|i| code_start + i).unwrap_or(code_start + 14); let formatted_code = &text[code_start..code_end]; // Log out, sign up a new user with the invite code h.client.post_form("/logout", "").await; h.client.fetch_csrf_token().await; let body = format!( "username=invitee&email=invitee@test.com&password=password123&invite_code={}", urlencoding::encode(formatted_code), ); let resp = h.client.post_form("/join/step/account", &body).await; assert!( resp.status.is_success() || resp.status.is_redirection(), "Signup with invite failed: {} {}", resp.status, resp.text, ); // Verify the invite was redeemed in the DB let redeemed: Option<(uuid::Uuid,)> = sqlx::query_as( "SELECT redeemed_by_id FROM invite_codes WHERE creator_id = $1 AND redeemed_by_id IS NOT NULL" ) .bind(creator_id) .fetch_optional(&h.db) .await .unwrap(); assert!(redeemed.is_some(), "Invite code should have been redeemed"); } #[tokio::test] async fn invite_limit_enforced() { let mut h = TestHarness::new().await; let user_id = h.signup("invlimit", "invlimit@test.com", "password123").await; h.grant_creator(user_id).await; h.client.post_form("/logout", "").await; h.login("invlimit", "password123").await; // Create 5 invites (the limit) for i in 0..5 { let resp = h.client.post_form("/api/invites/create", "").await; assert!( resp.text.contains("Invite code:"), "Invite {} should succeed, got: {}", i + 1, resp.text, ); } // 6th should be rejected let resp = h.client.post_form("/api/invites/create", "").await; assert!( resp.text.contains("limit"), "6th invite should mention 'limit', got: {}", resp.text, ); }