This repository has been archived on 2026-05-05. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
el-ui/vessels/el-auth/src/tests.rs
T
Will Anderson f4abfe6fdc feat: rename crates/ → vessels/ + add El ports per sub-vessel
Belated rename commit for foundation/el-ui — was missed in the
workspace-wide crates→vessels pass earlier today. Same structural
intent as the rename in the other repos: 'crates' is the Rust word,
'vessel' is El's, and the directory rename is the marker that this
slot holds an El buildable unit even if its current contents are
still Rust pending port.

Plus the El ports themselves — manifest.el + src/main.el per sub-
vessel (el-aop, el-auth, el-config, el-i18n, el-identity, el-layout,
el-platform, el-publish, el-secrets, el-services, el-style, el-ui-
compiler). The ui-compiler is a stub: elc only emits C right now;
generating browser-target JS/Wasm is the biggest open language gap
and gets its own project. Until then, el-ui-compiler emits a JS
module that throws elc.backend_missing so callers fail loudly.
Cross-repo path dependencies in Cargo.toml updated to vessels/.
2026-04-30 18:18:39 -05:00

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