//! 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"); } }