use std::sync::Arc;
use astrid_core::dirs::AstridHome;
use astrid_core::groups::{BUILTIN_AGENT, BUILTIN_RESTRICTED};
use astrid_core::principal::PrincipalId;
use astrid_core::profile::PrincipalProfile;
use astrid_events::kernel_api::{AdminRequestKind, AdminResponseBody};
use tempfile::TempDir;
use super::handlers;
use crate::Kernel;
async fn fixture() -> (TempDir, Arc<Kernel>) {
let dir = tempfile::tempdir().expect("tempdir");
let home = AstridHome::from_path(dir.path());
let kernel = crate::test_kernel_with_home(home).await;
(dir, kernel)
}
fn pid(name: &str) -> PrincipalId {
PrincipalId::new(name).unwrap()
}
fn assert_success(res: &AdminResponseBody) {
match res {
AdminResponseBody::Success(_)
| AdminResponseBody::Quotas(_)
| AdminResponseBody::AgentList(_)
| AdminResponseBody::GroupList(_) => {},
AdminResponseBody::Error(msg) => panic!("expected success, got Error: {msg}"),
}
}
fn assert_error_contains(res: &AdminResponseBody, needle: &str) {
match res {
AdminResponseBody::Error(msg) => {
assert!(
msg.contains(needle),
"expected error to contain {needle:?}, got: {msg}"
);
},
other => panic!("expected Error, got: {other:?}"),
}
}
#[tokio::test(flavor = "multi_thread")]
async fn agent_modify_adds_and_removes_groups_idempotently() {
let (_dir, kernel) = fixture().await;
handlers::dispatch(
&kernel,
AdminRequestKind::AgentCreate {
name: "mia".into(),
groups: vec![BUILTIN_AGENT.into()],
grants: Vec::new(),
},
)
.await;
let res = handlers::dispatch(
&kernel,
AdminRequestKind::AgentModify {
principal: pid("mia"),
add_groups: vec![BUILTIN_RESTRICTED.into()],
remove_groups: Vec::new(),
},
)
.await;
assert_success(&res);
let path = PrincipalProfile::path_for(&kernel.astrid_home, &pid("mia"));
let profile = PrincipalProfile::load_from_path(&path).unwrap();
assert_eq!(
profile.groups,
vec![BUILTIN_AGENT.to_string(), BUILTIN_RESTRICTED.to_string()]
);
let res = handlers::dispatch(
&kernel,
AdminRequestKind::AgentModify {
principal: pid("mia"),
add_groups: vec![BUILTIN_RESTRICTED.into()],
remove_groups: Vec::new(),
},
)
.await;
assert_success(&res);
let res = handlers::dispatch(
&kernel,
AdminRequestKind::AgentModify {
principal: pid("mia"),
add_groups: Vec::new(),
remove_groups: vec![BUILTIN_AGENT.into()],
},
)
.await;
assert_success(&res);
let profile = PrincipalProfile::load_from_path(&path).unwrap();
assert_eq!(profile.groups, vec![BUILTIN_RESTRICTED.to_string()]);
}
#[tokio::test(flavor = "multi_thread")]
async fn agent_modify_rejects_empty_changes() {
let (_dir, kernel) = fixture().await;
handlers::dispatch(
&kernel,
AdminRequestKind::AgentCreate {
name: "nina".into(),
groups: Vec::new(),
grants: Vec::new(),
},
)
.await;
let res = handlers::dispatch(
&kernel,
AdminRequestKind::AgentModify {
principal: pid("nina"),
add_groups: Vec::new(),
remove_groups: Vec::new(),
},
)
.await;
assert_error_contains(&res, "must be non-empty");
}
#[tokio::test(flavor = "multi_thread")]
async fn agent_modify_rejects_unknown_principal() {
let (_dir, kernel) = fixture().await;
let res = handlers::dispatch(
&kernel,
AdminRequestKind::AgentModify {
principal: pid("ghost"),
add_groups: vec![BUILTIN_RESTRICTED.into()],
remove_groups: Vec::new(),
},
)
.await;
assert_error_contains(&res, "ghost");
}