use std::path::{Path, PathBuf};
use tempfile::TempDir;
use vti_common::config::StoreConfig;
use vtc_service::acl::{VtcAclEntry, VtcRole, get_acl_entry};
use vtc_service::acl_cli::{AclAddArgs, run_acl_add, run_acl_list, run_acl_remove};
use vtc_service::store::Store;
const DID: &str = "did:key:z6MkAclCliTestAdmin";
fn fixture() -> (TempDir, PathBuf, PathBuf) {
let dir = tempfile::tempdir().expect("tempdir");
let data_dir = dir.path().join("data");
let cfg_path = dir.path().join("config.toml");
std::fs::write(
&cfg_path,
format!("[store]\ndata_dir = \"{}\"\n", data_dir.display()),
)
.expect("write config");
(dir, cfg_path, data_dir)
}
async fn read_entry(data_dir: &Path, did: &str) -> Option<VtcAclEntry> {
let store = Store::open(&StoreConfig {
data_dir: data_dir.to_path_buf(),
})
.expect("open store");
let ks = store.keyspace("acl").expect("acl ks");
get_acl_entry(&ks, did).await.expect("get_acl_entry")
}
#[tokio::test]
async fn add_list_remove_round_trip() {
let (_dir, cfg, data_dir) = fixture();
run_acl_add(AclAddArgs {
config_path: Some(cfg.clone()),
did: DID.into(),
role: "admin".into(),
label: Some("ops".into()),
contexts: vec!["ctx-1".into(), "ctx-2".into()],
expires: Some(3600),
})
.await
.expect("add");
let e = read_entry(&data_dir, DID).await.expect("entry stored");
assert_eq!(e.role, VtcRole::Admin);
assert_eq!(e.label.as_deref(), Some("ops"));
assert_eq!(e.allowed_contexts, vec!["ctx-1", "ctx-2"]);
assert!(e.expires_at.is_some(), "expiry should be set");
run_acl_list(Some(cfg.clone())).await.expect("list");
run_acl_remove(Some(cfg.clone()), DID.into())
.await
.expect("remove");
assert!(read_entry(&data_dir, DID).await.is_none(), "entry removed");
}
#[tokio::test]
async fn add_is_upsert_and_preserves_created_at() {
let (_dir, cfg, data_dir) = fixture();
run_acl_add(AclAddArgs {
config_path: Some(cfg.clone()),
did: DID.into(),
role: "member".into(),
label: None,
contexts: vec![],
expires: None,
})
.await
.expect("add member");
let first = read_entry(&data_dir, DID).await.expect("first");
assert_eq!(first.role, VtcRole::Member);
run_acl_add(AclAddArgs {
config_path: Some(cfg.clone()),
did: DID.into(),
role: "moderator".into(),
label: Some("mod".into()),
contexts: vec![],
expires: None,
})
.await
.expect("upsert moderator");
let second = read_entry(&data_dir, DID).await.expect("second");
assert_eq!(second.role, VtcRole::Moderator);
assert_eq!(second.label.as_deref(), Some("mod"));
assert_eq!(
second.created_at, first.created_at,
"created_at preserved across update"
);
}
#[tokio::test]
async fn add_rejects_unknown_role() {
let (_dir, cfg, data_dir) = fixture();
let result = run_acl_add(AclAddArgs {
config_path: Some(cfg),
did: DID.into(),
role: "wizard".into(),
label: None,
contexts: vec![],
expires: None,
})
.await;
assert!(result.is_err(), "unknown role must be rejected");
assert!(read_entry(&data_dir, DID).await.is_none());
}
#[tokio::test]
async fn remove_missing_did_is_ok() {
let (_dir, cfg, _data_dir) = fixture();
run_acl_remove(Some(cfg), "did:key:zNeverAdded".into())
.await
.expect("remove missing is ok");
}