Readur/tests/integration_admin_password_...

278 lines
9.4 KiB
Rust

use anyhow::Result;
use readur::test_utils::{TestContext, TestAuthHelper};
use readur::{seed, commands, models::{CreateUser, UserRole}};
/// Test that admin user is created with auto-generated password on first run
#[tokio::test]
async fn test_admin_seed_creates_user_with_auto_password() {
let ctx = TestContext::new().await;
let result: Result<()> = async {
// Clear any env vars to test auto-generation
std::env::remove_var("ADMIN_PASSWORD");
std::env::remove_var("ADMIN_USERNAME");
// Run seed
seed::seed_admin_user(&ctx.state.db).await?;
// Verify admin user exists
let admin = ctx.state.db.get_user_by_username("admin").await?
.expect("Admin user should exist");
// Verify role is Admin
assert_eq!(admin.role, UserRole::Admin, "User should have Admin role");
// Verify password is hashed (bcrypt format)
assert!(admin.password_hash.is_some(), "Password hash should exist");
let hash = admin.password_hash.unwrap();
assert!(
hash.starts_with("$2b$") || hash.starts_with("$2a$"),
"Password should be bcrypt hashed"
);
// Verify email format
assert_eq!(admin.email, "admin@readur.com", "Email should use default format");
Ok(())
}.await;
if let Err(e) = ctx.cleanup_and_close().await {
eprintln!("Warning: Test cleanup failed: {}", e);
}
result.unwrap();
}
/// Test that admin user is created with provided ADMIN_PASSWORD
#[tokio::test]
async fn test_admin_seed_uses_env_password() {
let ctx = TestContext::new().await;
let result: Result<()> = async {
// Set password via environment variable
std::env::set_var("ADMIN_PASSWORD", "testpass123");
std::env::remove_var("ADMIN_USERNAME");
// Run seed
seed::seed_admin_user(&ctx.state.db).await?;
// Verify admin user exists
let admin = ctx.state.db.get_user_by_username("admin").await?
.expect("Admin user should exist");
// Verify we can login with the provided password
let auth_helper = TestAuthHelper::new(ctx.app.clone());
let token = auth_helper.login_user("admin", "testpass123").await;
assert!(!token.is_empty(), "Should be able to login with provided password");
// Clean up env var
std::env::remove_var("ADMIN_PASSWORD");
Ok(())
}.await;
std::env::remove_var("ADMIN_PASSWORD");
if let Err(e) = ctx.cleanup_and_close().await {
eprintln!("Warning: Test cleanup failed: {}", e);
}
result.unwrap();
}
/// Test that subsequent runs don't duplicate admin user
#[tokio::test]
async fn test_admin_seed_does_not_duplicate_user() {
let ctx = TestContext::new().await;
let result: Result<()> = async {
std::env::remove_var("ADMIN_PASSWORD");
std::env::remove_var("ADMIN_USERNAME");
// Run seed first time
seed::seed_admin_user(&ctx.state.db).await?;
let first_admin = ctx.state.db.get_user_by_username("admin").await?
.expect("Admin should exist after first seed");
// Run seed second time
seed::seed_admin_user(&ctx.state.db).await?;
let second_admin = ctx.state.db.get_user_by_username("admin").await?
.expect("Admin should still exist after second seed");
// Verify same user (same ID and hash)
assert_eq!(first_admin.id, second_admin.id, "Should be the same user");
assert_eq!(
first_admin.password_hash, second_admin.password_hash,
"Password should not have changed"
);
// Verify only one admin exists
let all_users = ctx.state.db.get_all_users().await?;
let admin_count = all_users.iter().filter(|u| u.username == "admin").count();
assert_eq!(admin_count, 1, "Should only have one admin user");
Ok(())
}.await;
if let Err(e) = ctx.cleanup_and_close().await {
eprintln!("Warning: Test cleanup failed: {}", e);
}
result.unwrap();
}
/// Test that user can successfully login with generated credentials
#[tokio::test]
async fn test_admin_seed_allows_login() {
let ctx = TestContext::new().await;
let result: Result<()> = async {
// Set known password for testing
std::env::set_var("ADMIN_PASSWORD", "logintest123");
std::env::remove_var("ADMIN_USERNAME");
// Run seed
seed::seed_admin_user(&ctx.state.db).await?;
// Attempt login
let auth_helper = TestAuthHelper::new(ctx.app.clone());
let token = auth_helper.login_user("admin", "logintest123").await;
// Verify token is not empty
assert!(!token.is_empty(), "Should receive valid JWT token");
// Verify token is valid by making authenticated request
let _response = auth_helper
.make_authenticated_request("GET", "/api/auth/me", None, &token)
.await;
// If we get here without panicking, the authenticated request succeeded
// Clean up env var
std::env::remove_var("ADMIN_PASSWORD");
Ok(())
}.await;
std::env::remove_var("ADMIN_PASSWORD");
if let Err(e) = ctx.cleanup_and_close().await {
eprintln!("Warning: Test cleanup failed: {}", e);
}
result.unwrap();
}
/// Test that reset command generates new password and invalidates old one
#[tokio::test]
async fn test_reset_command_changes_password() {
let ctx = TestContext::new().await;
let result: Result<()> = async {
// Create admin with known password
let _ = ctx.state.db.create_user(CreateUser {
username: "admin".to_string(),
email: "admin@readur.com".to_string(),
password: "oldpass123".to_string(),
role: Some(UserRole::Admin),
}).await?;
// Get original password hash
let old_hash = ctx.state.db.get_user_by_username("admin")
.await?
.unwrap()
.password_hash;
// Reset password with new one
std::env::set_var("ADMIN_PASSWORD", "newpass456");
commands::reset_admin_password(&ctx.state.db).await?;
std::env::remove_var("ADMIN_PASSWORD");
// Get new password hash
let new_hash = ctx.state.db.get_user_by_username("admin")
.await?
.unwrap()
.password_hash;
// Verify password changed
assert_ne!(old_hash, new_hash, "Password hash should have changed");
// Verify new password works
let auth_helper = TestAuthHelper::new(ctx.app.clone());
let token = auth_helper.login_user("admin", "newpass456").await;
assert!(!token.is_empty(), "Should be able to login with new password");
// Note: We don't explicitly test that old password doesn't work because
// the auth helper panics on login failure. The fact that new password works
// and the hash changed is sufficient verification.
Ok(())
}.await;
std::env::remove_var("ADMIN_PASSWORD");
if let Err(e) = ctx.cleanup_and_close().await {
eprintln!("Warning: Test cleanup failed: {}", e);
}
result.unwrap();
}
/// Test that reset command uses provided ADMIN_PASSWORD
#[tokio::test]
async fn test_reset_command_uses_env_password() {
let ctx = TestContext::new().await;
let result: Result<()> = async {
// Create admin
let _ = ctx.state.db.create_user(CreateUser {
username: "admin".to_string(),
email: "admin@readur.com".to_string(),
password: "initial123".to_string(),
role: Some(UserRole::Admin),
}).await?;
// Reset with specific password
std::env::set_var("ADMIN_PASSWORD", "specific789");
commands::reset_admin_password(&ctx.state.db).await?;
std::env::remove_var("ADMIN_PASSWORD");
// Verify can login with the specific password
let auth_helper = TestAuthHelper::new(ctx.app.clone());
let token = auth_helper.login_user("admin", "specific789").await;
assert!(!token.is_empty(), "Should login with environment-specified password");
Ok(())
}.await;
std::env::remove_var("ADMIN_PASSWORD");
if let Err(e) = ctx.cleanup_and_close().await {
eprintln!("Warning: Test cleanup failed: {}", e);
}
result.unwrap();
}
/// Test that reset command returns error for non-existent user
#[tokio::test]
async fn test_reset_command_fails_for_nonexistent_user() {
let ctx = TestContext::new().await;
let result: Result<()> = async {
// Don't create any admin user
// Try to reset password for non-existent admin
std::env::remove_var("ADMIN_USERNAME"); // Use default "admin"
std::env::set_var("ADMIN_PASSWORD", "testpass123");
let reset_result = commands::reset_admin_password(&ctx.state.db).await;
// Should return an error
assert!(
reset_result.is_err(),
"Reset should fail when user doesn't exist"
);
let error_message = reset_result.unwrap_err().to_string();
assert!(
error_message.contains("not found") || error_message.contains("Admin user"),
"Error should indicate user not found, got: {}",
error_message
);
std::env::remove_var("ADMIN_PASSWORD");
Ok(())
}.await;
std::env::remove_var("ADMIN_PASSWORD");
if let Err(e) = ctx.cleanup_and_close().await {
eprintln!("Warning: Test cleanup failed: {}", e);
}
result.unwrap();
}