use super::*;
use trusty_agents_common::agents::manifest::{AgentManifest, ManifestEntry, Origin, checksum};
use trusty_agents_common::agents::tier_audit::TierResidentClass;
fn hermetic_paths(base: &Path) -> FrameworkPaths {
let mut paths = FrameworkPaths::under(base);
paths.trusty_mpm_root = None;
paths
}
fn doc(name: &str) -> String {
format!("---\nname: {name}\nrole: engineer\n---\n\nBody.\n")
}
fn place_agent(base: &Path, file_name: &str, body: &str) {
let dir = base.join(".claude").join("agents");
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join(file_name), body).unwrap();
}
fn track(base: &Path, file_name: &str, origin: Origin) {
let dir = base.join(".claude").join("agents");
let mut manifest = AgentManifest::default();
manifest.managed.insert(
file_name.to_owned(),
ManifestEntry {
source_chain: vec![],
checksum: checksum("whatever"),
deployed_at: "2026-07-31T00:00:00Z".to_owned(),
origin,
},
);
manifest.save(&dir).unwrap();
}
fn scan(label: &'static str, dir: &Path, names: &[&str]) -> TierScan {
TierScan {
label,
dir: dir.to_path_buf(),
found: names
.iter()
.map(|s| MisplacedAgent {
path: dir.join(format!("{s}.md")),
name: (*s).to_owned(),
class: TierResidentClass::ShadowsBundled,
})
.collect(),
}
}
#[test]
fn project_tier_stub_on_a_bundled_name_fails() {
let tmp = tempfile::tempdir().unwrap();
let home = tmp.path().join("home");
let project = tmp.path().join("project");
std::fs::create_dir_all(&home).unwrap();
std::fs::create_dir_all(&project).unwrap();
place_agent(
&project,
"rust-engineer.md",
"---\nname: rust-engineer\n---\n",
);
let paths = hermetic_paths(&home);
let check = check_asset_tier(&paths, Some(&project), &home);
assert_eq!(
check.status,
CheckStatus::Fail,
"message: {}",
check.message
);
assert!(check.message.contains("rust-engineer"), "{}", check.message);
assert!(check.message.contains("project tier"), "{}", check.message);
}
#[test]
fn clean_project_tier_is_ok() {
let tmp = tempfile::tempdir().unwrap();
let home = tmp.path().join("home");
let project = tmp.path().join("project");
std::fs::create_dir_all(&home).unwrap();
std::fs::create_dir_all(&project).unwrap();
let paths = hermetic_paths(&home);
let check = check_asset_tier(&paths, Some(&project), &home);
assert_eq!(check.status, CheckStatus::Ok, "message: {}", check.message);
}
#[test]
fn custom_project_agent_does_not_fire() {
let tmp = tempfile::tempdir().unwrap();
let home = tmp.path().join("home");
let project = tmp.path().join("project");
std::fs::create_dir_all(&home).unwrap();
std::fs::create_dir_all(&project).unwrap();
place_agent(
&project,
"acme-internal-reviewer.md",
&doc("acme-internal-reviewer"),
);
let paths = hermetic_paths(&home);
let check = check_asset_tier(&paths, Some(&project), &home);
assert_eq!(check.status, CheckStatus::Ok, "message: {}", check.message);
}
#[test]
fn user_owned_project_agent_on_a_bundled_name_does_not_fire() {
let tmp = tempfile::tempdir().unwrap();
let home = tmp.path().join("home");
let project = tmp.path().join("project");
std::fs::create_dir_all(&home).unwrap();
std::fs::create_dir_all(&project).unwrap();
place_agent(&project, "qa.md", &doc("qa"));
track(&project, "qa.md", Origin::User);
let paths = hermetic_paths(&home);
let check = check_asset_tier(&paths, Some(&project), &home);
assert_eq!(check.status, CheckStatus::Ok, "message: {}", check.message);
}
#[test]
fn a_renamed_project_file_declaring_a_bundled_name_fails() {
let tmp = tempfile::tempdir().unwrap();
let home = tmp.path().join("home");
let project = tmp.path().join("project");
std::fs::create_dir_all(&home).unwrap();
std::fs::create_dir_all(&project).unwrap();
place_agent(&project, "helper.md", &doc("qa"));
let paths = hermetic_paths(&home);
let check = check_asset_tier(&paths, Some(&project), &home);
assert_eq!(
check.status,
CheckStatus::Fail,
"message: {}",
check.message
);
assert!(check.message.contains("qa"), "{}", check.message);
}
#[test]
fn home_tier_copy_is_only_a_warn() {
let tmp = tempfile::tempdir().unwrap();
let home = tmp.path().join("home");
let project = tmp.path().join("project");
std::fs::create_dir_all(&home).unwrap();
std::fs::create_dir_all(&project).unwrap();
place_agent(&home, "qa.md", &doc("qa"));
let paths = hermetic_paths(&home);
let check = check_asset_tier(&paths, Some(&project), &home);
assert_eq!(
check.status,
CheckStatus::Warn,
"message: {}",
check.message
);
assert!(check.message.contains("qa"), "{}", check.message);
}
#[test]
fn canonical_tier_is_never_scanned() {
let tmp = tempfile::tempdir().unwrap();
let home = tmp.path().join("home");
std::fs::create_dir_all(&home).unwrap();
let paths = hermetic_paths(&home);
let canonical = paths.agent_deploy_dir();
std::fs::create_dir_all(&canonical).unwrap();
std::fs::write(canonical.join("rust-engineer.md"), doc("rust-engineer")).unwrap();
let check = check_asset_tier(&paths, None, &home);
assert_eq!(check.status, CheckStatus::Ok, "message: {}", check.message);
}
#[test]
fn roster_falls_back_to_the_embedded_bundle() {
let tmp = tempfile::tempdir().unwrap();
let paths = hermetic_paths(tmp.path());
assert!(!paths.agent_source_dir().is_dir());
let roster = bundled_roster(&paths);
assert!(
roster.contains("rust-engineer"),
"embedded roster missing rust-engineer: {roster:?}"
);
}
#[test]
fn roster_keys_the_embedded_half_by_declared_name() {
let tmp = tempfile::tempdir().unwrap();
let paths = hermetic_paths(tmp.path());
let roster = bundled_roster(&paths);
assert!(roster.contains("base-agent"), "roster: {roster:?}");
assert!(!roster.contains("BASE-AGENT"), "roster: {roster:?}");
}
#[test]
fn roster_includes_on_disk_source_names() {
let tmp = tempfile::tempdir().unwrap();
let paths = hermetic_paths(tmp.path());
let source = paths.agent_source_dir();
std::fs::create_dir_all(&source).unwrap();
std::fs::write(source.join("whatever.md"), doc("locally-installed")).unwrap();
assert!(bundled_roster(&paths).contains("locally-installed"));
}
#[test]
fn verdict_project_hit_is_fail() {
let dir = PathBuf::from("/w/.claude/agents");
let canonical = PathBuf::from("/c/agents");
let check = verdict(&[scan("project", &dir, &["qa"])], &canonical);
assert_eq!(check.status, CheckStatus::Fail);
}
#[test]
fn verdict_home_only_is_warn() {
let dir = PathBuf::from("/h/.claude/agents");
let canonical = PathBuf::from("/c/agents");
let check = verdict(&[scan("operator home", &dir, &["qa"])], &canonical);
assert_eq!(check.status, CheckStatus::Warn);
}
#[test]
fn verdict_clean_is_ok() {
let dir = PathBuf::from("/w/.claude/agents");
let canonical = PathBuf::from("/c/agents");
let check = verdict(&[scan("project", &dir, &[])], &canonical);
assert_eq!(check.status, CheckStatus::Ok);
assert!(check.message.contains("/c/agents"), "{}", check.message);
}
#[test]
fn verdict_names_the_files_and_both_tiers() {
let project = PathBuf::from("/w/.claude/agents");
let home = PathBuf::from("/h/.claude/agents");
let canonical = PathBuf::from("/c/agents");
let check = verdict(
&[
scan("project", &project, &["qa"]),
scan("operator home", &home, &["engineer"]),
],
&canonical,
);
assert_eq!(check.status, CheckStatus::Fail);
for needle in [
"/w/.claude/agents",
"/h/.claude/agents",
"/c/agents",
"qa",
"engineer",
"--reset-agents-workspaces",
] {
assert!(
check.message.contains(needle),
"missing {needle}: {}",
check.message
);
}
}
#[test]
fn verdict_summarises_a_long_list() {
let dir = PathBuf::from("/w/.claude/agents");
let canonical = PathBuf::from("/c/agents");
let names = ["a", "b", "c", "d", "e", "f"];
let check = verdict(&[scan("project", &dir, &names)], &canonical);
assert!(check.message.contains("(+1 more)"), "{}", check.message);
}