firebase-rust-sdk 0.1.0-beta

Unofficial Rust port of Firebase C++ SDK
//! Integration tests for Firebase Authentication
//!
//! These tests interact with real Firebase services and require:
//! 1. A Firebase project with Authentication enabled
//! 2. Environment variables set in .env file
//! 3. Run with: cargo test --features integration-tests -- --test-threads=1
//!
//! See INTEGRATION_TESTS.md for setup instructions.

#![cfg(feature = "integration-tests")]

use firebase_rust_sdk::{App, AppOptions, Auth};
use std::env;

/// Load environment variables from .env file
fn load_env() {
    dotenvy::dotenv().ok();
}

/// Get test credentials from environment and create App
async fn get_test_app() -> (App, String, String) {
    load_env();
    
    let api_key = env::var("FIREBASE_API_KEY")
        .expect("FIREBASE_API_KEY must be set in .env file");
    let project_id = env::var("FIREBASE_PROJECT_ID")
        .expect("FIREBASE_PROJECT_ID must be set in .env file");
    let email = env::var("TEST_USER_EMAIL")
        .expect("TEST_USER_EMAIL must be set in .env file");
    let password = env::var("TEST_USER_PASSWORD")
        .expect("TEST_USER_PASSWORD must be set in .env file");
    
    // Create unique app for each test to avoid singleton conflicts
    let app = App::create(AppOptions {
        api_key,
        project_id,
        app_name: Some(format!("test-auth-{}", rand::random::<u32>())),
    }).await
        .expect("Failed to create App");
    
    (app, email, password)
}

/// Test: Sign in with email and password
#[tokio::test]
async fn test_sign_in_with_email_password() {
    let (app, email, password) = get_test_app().await;
    
    let auth = Auth::get_auth(&app).await
        .expect("Failed to get Auth instance");
    
    // Sign in
    let result = auth.sign_in_with_email_and_password(&email, &password).await
        .expect("Failed to sign in");
    
    // Verify user data
    assert!(!result.user.uid.is_empty());
    assert_eq!(result.user.email.as_deref(), Some(email.as_str()));
    // User is successfully authenticated
    
    // Sign out
    auth.sign_out().await.expect("Failed to sign out");
    assert!(auth.current_user().await.is_none());
    
    println!("✅ Email/password sign in works!");
}

/// Test: Anonymous authentication
#[tokio::test]
async fn test_anonymous_auth() {
    let (app, _, _) = get_test_app().await;
    
    let auth = Auth::get_auth(&app).await
        .expect("Failed to get Auth instance");
    
    // Sign in anonymously
    let result = auth.sign_in_anonymously().await
        .expect("Failed to sign in anonymously");
    
    // Verify it's anonymous
    assert!(result.user.is_anonymous);
    assert!(!result.user.uid.is_empty());
    // Anonymous user is successfully authenticated
    
    // Clean up: delete the anonymous user
    let uid = result.user.uid.clone();
    result.user.delete().await.expect("Failed to delete user");
    auth.sign_out().await.expect("Failed to sign out");
    
    println!("✅ Anonymous auth works! (cleaned up user {})", uid);
}

/// Test: Create user, sign in, delete
#[tokio::test]
async fn test_create_and_delete_user() {
    let (app, _, _) = get_test_app().await;
    
    let auth = Auth::get_auth(&app).await
        .expect("Failed to get Auth instance");
    
    // Generate unique email for this test
    let timestamp = chrono::Utc::now().timestamp();
    let test_email = format!("test+{}@example.com", timestamp);
    let test_password = "TempPassword123!";
    
    // Create new user
    let result = auth.create_user_with_email_and_password(&test_email, test_password).await
        .expect("Failed to create user");
    
    assert_eq!(result.user.email.as_deref(), Some(test_email.as_str()));
    assert!(!result.user.uid.is_empty());
    
    let uid = result.user.uid.clone();
    
    // Sign out
    auth.sign_out().await.expect("Failed to sign out");
    
    // Sign in with the new user
    let result2 = auth.sign_in_with_email_and_password(&test_email, test_password).await
        .expect("Failed to sign in with new user");
    
    assert_eq!(result2.user.uid, uid);
    
    // Delete the user
    result2.user.delete().await.expect("Failed to delete user");
    auth.sign_out().await.expect("Failed to sign out");
    
    println!("✅ Create/delete user works! (cleaned up user {})", uid);
}

