/// Fluent builders for test data construction (DRY principle) /// Builder for OidcClaims (OIDC token claims from Authentik) pub struct OidcClaimsBuilder { sub: String, groups: Vec, roles: Vec, permissions: Vec, } #[derive(Debug, Clone, PartialEq)] pub struct OidcClaims { pub sub: String, pub groups: Vec, pub roles: Vec, pub permissions: Vec, } impl OidcClaimsBuilder { pub fn new(sub: &str) -> Self { Self { sub: sub.to_string(), groups: vec![], roles: vec!["viewer".to_string()], permissions: vec![], } } pub fn group(mut self, group: &str) -> Self { self.groups.push(group.to_string()); self } pub fn groups(mut self, groups: Vec<&str>) -> Self { self.groups = groups.iter().map(|s| s.to_string()).collect(); self } pub fn role(mut self, role: &str) -> Self { self.roles.push(role.to_string()); self } pub fn permission(mut self, perm: &str) -> Self { self.permissions.push(perm.to_string()); self } pub fn permissions(mut self, perms: Vec<&str>) -> Self { self.permissions = perms.iter().map(|s| s.to_string()).collect(); self } pub fn build(self) -> OidcClaims { OidcClaims { sub: self.sub, groups: self.groups, roles: self.roles, permissions: self.permissions, } } } /// Builder for AccessPolicy (RBAC policy from Vault) pub struct AccessPolicyBuilder { access_level: String, owner_group: String, allowed_groups: Vec, required_role: Option, required_permission: Option, } #[derive(Debug, Clone, PartialEq)] pub struct AccessPolicy { pub access_level: String, pub owner_group: String, pub allowed_groups: Vec, pub required_role: Option, pub required_permission: Option, } impl AccessPolicyBuilder { pub fn public() -> Self { Self { access_level: "public".to_string(), owner_group: Default::default(), allowed_groups: Default::default(), required_role: None, required_permission: None, } } pub fn private(mut self, owner: &str) -> Self { self.access_level = "private".to_string(); self.owner_group = owner.to_string(); self } pub fn group(mut self, groups: Vec<&str>) -> Self { self.access_level = "group".to_string(); self.allowed_groups = groups.iter().map(|s| s.to_string()).collect(); self } pub fn require_role(mut self, role: &str) -> Self { self.required_role = Some(role.to_string()); self } pub fn require_permission(mut self, perm: &str) -> Self { self.required_permission = Some(perm.to_string()); self } pub fn build(self) -> AccessPolicy { AccessPolicy { access_level: self.access_level, owner_group: self.owner_group, allowed_groups: self.allowed_groups, required_role: self.required_role, required_permission: self.required_permission, } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_oidc_claims_builder_basic() { let claims = OidcClaimsBuilder::new("charlie") .group("platform-team") .permission("memory:read") .build(); assert_eq!(claims.sub, "charlie"); assert_eq!(claims.groups, vec!["platform-team"]); assert!(claims.permissions.contains(&"memory:read".to_string())); } #[test] fn test_oidc_claims_builder_multiple_groups() { let claims = OidcClaimsBuilder::new("alice") .groups(vec!["platform-team", "devops-team"]) .role("editor") .build(); assert_eq!(claims.groups.len(), 2); assert!(claims.groups.contains(&"platform-team".to_string())); assert!(claims.groups.contains(&"devops-team".to_string())); assert!(claims.roles.contains(&"editor".to_string())); } #[test] fn test_access_policy_public() { let policy = AccessPolicyBuilder::public().build(); assert_eq!(policy.access_level, "public"); assert!(policy.owner_group.is_empty()); assert!(policy.allowed_groups.is_empty()); } #[test] fn test_access_policy_private() { let policy = AccessPolicyBuilder::public() .private("ml-team") .build(); assert_eq!(policy.access_level, "private"); assert_eq!(policy.owner_group, "ml-team"); } #[test] fn test_access_policy_group() { let policy = AccessPolicyBuilder::public() .group(vec!["platform-team", "devops-team"]) .require_role("viewer") .build(); assert_eq!(policy.access_level, "group"); assert_eq!(policy.allowed_groups.len(), 2); assert_eq!(policy.required_role, Some("viewer".to_string())); } }