use dbnexus::{AuthCredentials, AuthError, AuthenticationManager, JwtManager, PasswordHasher, TokenType, User};
const TEST_SECRET: &[u8] = b"test_secret_key_for_integration_tests_2026";
const ALT_SECRET: &[u8] = b"different_secret_key_for_signature_mismatch";
fn make_user(username: &str, password: &str, role: &str) -> User {
let hash = PasswordHasher::new().hash(password).expect("hash should succeed");
User {
id: format!("uid_{}", username),
username: username.to_string(),
password_hash: hash,
role: role.to_string(),
email: Some(format!("{}@example.com", username)),
created_at: Some("2026-07-03T00:00:00Z".to_string()),
}
}
async fn make_manager_with_user() -> AuthenticationManager {
let mgr = AuthenticationManager::new(TEST_SECRET);
mgr.add_user(make_user("alice", "Password123", "admin"))
.await
.expect("add_user should succeed");
mgr
}
#[tokio::test]
async fn test_jwt_issue_and_verify() {
let mgr = JwtManager::new(TEST_SECRET);
let token = mgr
.generate_token("user_1", "alice", "admin", TokenType::Access)
.expect("generate_token should succeed");
assert!(!token.is_empty(), "token must be non-empty");
let claims = mgr.verify_token(&token).expect("verify_token should succeed");
assert_eq!(claims.sub, "user_1");
assert_eq!(claims.username, "alice");
assert_eq!(claims.role, "admin");
assert_eq!(claims.token_type, TokenType::Access);
assert!(claims.exp > claims.iat, "exp must be after iat");
}
#[tokio::test]
async fn test_jwt_expired() {
let mgr = JwtManager::with_expiration(TEST_SECRET, 1, 1);
let token = mgr
.generate_token("user_2", "bob", "user", TokenType::Access)
.expect("generate_token should succeed");
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
let result = mgr.verify_token(&token);
assert!(result.is_err(), "expired token should fail verification");
assert!(
matches!(result, Err(AuthError::TokenExpired)),
"expected TokenExpired, got {:?}",
result
);
}
#[tokio::test]
async fn test_jwt_tampered() {
let mgr = JwtManager::new(TEST_SECRET);
let token = mgr
.generate_token("user_3", "carol", "user", TokenType::Access)
.expect("generate_token should succeed");
let tampered = {
let parts: Vec<&str> = token.split('.').collect();
assert_eq!(parts.len(), 3, "JWT should have 3 parts");
let mut payload = parts[1].to_string();
let last_char = payload.pop().unwrap_or('A');
payload.push(if last_char == 'A' { 'B' } else { 'A' });
format!("{}.{}.{}", parts[0], payload, parts[2])
};
let result = mgr.verify_token(&tampered);
assert!(result.is_err(), "tampered token should fail");
assert!(
matches!(result, Err(AuthError::InvalidToken)),
"expected InvalidToken, got {:?}",
result
);
}
#[tokio::test]
async fn test_jwt_invalid_signature() {
let signer = JwtManager::new(TEST_SECRET);
let verifier = JwtManager::new(ALT_SECRET);
let token = signer
.generate_token("user_4", "dave", "user", TokenType::Access)
.expect("generate_token should succeed");
let result = verifier.verify_token(&token);
assert!(result.is_err(), "token signed with different key should fail");
assert!(
matches!(result, Err(AuthError::InvalidToken)),
"expected InvalidToken for signature mismatch, got {:?}",
result
);
}
#[test]
fn test_password_hash_and_verify() {
let hasher = PasswordHasher::new();
let password = "MySecurePassword123";
let hash = hasher.hash(password).expect("hash should succeed");
assert!(hash.starts_with("$2b$"), "bcrypt hash should start with $2b$");
assert_ne!(hash, password, "hash must differ from plaintext");
hasher
.verify(password, &hash)
.expect("verify with correct password should succeed");
}
#[test]
fn test_password_wrong_password() {
let hasher = PasswordHasher::new();
let hash = hasher.hash("CorrectPassword123").expect("hash should succeed");
let result = hasher.verify("WrongPassword456", &hash);
assert!(result.is_err(), "wrong password should fail");
assert!(
matches!(result, Err(AuthError::InvalidCredentials)),
"expected InvalidCredentials, got {:?}",
result
);
}
#[test]
fn test_password_empty() {
let hasher = PasswordHasher::new();
let result = hasher.validate_strength("");
assert!(result.is_err(), "empty password should fail strength check");
}
#[test]
fn test_password_update() {
let hasher = PasswordHasher::new();
let old_password = "OldPassword123";
let new_password = "NewPassword456";
let old_hash = hasher.hash(old_password).expect("hash old should succeed");
let new_hash = hasher.hash(new_password).expect("hash new should succeed");
hasher
.verify(new_password, &new_hash)
.expect("new password should verify");
let result = hasher.verify(old_password, &new_hash);
assert!(result.is_err(), "old password should not verify against new hash");
let result = hasher.verify(new_password, &old_hash);
assert!(result.is_err(), "new password should not verify against old hash");
}
#[tokio::test]
async fn test_authenticate_success() {
let mgr = make_manager_with_user().await;
let token = mgr
.authenticate(AuthCredentials {
username: "alice".to_string(),
password: "Password123".to_string(),
})
.await
.expect("authenticate should succeed");
assert!(!token.is_empty(), "token must be non-empty");
let claims = mgr.verify_token(&token).expect("verify_token should succeed");
assert_eq!(claims.username, "alice");
assert_eq!(claims.role, "admin");
}
#[tokio::test]
async fn test_authenticate_wrong_password() {
let mgr = make_manager_with_user().await;
let result = mgr
.authenticate(AuthCredentials {
username: "alice".to_string(),
password: "WrongPassword".to_string(),
})
.await;
assert!(result.is_err(), "wrong password should fail");
assert!(
matches!(result, Err(AuthError::InvalidCredentials)),
"expected InvalidCredentials, got {:?}",
result
);
}
#[tokio::test]
async fn test_authenticate_unknown_user() {
let mgr = make_manager_with_user().await;
let result = mgr
.authenticate(AuthCredentials {
username: "nonexistent_user".to_string(),
password: "Password123".to_string(),
})
.await;
assert!(result.is_err(), "unknown user should fail");
assert!(
matches!(result, Err(AuthError::InvalidCredentials)),
"expected InvalidCredentials for unknown user, got {:?}",
result
);
}