use super::*;
use crate::core::update_check::detect_for_framework;
use crate::provisioner::FakeGitBackend;
use std::fs;
use std::path::Path;
use tempfile::TempDir;
fn mark_catalog_repo_as_valid(catalog_root: &Path) {
let git_dir = catalog_root.join("repo/.git");
fs::create_dir_all(&git_dir).unwrap();
fs::write(
git_dir.join("config"),
"[core]\n\trepositoryformatversion = 0\n[remote \"origin\"]\n\turl = https://github.com/bobmatnyc/claude-mpm\n",
)
.unwrap();
}
fn seed_catalog_agent(catalog_root: &Path, stem: &str, body: &str) {
mark_catalog_repo_as_valid(catalog_root);
let dir = catalog_root.join("repo/.claude/agents");
fs::create_dir_all(&dir).unwrap();
fs::write(
dir.join(format!("{stem}.md")),
format!("---\nname: {stem}\nrole: {stem}\n---\n\n# {stem}\n\n{body}\n"),
)
.unwrap();
}
fn seed_catalog_skill(catalog_root: &Path, stem: &str, body: &str) {
mark_catalog_repo_as_valid(catalog_root);
let dir = catalog_root.join("repo/.claude/skills");
fs::create_dir_all(&dir).unwrap();
fs::write(
dir.join(format!("{stem}.md")),
format!("# {stem}\n\n{body}\n"),
)
.unwrap();
}
fn write_catalog_manifest(project_dir: &Path) {
let dir = project_dir.join(".trusty-mpm");
fs::create_dir_all(&dir).unwrap();
fs::write(
dir.join("manifest.toml"),
"[agents]\nsource = \"catalog\"\n\n[skills]\nsource = \"catalog\"\n",
)
.unwrap();
}
#[test]
fn apply_redeploys_and_clears_staleness() {
let fw_root = TempDir::new().unwrap();
let project = TempDir::new().unwrap();
let fw = crate::core::paths::FrameworkPaths::from_root(fw_root.path().join(".trusty-mpm"));
write_catalog_manifest(project.path());
let catalog_root = crate::content::catalog_root_for(&fw.root);
seed_catalog_agent(&catalog_root, "rust-engineer", "ENGINEER BODY");
seed_catalog_skill(&catalog_root, "tm-doctor", "DOCTOR BODY");
let report = apply_catalog(
FakeGitBackend::new(),
&fw,
project.path(),
true, false, )
.unwrap();
assert!(
report
.agents_deployed
.contains(&"rust-engineer.md".to_string()),
"agent deployed: {report:?}"
);
assert!(
report.skills_deployed.contains(&"tm-doctor".to_string()),
"skill deployed: {report:?}"
);
assert!(fw.agent_deploy_dir().join("rust-engineer.md").is_file());
assert!(
fw.claude_skills_dir()
.join("tm-doctor")
.join("SKILL.md")
.is_file()
);
let report = detect_for_framework(&fw, project.path());
assert!(!report.stale, "apply must clear staleness: {report:?}");
assert!(!report.unknown, "catalog is synced: {report:?}");
}
#[test]
fn apply_is_stale_before_apply_then_fresh_after() {
let fw_root = TempDir::new().unwrap();
let project = TempDir::new().unwrap();
let fw = crate::core::paths::FrameworkPaths::from_root(fw_root.path().join(".trusty-mpm"));
write_catalog_manifest(project.path());
let catalog_root = crate::content::catalog_root_for(&fw.root);
seed_catalog_agent(&catalog_root, "newcomer", "NEW BODY");
let before = detect_for_framework(&fw, project.path());
assert!(
before.stale,
"new catalog agent is stale before apply: {before:?}"
);
apply_catalog(FakeGitBackend::new(), &fw, project.path(), true, false).unwrap();
let after = detect_for_framework(&fw, project.path());
assert!(!after.stale, "apply clears it: {after:?}");
}
#[test]
fn apply_prune_removes_deselected() {
let fw_root = TempDir::new().unwrap();
let project = TempDir::new().unwrap();
let fw = crate::core::paths::FrameworkPaths::from_root(fw_root.path().join(".trusty-mpm"));
let catalog_root = crate::content::catalog_root_for(&fw.root);
seed_catalog_agent(&catalog_root, "keep-me", "KEEP");
seed_catalog_agent(&catalog_root, "drop-me", "DROP");
write_catalog_manifest(project.path());
apply_catalog(FakeGitBackend::new(), &fw, project.path(), true, false).unwrap();
assert!(fw.agent_deploy_dir().join("drop-me.md").is_file());
fs::write(
project.path().join(".trusty-mpm").join("manifest.toml"),
"[agents]\nsource = \"catalog\"\nexclude = [\"drop-me\"]\n\n[skills]\nsource = \"catalog\"\n",
)
.unwrap();
let report = apply_catalog(FakeGitBackend::new(), &fw, project.path(), true, true).unwrap();
assert!(
report.agents_pruned.contains(&"drop-me.md".to_string()),
"drop-me must be pruned: {report:?}"
);
assert!(
!fw.agent_deploy_dir().join("drop-me.md").exists(),
"pruned agent file removed"
);
assert!(
fw.agent_deploy_dir().join("keep-me.md").is_file(),
"selected agent retained"
);
let manifest = AgentManifest::load(&fw.agent_deploy_dir());
assert!(!manifest.is_managed("drop-me.md"));
assert!(manifest.is_managed("keep-me.md"));
}
#[test]
fn apply_preserves_user_tier_override_deployed_via_launch_path() {
let fw_root = TempDir::new().unwrap();
let project = TempDir::new().unwrap();
let fw = crate::core::paths::FrameworkPaths::from_root(fw_root.path().join(".trusty-mpm"));
write_catalog_manifest(project.path());
let catalog_root = crate::content::catalog_root_for(&fw.root);
seed_catalog_skill(&catalog_root, "tm-doctor", "CATALOG BODY");
let user_source = fw.user_skill_source_dir();
fs::create_dir_all(&user_source).unwrap();
fs::write(
user_source.join("tm-doctor.md"),
"---\nname: tm-doctor\n---\n\nUSER OVERRIDE BODY\n",
)
.unwrap();
let sources =
crate::core::manifest::ManifestSources::resolve(project.path(), &fw.root, &catalog_root);
let manifest = crate::core::manifest::resolve_manifest(&sources);
let plan = HarnessPlan::from_manifest(&manifest, &fw, &catalog_root);
crate::core::skill_tiers::deploy_all_skill_tiers(
&plan.skill_source,
&fw.user_skill_source_dir(),
&fw.claude_skills_dir(),
|name| plan.skill_selected(name),
)
.unwrap();
let deployed_path = fw.claude_skills_dir().join("tm-doctor").join("SKILL.md");
let after_launch = fs::read_to_string(&deployed_path).unwrap();
assert!(
after_launch.contains("USER OVERRIDE BODY"),
"user tier must win at launch: {after_launch}"
);
apply_catalog(FakeGitBackend::new(), &fw, project.path(), true, false).unwrap();
let after_apply = fs::read_to_string(&deployed_path).unwrap();
assert_eq!(
after_apply, after_launch,
"apply_catalog must preserve the user-tier override byte-identical"
);
assert!(
after_apply.contains("USER OVERRIDE BODY"),
"override must survive apply: {after_apply}"
);
}
#[test]
fn apply_prune_spares_user_owned() {
let fw_root = TempDir::new().unwrap();
let project = TempDir::new().unwrap();
let fw = crate::core::paths::FrameworkPaths::from_root(fw_root.path().join(".trusty-mpm"));
let catalog_root = crate::content::catalog_root_for(&fw.root);
seed_catalog_agent(&catalog_root, "managed-agent", "BODY");
write_catalog_manifest(project.path());
apply_catalog(FakeGitBackend::new(), &fw, project.path(), true, false).unwrap();
let user_file = fw.agent_deploy_dir().join("my-own.md");
fs::write(&user_file, "USER OWNED").unwrap();
let report = apply_catalog(FakeGitBackend::new(), &fw, project.path(), true, true).unwrap();
assert!(
!report.agents_pruned.contains(&"my-own.md".to_string()),
"user-owned file must not be pruned: {report:?}"
);
assert!(user_file.is_file(), "user-owned file survives prune");
assert_eq!(fs::read_to_string(&user_file).unwrap(), "USER OWNED");
}