use super::*;
use crate::core::content_source::test_support::rc;
fn bundled_agent_basenames() -> Vec<String> {
roster().iter().map(|(name, _)| name.to_string()).collect()
}
fn roster() -> AgentRoster {
crate::core::content_source::test_support::repo_roster()
}
#[test]
fn agent_bundle_stamp_is_stable_across_calls() {
assert_eq!(agent_bundle_stamp(&roster()), agent_bundle_stamp(&roster()));
}
#[test]
fn agent_bundle_stamp_differs_from_skill_stamp() {
assert_ne!(
agent_bundle_stamp(&roster()),
crate::core::skill_source::skill_bundle_stamp(rc())
);
}
#[test]
fn materialize_agent_artifacts_writes_all_agents() {
let tmp = tempfile::TempDir::new().unwrap();
let agents = tmp.path().join("agents");
let written = materialize_agent_artifacts(&agents, &roster()).unwrap();
let expected = bundled_agent_basenames();
assert_eq!(written.len(), expected.len());
for name in expected {
let content = std::fs::read_to_string(agents.join(&name))
.unwrap_or_else(|e| panic!("missing {name}: {e}"));
assert_eq!(
content,
roster().require(&name).unwrap(),
"{name} content must match bundle"
);
}
assert!(
agents.join("BASE-AGENT.md").exists(),
"BASE-AGENT.md is the file #4840 measured as stale — it must materialize"
);
}
#[test]
fn no_deployed_agent_asset_enables_auto_memory() {
let tmp = tempfile::TempDir::new().unwrap();
let agents = tmp.path().join("agents");
let written = materialize_agent_artifacts(&agents, &roster()).unwrap();
assert!(!written.is_empty(), "the roster must not be empty");
for name in &written {
let text = std::fs::read_to_string(agents.join(name)).unwrap();
let Some(rest) = text.strip_prefix("---\n") else {
continue;
};
let Some(frontmatter) = rest.split("\n---").next() else {
continue;
};
for line in frontmatter.lines() {
assert!(
!line.trim_start().starts_with("memory:"),
"{name} sets the subagent `memory:` frontmatter field: {line:?}"
);
}
}
}
#[test]
fn materialize_agent_artifacts_prunes_files_not_in_table() {
let tmp = tempfile::TempDir::new().unwrap();
let agents = tmp.path().join("agents");
std::fs::create_dir_all(&agents).unwrap();
std::fs::write(agents.join("retired-engineer.md"), "stale\n").unwrap();
materialize_agent_artifacts(&agents, &roster()).unwrap();
assert!(!agents.join("retired-engineer.md").exists());
assert!(agents.join("engineer.md").exists());
}
#[test]
fn ensure_agent_source_fresh_materializes_when_missing() {
let tmp = tempfile::TempDir::new().unwrap();
let agents = tmp.path().join("agents");
assert!(!agents.exists());
let refreshed = ensure_agent_source_fresh_with(&agents, &roster()).unwrap();
assert!(refreshed, "a missing source dir must trigger a refresh");
assert!(agents.join("BASE-AGENT.md").exists());
assert!(agents.join(STAMP_FILE_NAME).exists());
}
#[test]
fn ensure_agent_source_fresh_is_noop_when_current() {
let tmp = tempfile::TempDir::new().unwrap();
let agents = tmp.path().join("agents");
assert!(ensure_agent_source_fresh_with(&agents, &roster()).unwrap());
let marker = agents.join("BASE-AGENT.md");
let before = std::fs::metadata(&marker).unwrap().modified().unwrap();
let refreshed = ensure_agent_source_fresh_with(&agents, &roster()).unwrap();
assert!(!refreshed, "an already-current source dir must be a no-op");
assert_eq!(
before,
std::fs::metadata(&marker).unwrap().modified().unwrap(),
"a no-op refresh must not rewrite files"
);
}
#[test]
fn ensure_agent_source_fresh_prunes_renamed_files() {
let tmp = tempfile::TempDir::new().unwrap();
let agents = tmp.path().join("agents");
std::fs::create_dir_all(&agents).unwrap();
std::fs::write(agents.join("mpm-old-agent.md"), "stale\n").unwrap();
std::fs::write(agents.join(STAMP_FILE_NAME), "stale-stamp").unwrap();
let refreshed = ensure_agent_source_fresh_with(&agents, &roster()).unwrap();
assert!(refreshed, "a stale stamp must trigger a refresh");
assert!(!agents.join("mpm-old-agent.md").exists());
assert!(agents.join("engineer.md").exists());
}
#[test]
fn deploy_summary_lines_is_empty_on_a_clean_deploy() {
let result = DeployResult {
deployed: vec!["engineer.md".into()],
..DeployResult::default()
};
assert!(deploy_summary_lines(&result).is_empty());
}
#[test]
fn deploy_summary_lines_summarises_skips_in_one_line() {
let skipped: Vec<String> = (0..12).map(|i| format!("agent-{i}.md")).collect();
let result = DeployResult {
skipped: skipped.clone(),
untracked_modified: vec!["agent-0.md".into()],
..DeployResult::default()
};
let lines = deploy_summary_lines(&result);
assert_eq!(lines.len(), 1, "one summary line, not N: {lines:?}");
assert!(lines[0].contains("12 agent file(s)"), "{:?}", lines[0]);
assert!(lines[0].contains("agent-0.md"), "{:?}", lines[0]);
assert!(lines[0].contains('…'), "elision marker: {:?}", lines[0]);
assert!(
!lines[0].contains("agent-11.md"),
"the preview must be truncated: {:?}",
lines[0]
);
assert!(
lines[0].contains("--reset-agents agent-0"),
"{:?}",
lines[0]
);
}
#[test]
fn deploy_summary_lines_omits_the_reset_pointer_for_user_owned_edits_only() {
let result = DeployResult {
skipped: vec!["engineer.md".into()],
..DeployResult::default()
};
let lines = deploy_summary_lines(&result);
assert_eq!(lines.len(), 1, "{lines:?}");
assert!(lines[0].contains("engineer.md"), "{:?}", lines[0]);
assert!(
!lines[0].contains("--reset-agents"),
"a user-owned edit must not carry the reset pointer: {:?}",
lines[0]
);
}
#[test]
fn deploy_summary_lines_names_the_untracked_modified_file_in_the_pointer() {
let result = DeployResult {
skipped: vec!["user-owned-edit.md".into(), "untracked-drift.md".into()],
untracked_modified: vec!["untracked-drift.md".into()],
..DeployResult::default()
};
let lines = deploy_summary_lines(&result);
assert_eq!(lines.len(), 1, "{lines:?}");
assert!(
lines[0].contains("--reset-agents untracked-drift"),
"{:?}",
lines[0]
);
assert!(
!lines[0].contains("--reset-agents user-owned-edit"),
"the pointer must never name the user-owned edit: {:?}",
lines[0]
);
}
#[test]
fn deploy_summary_lines_reports_failed_agents() {
let result = DeployResult {
failed: vec!["engineer: invalid frontmatter YAML: bad".into()],
..DeployResult::default()
};
let lines = deploy_summary_lines(&result);
assert_eq!(lines.len(), 1, "{lines:?}");
assert!(lines[0].contains("engineer"), "{:?}", lines[0]);
assert!(
lines[0].contains("did NOT deploy at all"),
"the failure must read as worse than stale: {:?}",
lines[0]
);
}
#[test]
fn autodeploy_agents_deploys_when_bundle_differs() {
let tmp = tempfile::TempDir::new().unwrap();
let source = tmp.path().join("framework/agents");
let target = tmp.path().join("claude-config/agents");
let out = autodeploy_agents_with(&source, &target, &std::env::temp_dir(), Ok(roster()));
assert!(out.refreshed, "a stale (absent) source must refresh");
assert!(
out.warnings.is_empty(),
"unexpected warnings: {:?}",
out.warnings
);
assert!(
out.deployed.iter().any(|f| f == "BASE-AGENT.md"),
"BASE-AGENT.md must deploy: {:?}",
out.deployed
);
assert!(target.join("engineer.md").exists());
}
#[test]
fn autodeploy_agents_is_a_noop_when_already_current() {
let tmp = tempfile::TempDir::new().unwrap();
let source = tmp.path().join("framework/agents");
let target = tmp.path().join("claude-config/agents");
assert!(
autodeploy_agents_with(&source, &target, &std::env::temp_dir(), Ok(roster())).refreshed
);
let marker = target.join("engineer.md");
let before = std::fs::metadata(&marker).unwrap().modified().unwrap();
let out = autodeploy_agents_with(&source, &target, &std::env::temp_dir(), Ok(roster()));
assert!(!out.refreshed, "matching checksums must skip the refresh");
assert!(
out.deployed.is_empty(),
"nothing should be rewritten: {:?}",
out.deployed
);
assert!(out.warnings.is_empty(), "{:?}", out.warnings);
assert_eq!(
before,
std::fs::metadata(&marker).unwrap().modified().unwrap(),
"a no-op run must not rewrite deployed files"
);
}
#[test]
fn autodeploy_agents_fails_open_when_target_is_unwritable() {
let tmp = tempfile::TempDir::new().unwrap();
let source = tmp.path().join("framework/agents");
let target = tmp.path().join("not-a-dir");
std::fs::write(&target, "blocking file\n").unwrap();
let out = autodeploy_agents_with(&source, &target, &std::env::temp_dir(), Ok(roster()));
assert!(
out.refreshed,
"the source refresh is independent of the target and must still run"
);
assert!(out.deployed.is_empty());
assert!(
out.warnings.iter().any(|w| w.contains("could not deploy")),
"the failure must surface as a warning, not an error: {:?}",
out.warnings
);
assert_eq!(std::fs::read_to_string(&target).unwrap(), "blocking file\n");
}
#[test]
fn autodeploy_agents_fails_open_when_source_is_unwritable() {
let tmp = tempfile::TempDir::new().unwrap();
let source = tmp.path().join("not-a-dir");
std::fs::write(&source, "blocking file\n").unwrap();
let target = tmp.path().join("claude-config/agents");
let out = autodeploy_agents_with(&source, &target, &std::env::temp_dir(), Ok(roster()));
assert!(!out.refreshed);
assert!(
out.warnings.iter().any(|w| w.contains("could not refresh")),
"{:?}",
out.warnings
);
}
#[test]
fn autodeploy_agents_warns_when_it_skips_a_user_modified_file() {
let tmp = tempfile::TempDir::new().unwrap();
let source = tmp.path().join("framework/agents");
let target = tmp.path().join("claude-config/agents");
std::fs::create_dir_all(&target).unwrap();
std::fs::write(
target.join("engineer.md"),
"---\nname: engineer\ndescription: hand-edited\n---\n\nMine.\n",
)
.unwrap();
let out = autodeploy_agents_with(&source, &target, &std::env::temp_dir(), Ok(roster()));
assert!(
out.warnings.iter().any(|w| w.contains("engineer.md")),
"the skipped file must be named in a warning: {:?}",
out.warnings
);
assert!(
!out.deployed.iter().any(|f| f == "engineer.md"),
"the user's file must be preserved, not overwritten"
);
assert!(
std::fs::read_to_string(target.join("engineer.md"))
.unwrap()
.contains("Mine."),
"the user's content must survive byte-for-byte"
);
assert!(target.join("BASE-AGENT.md").exists());
}
#[test]
fn autodeploy_agents_for_skips_source_refresh_on_submodule() {
let tmp = tempfile::TempDir::new().unwrap();
let checkout = tempfile::TempDir::new().unwrap();
let submodule = checkout.path().join("agents").join("agents");
std::fs::create_dir_all(&submodule).unwrap();
std::fs::write(
submodule.join("submodule-agent.md"),
"---\nname: submodule-agent\ndescription: from the submodule\n---\n\nSUBMODULE.\n",
)
.unwrap();
let mut paths = FrameworkPaths::under(tmp.path());
paths.trusty_mpm_root = Some(checkout.path().to_path_buf());
assert_eq!(paths.agent_source_dir(), submodule);
let target = tmp.path().join("claude-config/agents");
let out = autodeploy_agents_for(&paths, &target, &std::env::temp_dir());
assert!(
!out.refreshed,
"a submodule source is never re-materialized"
);
assert!(
!paths.agents.exists(),
"must never create the framework source dir when a submodule is in play"
);
assert_eq!(
std::fs::read_dir(&submodule).unwrap().count(),
1,
"the git-tracked submodule must be left exactly as found"
);
assert!(target.join("submodule-agent.md").exists());
}
#[test]
fn autodeploy_agents_for_falls_back_when_the_submodule_is_empty() {
let tmp = tempfile::TempDir::new().unwrap();
let checkout = tempfile::TempDir::new().unwrap();
let submodule = checkout.path().join("agents").join("agents");
std::fs::create_dir_all(&submodule).unwrap();
let mut paths = FrameworkPaths::under(tmp.path());
paths.trusty_mpm_root = Some(checkout.path().to_path_buf());
assert_eq!(paths.agent_source_dir(), submodule);
let target = tmp.path().join("claude-config/agents");
let out = autodeploy_agents_for(&paths, &target, &std::env::temp_dir());
assert!(
out.refreshed,
"the empty submodule must not suppress the bundle refresh"
);
assert!(
out.deployed.iter().any(|f| f == "BASE-AGENT.md"),
"the bundled roster must land: {:?}",
out.deployed
);
assert!(target.join("engineer.md").exists());
assert_eq!(
std::fs::read_dir(&submodule).unwrap().count(),
0,
"the submodule directory is still never written"
);
}
#[test]
fn autodeploy_agents_overwrites_a_drifted_bundled_file() {
let tmp = tempfile::TempDir::new().unwrap();
let source = tmp.path().join("framework/agents");
let target = tmp.path().join("claude-config/agents");
assert!(
!autodeploy_agents_with(&source, &target, &std::env::temp_dir(), Ok(roster()))
.deployed
.is_empty()
);
std::fs::write(target.join("engineer.md"), "CORRUPTED\n").unwrap();
let out = autodeploy_agents_with(&source, &target, &std::env::temp_dir(), Ok(roster()));
assert!(
out.deployed.iter().any(|f| f == "engineer.md"),
"a drifted bundled-origin file must be re-deployed: {:?}",
out.deployed
);
assert!(
!std::fs::read_to_string(target.join("engineer.md"))
.unwrap()
.contains("CORRUPTED")
);
assert!(
out.warnings.is_empty(),
"repairing framework-owned drift is not a declined overwrite: {:?}",
out.warnings
);
}
#[test]
fn autodeploy_agents_deploys_nothing_without_content() {
use crate::core::content_source::{DevOverride, agent_roster_in};
let cache = tempfile::TempDir::new().unwrap();
let missing = || agent_roster_in(cache.path(), DevOverride::Off);
let tmp = tempfile::TempDir::new().unwrap();
let source = tmp.path().join("framework/agents");
let target = tmp.path().join("claude-config/agents");
let out = autodeploy_agents_with(&source, &target, &std::env::temp_dir(), missing());
assert!(!out.refreshed);
assert!(out.deployed.is_empty(), "{:?}", out.deployed);
assert!(out.warnings.is_empty(), "{:?}", out.warnings);
assert!(
!source.exists(),
"an unavailable roster must not create the source"
);
materialize_agent_artifacts(&source, &roster()).unwrap();
let out = autodeploy_agents_with(&source, &target, &std::env::temp_dir(), missing());
assert!(!out.refreshed);
assert!(out.deployed.is_empty(), "{:?}", out.deployed);
assert!(
!target.join("engineer.md").exists(),
"a stale source must not deploy"
);
assert!(out.warnings.is_empty(), "{:?}", out.warnings);
}