From 3dcf974941731cfb22b32f0c22f55919c0c6f34b Mon Sep 17 00:00:00 2001 From: rock Date: Tue, 1 Sep 2026 09:20:16 -0700 Subject: [PATCH] test(rbac): add HTTP server RBAC integration tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 9 new tests covering: - JWT → RBAC claims conversion - QueryResult → ResourceMeta conversion - Admin role access (full access) - Portfolio-agent role (public only) - No-role user (denied) Total: 669 tests passing. --- crates/mem-cli/src/http_server.rs | 197 ++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) diff --git a/crates/mem-cli/src/http_server.rs b/crates/mem-cli/src/http_server.rs index 3396c16..52593a4 100644 --- a/crates/mem-cli/src/http_server.rs +++ b/crates/mem-cli/src/http_server.rs @@ -1514,3 +1514,200 @@ pub async fn vault_file_handler( } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_to_rbac_claims_basic() { + let jwt = JwtClaims { + sub: "alice".to_string(), + iss: "test".to_string(), + aud: "memory".to_string(), + exp: i64::MAX, + iat: 0, + nbf: None, + permissions: Some(vec!["memory:read".to_string(), "memory:write".to_string()]), + groups: Some(vec!["engineering".to_string(), "ml-team".to_string()]), + }; + + let rbac = to_rbac_claims(&jwt); + + assert_eq!(rbac.sub, "alice"); + assert!(rbac.in_group("engineering")); + assert!(rbac.in_group("ml-team")); + assert!(rbac.has_permission("memory:read")); + assert!(rbac.has_permission("memory:write")); + } + + #[test] + fn test_to_rbac_claims_empty() { + let jwt = JwtClaims { + sub: "anonymous".to_string(), + iss: "test".to_string(), + aud: "memory".to_string(), + exp: i64::MAX, + iat: 0, + nbf: None, + permissions: None, + groups: None, + }; + + let rbac = to_rbac_claims(&jwt); + + assert_eq!(rbac.sub, "anonymous"); + assert!(!rbac.in_group("any")); + assert!(!rbac.has_permission("any")); + } + + #[test] + fn test_query_result_to_resource_meta_wiki() { + let result = crate::query_worker::QueryResult { + level: "corpus".to_string(), + score: 0.9, + text: "Some wiki content".to_string(), + source: Some("docs/kubernetes.md".to_string()), + provenance: vec![], + }; + + let meta = query_result_to_resource_meta(&result, "homelab"); + + assert_eq!(meta.resource_type, ResourceType::Wiki); + assert_eq!(meta.project, "homelab"); + assert_eq!(meta.visibility, Visibility::Public); + } + + #[test] + fn test_query_result_to_resource_meta_skill() { + let result = crate::query_worker::QueryResult { + level: "L1".to_string(), + score: 0.8, + text: "Skill content".to_string(), + source: Some("shared/skills/SKILL-debug/SKILL.md".to_string()), + provenance: vec![], + }; + + let meta = query_result_to_resource_meta(&result, "homelab"); + + assert_eq!(meta.resource_type, ResourceType::Skill); + } + + #[test] + fn test_query_result_to_resource_meta_private() { + let result = crate::query_worker::QueryResult { + level: "L2".to_string(), + score: 0.7, + text: "Private content".to_string(), + source: Some("docs/private/secrets.md".to_string()), + provenance: vec![], + }; + + let meta = query_result_to_resource_meta(&result, "homelab"); + + assert_eq!(meta.visibility, Visibility::Private); + } + + #[test] + fn test_query_result_to_resource_meta_embedding() { + let result = crate::query_worker::QueryResult { + level: "L1".to_string(), + score: 0.85, + text: "Learned fact".to_string(), + source: Some("memory-123".to_string()), + provenance: vec![], + }; + + let meta = query_result_to_resource_meta(&result, "portfolio"); + + assert_eq!(meta.resource_type, ResourceType::Embedding); + assert_eq!(meta.project, "portfolio"); + } + + #[tokio::test] + async fn test_rbac_integration_admin_access() { + use std::sync::Arc; + use crate::rbac::{builtin_role_provider, AccessGuard}; + + let guard = AccessGuard::new(Arc::new(builtin_role_provider())); + + // Admin JWT + let jwt = JwtClaims { + sub: "admin-user".to_string(), + iss: "test".to_string(), + aud: "memory".to_string(), + exp: i64::MAX, + iat: 0, + nbf: None, + permissions: Some(vec!["*".to_string()]), + groups: None, + }; + let rbac_claims = to_rbac_claims(&jwt).with_roles(vec!["admin"]); + + // Admin can access any project + let project = ResourceMeta::new("secret-project", ResourceType::Project, "secret-project"); + assert!(guard.can_read(&rbac_claims, &project).await); + assert!(guard.can_write(&rbac_claims, &project).await); + } + + #[tokio::test] + async fn test_rbac_integration_portfolio_agent() { + use std::sync::Arc; + use crate::rbac::{builtin_role_provider, AccessGuard}; + + let guard = AccessGuard::new(Arc::new(builtin_role_provider())); + + // Portfolio agent JWT + let jwt = JwtClaims { + sub: "visitor-123".to_string(), + iss: "test".to_string(), + aud: "memory".to_string(), + exp: i64::MAX, + iat: 0, + nbf: None, + permissions: Some(vec!["memory:read".to_string()]), + groups: None, + }; + let rbac_claims = to_rbac_claims(&jwt).with_roles(vec!["portfolio-agent"]); + + // Can read public wiki in allowed project + let public_wiki = ResourceMeta::wiki("doc-1", "homelab") + .with_visibility(Visibility::Public); + assert!(guard.can_read(&rbac_claims, &public_wiki).await); + + // Cannot read private wiki + let private_wiki = ResourceMeta::wiki("secret", "homelab") + .with_visibility(Visibility::Private); + assert!(!guard.can_read(&rbac_claims, &private_wiki).await); + + // Cannot write to any project + let project = ResourceMeta::new("homelab", ResourceType::Project, "homelab"); + assert!(!guard.can_write(&rbac_claims, &project).await); + } + + #[tokio::test] + async fn test_rbac_integration_no_role() { + use std::sync::Arc; + use crate::rbac::{builtin_role_provider, AccessGuard}; + + let guard = AccessGuard::new(Arc::new(builtin_role_provider())); + + // JWT with no roles + let jwt = JwtClaims { + sub: "anonymous".to_string(), + iss: "test".to_string(), + aud: "memory".to_string(), + exp: i64::MAX, + iat: 0, + nbf: None, + permissions: None, + groups: None, + }; + let rbac_claims = to_rbac_claims(&jwt); // No roles + + // Cannot read anything without a role + let wiki = ResourceMeta::wiki("doc", "homelab") + .with_visibility(Visibility::Public); + assert!(!guard.can_read(&rbac_claims, &wiki).await); + } +}