use std::sync::Arc;
use astrid_capabilities::CapabilityCheck;
use astrid_core::principal::PrincipalId;
use astrid_core::{GroupConfig, PrincipalProfile};
use astrid_events::kernel_api::AdminRequestKind;
use super::{
AuthorityScope, admin_request_method, admin_response_topic, admin_target_principal,
required_capability_for_admin_request, resolve_admin_scope,
};
fn pid(name: &str) -> PrincipalId {
PrincipalId::new(name).unwrap()
}
fn all_admin_variants() -> Vec<AdminRequestKind> {
vec![
AdminRequestKind::AgentCreate {
name: "n".into(),
groups: Vec::new(),
grants: Vec::new(),
},
AdminRequestKind::AgentDelete {
principal: pid("a"),
},
AdminRequestKind::AgentEnable {
principal: pid("a"),
},
AdminRequestKind::AgentDisable {
principal: pid("a"),
},
AdminRequestKind::AgentList,
AdminRequestKind::QuotaSet {
principal: pid("a"),
quotas: astrid_core::profile::Quotas::default(),
},
AdminRequestKind::QuotaGet {
principal: pid("a"),
},
AdminRequestKind::GroupCreate {
name: "ops".into(),
capabilities: vec!["capsule:install".into()],
description: None,
unsafe_admin: false,
},
AdminRequestKind::GroupDelete { name: "ops".into() },
AdminRequestKind::GroupModify {
name: "ops".into(),
capabilities: None,
description: None,
unsafe_admin: None,
},
AdminRequestKind::GroupList,
AdminRequestKind::CapsGrant {
principal: pid("a"),
capabilities: vec!["self:capsule:install".into()],
unsafe_admin: false,
},
AdminRequestKind::CapsRevoke {
principal: pid("a"),
capabilities: vec!["self:*".into()],
},
]
}
fn agent_profile() -> PrincipalProfile {
let mut p = PrincipalProfile::default();
p.groups = vec!["agent".to_string()];
p
}
fn admin_profile() -> PrincipalProfile {
let mut p = PrincipalProfile::default();
p.groups = vec!["admin".to_string()];
p
}
fn authorize_with(
profile: &PrincipalProfile,
groups: &GroupConfig,
caller: &PrincipalId,
cap: &str,
) -> bool {
CapabilityCheck::new(profile, groups, caller.clone())
.require(cap)
.is_ok()
}
#[test]
fn every_variant_has_non_empty_mapping_in_both_scopes() {
for req in all_admin_variants() {
for scope in [AuthorityScope::Self_, AuthorityScope::Global] {
let cap = required_capability_for_admin_request(&req, scope);
assert!(
!cap.is_empty(),
"required_capability_for_admin_request returned empty for \
{req:?} at {scope:?}"
);
}
}
}
#[test]
fn quota_self_vs_global_mapping_distinguishes_principal() {
let req = AdminRequestKind::QuotaSet {
principal: pid("alice"),
quotas: astrid_core::profile::Quotas::default(),
};
assert_eq!(
required_capability_for_admin_request(&req, AuthorityScope::Self_),
"self:quota:set"
);
assert_eq!(
required_capability_for_admin_request(&req, AuthorityScope::Global),
"quota:set"
);
}
#[test]
fn agent_list_maps_self_to_self_prefix() {
assert_eq!(
required_capability_for_admin_request(&AdminRequestKind::AgentList, AuthorityScope::Self_),
"self:agent:list"
);
assert_eq!(
required_capability_for_admin_request(&AdminRequestKind::AgentList, AuthorityScope::Global),
"agent:list"
);
}
#[test]
fn every_variant_has_a_method_label() {
for req in all_admin_variants() {
let m = admin_request_method(&req);
assert!(
m.starts_with("admin."),
"method must start with admin.: {m}"
);
}
}
#[test]
fn resolve_admin_scope_self_when_target_is_caller() {
let caller = pid("alice");
let req = AdminRequestKind::QuotaSet {
principal: caller.clone(),
quotas: astrid_core::profile::Quotas::default(),
};
assert_eq!(resolve_admin_scope(&req, &caller), AuthorityScope::Self_);
}
#[test]
fn resolve_admin_scope_global_when_target_differs() {
let caller = pid("alice");
let req = AdminRequestKind::QuotaSet {
principal: pid("bob"),
quotas: astrid_core::profile::Quotas::default(),
};
assert_eq!(resolve_admin_scope(&req, &caller), AuthorityScope::Global);
}
#[test]
fn admin_target_principal_some_for_cross_tenant_variants() {
assert!(
admin_target_principal(&AdminRequestKind::QuotaSet {
principal: pid("a"),
quotas: astrid_core::profile::Quotas::default(),
})
.is_some()
);
assert!(
admin_target_principal(&AdminRequestKind::CapsGrant {
principal: pid("a"),
capabilities: vec!["self:capsule:install".into()],
unsafe_admin: false,
})
.is_some()
);
}
#[test]
fn admin_target_principal_none_for_self_or_collection_variants() {
assert!(admin_target_principal(&AdminRequestKind::AgentList).is_none());
assert!(admin_target_principal(&AdminRequestKind::GroupList).is_none());
assert!(
admin_target_principal(&AdminRequestKind::AgentCreate {
name: "n".into(),
groups: Vec::new(),
grants: Vec::new(),
})
.is_none()
);
}
#[test]
fn admin_response_topic_mirrors_request_prefix() {
assert_eq!(
admin_response_topic("astrid.v1.admin.agent.create"),
"astrid.v1.admin.response.agent.create"
);
assert_eq!(
admin_response_topic("astrid.v1.admin.quota.set"),
"astrid.v1.admin.response.quota.set"
);
assert_eq!(admin_response_topic("other.topic"), "other.topic");
}
#[test]
fn agent_denied_cross_tenant_every_admin_topic() {
let groups = GroupConfig::builtin_only();
let profile = agent_profile();
let caller = pid("agent_user");
for req in all_admin_variants() {
let method = admin_request_method(&req);
let scope = resolve_admin_scope(&req, &caller);
let cap = required_capability_for_admin_request(&req, scope);
let self_allowed = matches!(scope, AuthorityScope::Self_);
let allowed = authorize_with(&profile, &groups, &caller, cap);
if self_allowed {
assert!(
allowed,
"agent should be allowed self-scoped {method} ({cap})",
);
} else {
assert!(
!allowed,
"agent should be denied cross-tenant {method} ({cap})",
);
}
}
}
#[test]
fn admin_allowed_every_admin_topic() {
let groups = GroupConfig::builtin_only();
let profile = admin_profile();
let caller = pid("admin_user");
for req in all_admin_variants() {
let method = admin_request_method(&req);
let scope = resolve_admin_scope(&req, &caller);
let cap = required_capability_for_admin_request(&req, scope);
assert!(
authorize_with(&profile, &groups, &caller, cap),
"admin group should be allowed {method} ({cap})",
);
}
}
#[test]
fn agent_holds_self_admin_caps_added_in_layer_6() {
let groups = GroupConfig::builtin_only();
let profile = agent_profile();
let caller = pid("agent_user");
let agent_group = groups.get("agent").unwrap();
assert!(
agent_group
.capabilities
.iter()
.any(|c| c == "self:quota:get")
);
assert!(
agent_group
.capabilities
.iter()
.any(|c| c == "self:agent:list")
);
assert!(authorize_with(&profile, &groups, &caller, "self:quota:get"));
assert!(authorize_with(
&profile,
&groups,
&caller,
"self:agent:list"
));
}
#[test]
fn arcswap_groups_swap_observed_by_next_check() {
use arc_swap::ArcSwap;
use astrid_core::groups::Group;
let swap: Arc<ArcSwap<GroupConfig>> =
Arc::new(ArcSwap::from_pointee(GroupConfig::builtin_only()));
let profile = {
let mut p = PrincipalProfile::default();
p.groups = vec!["ops".to_string()];
p
};
let caller = pid("ops_user");
let before = swap.load_full();
assert!(!authorize_with(
&profile,
before.as_ref(),
&caller,
"capsule:install"
));
let next = GroupConfig::builtin_only()
.insert_custom_group(
"ops".to_string(),
Group {
capabilities: vec!["capsule:install".into()],
description: None,
unsafe_admin: false,
},
)
.unwrap();
swap.store(Arc::new(next));
let after = swap.load_full();
assert!(authorize_with(
&profile,
after.as_ref(),
&caller,
"capsule:install"
));
assert!(!authorize_with(
&profile,
before.as_ref(),
&caller,
"capsule:install"
));
}
#[test]
fn profile_cache_invalidation_reflects_on_disk_mutation() {
use astrid_capsule::profile_cache::PrincipalProfileCache;
use astrid_core::dirs::AstridHome;
let dir = tempfile::tempdir().unwrap();
let home = AstridHome::from_path(dir.path());
let cache = PrincipalProfileCache::with_home(home.clone());
let principal = pid("alice");
let first = cache.resolve(&principal).unwrap();
assert!(first.enabled);
assert!(first.grants.is_empty());
let mut updated = PrincipalProfile::default();
updated.grants = vec!["self:capsule:install".into()];
let path = PrincipalProfile::path_for(&home, &principal);
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
updated.save_to_path(&path).unwrap();
let stale = cache.resolve(&principal).unwrap();
assert!(stale.grants.is_empty());
cache.invalidate(&principal);
let fresh = cache.resolve(&principal).unwrap();
assert_eq!(fresh.grants, vec!["self:capsule:install".to_string()]);
}
#[test]
fn caps_grant_preserves_revoke_precedence() {
let mut profile = PrincipalProfile::default();
profile.groups = vec!["admin".to_string()];
profile.revokes = vec!["self:*".to_string()];
profile.grants.push("self:capsule:install".to_string());
let groups = GroupConfig::builtin_only();
let caller = pid("alice");
let check = CapabilityCheck::new(&profile, &groups, caller);
let err = check.require("self:capsule:install").unwrap_err();
assert!(
matches!(
err,
astrid_capabilities::PermissionError::RevokedCapability { .. }
),
"grant must not clear matching revoke: {err:?}"
);
}
#[test]
fn caps_revoke_of_unheld_capability_is_not_an_error_shape() {
let mut profile = PrincipalProfile::default();
profile.groups = vec!["restricted".to_string()];
profile.revokes.push("capsule:install".to_string()); profile.grants.push("capsule:install".to_string());
let groups = GroupConfig::builtin_only();
let caller = pid("alice");
let check = CapabilityCheck::new(&profile, &groups, caller);
assert!(check.require("capsule:install").is_err(), "revoke wins");
}