Files
el/ui/crates/el-auth/src/tests.rs
T

253 lines
10 KiB
Rust

//! Tests for el-auth.
#[cfg(test)]
mod tests {
use std::sync::Arc;
use crate::{
context::{AuthContext, AuthUser},
jwt::{JwtClaims, JwtProvider},
middleware::AuthMiddleware,
roles::{Permission, Role, RoleRegistry},
session::SessionProvider,
AuthError, AuthProvider,
};
fn test_user() -> AuthUser {
AuthUser::new("user-1", "alice@example.com", "Alice")
}
fn test_provider() -> JwtProvider {
JwtProvider::new(b"super-secret-key-for-testing-only".to_vec())
.with_ttl(3600)
}
fn registry() -> RoleRegistry {
let mut r = RoleRegistry::new();
r.register(
Role::new("admin")
.with_permissions(vec!["read", "write", "delete"]),
);
r.register(
Role::new("user")
.with_permissions(vec!["read"]),
);
r
}
// ── Test 1: JWT round-trip — sign and verify ──────────────────────────────
#[test]
fn test_jwt_sign_and_verify() {
let provider = test_provider();
let user = test_user();
let claims = JwtClaims::new(&user, vec!["user".into()], 3600);
let token = provider.encode(&claims);
let decoded = provider.decode(&token).unwrap();
assert_eq!(decoded.sub, "user-1");
assert_eq!(decoded.email, "alice@example.com");
assert_eq!(decoded.roles, vec!["user"]);
}
// ── Test 2: JWT invalid signature is rejected ─────────────────────────────
#[test]
fn test_jwt_invalid_signature() {
let provider = test_provider();
let other_provider = JwtProvider::new(b"different-secret".to_vec());
let user = test_user();
let claims = JwtClaims::new(&user, vec![], 3600);
let token = other_provider.encode(&claims);
let result = provider.decode(&token);
assert!(matches!(result, Err(AuthError::TokenInvalid(_))));
}
// ── Test 3: JWT expired token is rejected ────────────────────────────────
#[test]
fn test_jwt_expired_token() {
let provider = test_provider();
let user = test_user();
// TTL of 0 — expires immediately
let claims = JwtClaims::new(&user, vec![], 0);
let token = provider.encode(&claims);
// Wait a moment (in tests, just check the claims are_expired)
assert!(claims.is_expired() || {
std::thread::sleep(std::time::Duration::from_millis(1100));
true
});
let result = provider.decode(&token);
assert!(matches!(result, Err(AuthError::TokenExpired) | Err(AuthError::TokenInvalid(_))));
}
// ── Test 4: JWT verify returns correct AuthContext ───────────────────────
#[test]
fn test_jwt_verify_returns_auth_context() {
let provider = test_provider();
let user = test_user();
let reg = registry();
let token = provider.issue(user, &reg).unwrap();
let ctx = provider.verify(&token).unwrap();
assert!(ctx.is_authenticated());
assert_eq!(ctx.user_id().unwrap(), "user-1");
}
// ── Test 5: AuthContext::has_role works ──────────────────────────────────
#[test]
fn test_auth_context_has_role() {
let ctx = AuthContext::authenticated(
test_user(),
vec!["admin".into(), "user".into()],
"tok",
);
assert!(ctx.has_role("admin"));
assert!(ctx.has_role("user"));
assert!(!ctx.has_role("superadmin"));
}
// ── Test 6: AuthContext::has_permission works ─────────────────────────────
#[test]
fn test_auth_context_has_permission() {
let ctx = AuthContext::authenticated(test_user(), vec!["admin".into()], "tok")
.with_permissions(vec!["read".into(), "write".into(), "delete".into()]);
assert!(ctx.has_permission("read"));
assert!(ctx.has_permission("delete"));
assert!(!ctx.has_permission("sudo"));
}
// ── Test 7: AuthContext::anonymous is not authenticated ───────────────────
#[test]
fn test_anonymous_context() {
let ctx = AuthContext::anonymous();
assert!(!ctx.is_authenticated());
assert!(ctx.user_id().is_none());
}
// ── Test 8: Role has_permission ──────────────────────────────────────────
#[test]
fn test_role_has_permission() {
let role = Role::new("editor")
.with_permissions(vec!["read", "write"]);
assert!(role.has_permission("read"));
assert!(role.has_permission("write"));
assert!(!role.has_permission("delete"));
}
// ── Test 9: RoleRegistry::has_permission checks across roles ─────────────
#[test]
fn test_role_registry_permission_check() {
let reg = registry();
let roles = vec!["user".to_string()];
assert!(reg.has_permission(&roles, "read"));
assert!(!reg.has_permission(&roles, "delete"));
let admin_roles = vec!["admin".to_string()];
assert!(reg.has_permission(&admin_roles, "delete"));
}
// ── Test 10: SessionProvider issue and verify ─────────────────────────────
#[test]
fn test_session_issue_and_verify() {
let provider = SessionProvider::new();
let reg = registry();
let session_id = provider.issue(test_user(), &reg).unwrap();
let ctx = provider.verify(&session_id).unwrap();
assert!(ctx.is_authenticated());
assert_eq!(ctx.user_id().unwrap(), "user-1");
}
// ── Test 11: SessionProvider revoke removes session ───────────────────────
#[test]
fn test_session_revoke() {
let provider = SessionProvider::new();
let reg = registry();
let session_id = provider.issue(test_user(), &reg).unwrap();
provider.revoke(&session_id).unwrap();
let result = provider.verify(&session_id);
assert!(matches!(result, Err(AuthError::SessionNotFound)));
}
// ── Test 12: SessionProvider unknown session returns error ────────────────
#[test]
fn test_session_unknown() {
let provider = SessionProvider::new();
let result = provider.verify("nonexistent-session-id");
assert!(matches!(result, Err(AuthError::SessionNotFound)));
}
// ── Test 13: AuthMiddleware extracts Bearer token ─────────────────────────
#[test]
fn test_middleware_extracts_bearer_token() {
let provider = Arc::new(test_provider());
let user = test_user();
let claims = JwtClaims::new(&user, vec!["user".into()], 3600);
let token = provider.encode(&claims);
let middleware = AuthMiddleware::new(provider);
let header = format!("Bearer {}", token);
let ctx = middleware.authenticate_from_header(Some(&header)).unwrap();
assert!(ctx.is_authenticated());
}
// ── Test 14: AuthMiddleware with no header returns anonymous ──────────────
#[test]
fn test_middleware_no_header_anonymous() {
let provider = Arc::new(test_provider());
let middleware = AuthMiddleware::new(provider);
let ctx = middleware.authenticate_from_header(None).unwrap();
assert!(!ctx.is_authenticated());
}
// ── Test 15: Permission Display ───────────────────────────────────────────
#[test]
fn test_permission_display() {
let perm = Permission::new("write");
assert_eq!(perm.to_string(), "write");
assert_eq!(perm.as_str(), "write");
}
// ── Test 16: JwtClaims::to_json and from_json round-trip ─────────────────
#[test]
fn test_jwt_claims_json_round_trip() {
let user = test_user();
let claims = JwtClaims::new(&user, vec!["admin".into(), "user".into()], 3600);
let json = claims.to_json();
let decoded = JwtClaims::from_json(&json).unwrap();
assert_eq!(decoded.sub, "user-1");
assert_eq!(decoded.email, "alice@example.com");
assert_eq!(decoded.roles, vec!["admin", "user"]);
}
// ── Test 17: JWT with multiple roles ─────────────────────────────────────
#[test]
fn test_jwt_multiple_roles() {
let provider = test_provider();
let user = test_user();
let claims = JwtClaims::new(&user, vec!["admin".into(), "user".into()], 3600);
let token = provider.encode(&claims);
let decoded = provider.decode(&token).unwrap();
assert_eq!(decoded.roles.len(), 2);
assert!(decoded.roles.contains(&"admin".to_string()));
}
// ── Test 18: RoleRegistry::role_names lists all roles ────────────────────
#[test]
fn test_role_registry_names() {
let reg = registry();
let mut names = reg.role_names();
names.sort();
assert_eq!(names, vec!["admin", "user"]);
}
// ── Test 19: SessionProvider TTL configuration ────────────────────────────
#[test]
fn test_session_ttl_config() {
let provider = SessionProvider::new().with_ttl(7200);
assert_eq!(provider.ttl.as_secs(), 7200);
}
// ── Test 20: AuthMiddleware provider_name returns correct name ────────────
#[test]
fn test_middleware_provider_name() {
let jwt_provider = Arc::new(test_provider());
let middleware = AuthMiddleware::new(jwt_provider);
assert_eq!(middleware.provider_name(), "jwt");
}
}