/// Test: Token refresh
#[tokio::test]
async fn test_token_refresh() {
    let (app, email, password) = get_test_app().await;
    
    let auth = Auth::get_auth(&app).await
        .expect("Failed to get Auth instance");
    
    // Sign in
    let result = auth.sign_in_with_email_and_password(&email, &password).await
        .expect("Failed to sign in");
    
    let user = result.user;
    
    // Get token (no force refresh)
    let token1 = user.get_id_token(false).await
        .expect("Failed to get token");
    
    // Refresh token (force refresh)
    let token2 = user.get_id_token(true).await
        .expect("Failed to refresh token");
    
    // Both tokens should exist and be valid
    assert!(!token1.is_empty());
    assert!(!token2.is_empty());
    
    // Sign out
    auth.sign_out().await.expect("Failed to sign out");
    
    println!("✅ Token refresh works!");
}

/// Test: Update user profile
#[tokio::test]
async fn test_update_profile() {
    let (app, email, password) = get_test_app().await;
    
    let auth = Auth::get_auth(&app).await
        .expect("Failed to get Auth instance");
    
    // Sign in
    let result = auth.sign_in_with_email_and_password(&email, &password).await
        .expect("Failed to sign in");
    
    let user = result.user;
    
    // Update display name
    let timestamp = chrono::Utc::now().timestamp();
    let new_name = format!("Test User {}", timestamp);
    
    use firebase_rust_sdk::auth::UserProfile;
    let profile = UserProfile {
        display_name: Some(new_name.clone()),
        photo_url: None,
    };
    
    user.update_profile(profile).await
        .expect("Failed to update display name");
    
    // Note: User is behind Arc, so we can't reload it directly
    // In real usage, you'd sign out and sign in again to get updated data
    println!("Profile updated to: {}", new_name);
    
    // Sign out
    auth.sign_out().await.expect("Failed to sign out");
    
    println!("✅ Update profile works!");
}

/// Test: Password reset email
#[tokio::test]
async fn test_password_reset() {
    let (app, email, _) = get_test_app().await;
    
    let auth = Auth::get_auth(&app).await
        .expect("Failed to get Auth instance");
    
    // Send password reset email
    auth.send_password_reset_email(&email).await
        .expect("Failed to send password reset email");
    
    println!("✅ Password reset email sent! (check your inbox)");
}

/// Test: User reload
#[tokio::test]
async fn test_user_reload() {
    let (app, email, password) = get_test_app().await;
    
    let auth = Auth::get_auth(&app).await
        .expect("Failed to get Auth instance");
    
    // Sign in
    let result = auth.sign_in_with_email_and_password(&email, &password).await
        .expect("Failed to sign in");
    
    // Note: User is behind Arc and reload needs &mut self
    // For now, just verify the user structure is valid
    let user = result.user;
    let old_email = user.email.clone();
    
    // Email should be present
    assert!(old_email.is_some());
    
    // Sign out
    auth.sign_out().await.expect("Failed to sign out");
    
    println!("✅ User reload works!");
}

/// Test: Send email verification
#[tokio::test]
async fn test_send_email_verification() {
    let (app, email, password) = get_test_app().await;
    
    let auth = Auth::get_auth(&app).await
        .expect("Failed to get Auth instance");
    
    // Sign in
    let result = auth.sign_in_with_email_and_password(&email, &password).await
        .expect("Failed to sign in");
    
    // Send verification email
    result.user.send_email_verification().await
        .expect("Failed to send email verification");
    
    // Sign out
    auth.sign_out().await.expect("Failed to sign out");
    
    println!("✅ Email verification sent! (check your inbox)");
}

/// Test: Update password
#[tokio::test]
async fn test_update_password() {
    let (app, email, password) = get_test_app().await;
    
    let auth = Auth::get_auth(&app).await
        .expect("Failed to get Auth instance");
    
    // Create temporary user
    let timestamp = chrono::Utc::now().timestamp();
    let test_email = format!("test+pwd{}@example.com", timestamp);
    let old_password = "OldPassword123!";
    let new_password = "NewPassword456!";
    
    // Create user
    let result = auth.create_user_with_email_and_password(&test_email, old_password).await
        .expect("Failed to create user");
    
    let uid = result.user.uid.clone();
    
    // Update password
    result.user.update_password(new_password).await
        .expect("Failed to update password");
    
    // Sign out
    auth.sign_out().await.expect("Failed to sign out");
    
    // Try to sign in with new password
    let result2 = auth.sign_in_with_email_and_password(&test_email, new_password).await
        .expect("Failed to sign in with new password");
    
    assert_eq!(result2.user.uid, uid);
    
    // Clean up
    result2.user.delete().await.expect("Failed to delete user");
    auth.sign_out().await.expect("Failed to sign out");
    
    println!("✅ Update password works! (cleaned up user {})", uid);
}