better-auth 1.0.0-alpha.2

The most comprehensive authentication framework for Rust
Documentation
#![allow(
    clippy::expect_used,
    clippy::unwrap_used,
    clippy::indexing_slicing,
    reason = "email normalization tests intentionally use panic-on-failure assertions and direct JSON indexing for concise behavior checks"
)]

mod compat;

use better_auth::prelude::{CreateUser, UpdateUser};
use compat::helpers::*;
use serde_json::json;

#[tokio::test]
async fn store_create_user_normalizes_email_and_lookup_matches_any_case() {
    let harness = TestHarness::minimal().await;
    let auth = harness.auth();

    let mut create_user = CreateUser::new().with_name("Mixed Case");
    create_user.email = Some("Mixed.Case@Test.com".to_string());

    let user = auth.store().create_user(create_user).await.unwrap();

    assert_eq!(user.email.as_deref(), Some("mixed.case@test.com"));

    let lower = auth
        .store()
        .get_user_by_email("mixed.case@test.com")
        .await
        .unwrap()
        .expect("lowercase lookup should find the user");
    let upper = auth
        .store()
        .get_user_by_email("MIXED.CASE@TEST.COM")
        .await
        .unwrap()
        .expect("uppercase lookup should find the user");

    assert_eq!(lower.id, user.id);
    assert_eq!(upper.id, user.id);
}

#[tokio::test]
async fn store_update_user_normalizes_email_before_persisting() {
    let harness = TestHarness::minimal().await;
    let auth = harness.auth();

    let mut create_user = CreateUser::new().with_name("Original");
    create_user.email = Some("original@test.com".to_string());

    let user = auth.store().create_user(create_user).await.unwrap();

    let updated = auth
        .store()
        .update_user(
            &user.id,
            UpdateUser {
                email: Some("Updated.Case@Test.com".to_string()),
                ..Default::default()
            },
        )
        .await
        .unwrap();

    assert_eq!(updated.email.as_deref(), Some("updated.case@test.com"));

    let fetched = auth
        .store()
        .get_user_by_email("UPDATED.CASE@TEST.COM")
        .await
        .unwrap()
        .expect("normalized lookup should find the updated user");
    assert_eq!(fetched.id, user.id);
}

#[tokio::test]
async fn signup_mixed_case_email_persists_lowercase_and_signin_accepts_lowercase() {
    let harness = TestHarness::minimal().await;
    let auth = harness.auth();

    let (_signup_token, signup_body) =
        signup_user(auth, "Mixed.Signup@Test.com", "password123", "Mixed Signup").await;
    assert_eq!(signup_body["user"]["email"], "mixed.signup@test.com");

    let (_signin_token, signin_body) =
        signin_user(auth, "mixed.signup@test.com", "password123").await;
    assert_eq!(signin_body["user"]["email"], "mixed.signup@test.com");
}

#[tokio::test]
async fn signup_rejects_duplicate_email_with_different_case() {
    let harness = TestHarness::minimal().await;
    let auth = harness.auth();

    let _ = signup_user(auth, "Duplicate.Case@Test.com", "password123", "Duplicate").await;

    let (status, body) = send_request(
        auth,
        post_json(
            "/sign-up/email",
            json!({
                "email": "duplicate.case@test.com",
                "password": "password123",
                "name": "Duplicate Lowercase"
            }),
        ),
    )
    .await;

    assert_eq!(status, 422);
    assert_eq!(body["message"], "User already exists. Use another email.");
}

#[tokio::test]
async fn request_password_reset_finds_user_from_lowercase_email() {
    let harness = TestHarness::minimal().await;
    let auth = harness.auth();

    let _ = signup_user(auth, "Reset.Case@Test.com", "password123", "Reset Case").await;

    let (status, body) = send_request(
        auth,
        post_json(
            "/request-password-reset",
            json!({
                "email": "reset.case@test.com",
                "redirectTo": "http://localhost:3000/reset"
            }),
        ),
    )
    .await;

    assert_eq!(status, 200);
    assert_eq!(body["status"], true);
    assert!(
        take_reset_password_token("reset.case@test.com").is_some(),
        "password reset token should be captured for the normalized email"
    );
}