253 lines
10 KiB
Rust
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, ®).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(), ®).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(), ®).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");
|
|
}
|
|
}
|