use super::*;
fn skill_doc(stem: &str) -> String {
format!("---\nname: {stem}\ndescription: fixture\n---\n\n# {stem}\n")
}
fn fixture(base: &Path, stems: &[&str]) -> (FrameworkPaths, PathBuf) {
let paths = FrameworkPaths::under(base);
let source = paths.skill_source_dir();
std::fs::create_dir_all(&source).expect("fixture: bundled source dir");
for stem in stems {
std::fs::write(source.join(format!("{stem}.md")), skill_doc(stem))
.expect("fixture: write bundled skill");
}
let project = base.join("project");
std::fs::create_dir_all(&project).expect("fixture: project dir");
(paths, project)
}
fn deploy_with(tier: &Path, stem: &str, body: &str) -> PathBuf {
let dir = tier.join(stem);
std::fs::create_dir_all(&dir).expect("fixture: deployed skill dir");
let entry = dir.join("SKILL.md");
std::fs::write(&entry, body).expect("fixture: write SKILL.md");
entry
}
fn deploy(tier: &Path, stem: &str) -> PathBuf {
deploy_with(tier, stem, &skill_doc(stem))
}
#[test]
fn a_fully_deployed_roster_is_ok() {
let tmp = tempfile::tempdir().expect("tempdir");
let (paths, project) = fixture(tmp.path(), &["tm-workflow", "tm-ticketing"]);
let tier = paths.skill_deploy_dir();
deploy(&tier, "tm-workflow");
deploy(&tier, "tm-ticketing");
let check = check_skill_reachability(&paths, Some(&project));
assert_eq!(check.status, CheckStatus::Ok, "{check:?}");
assert_eq!(check.name, "skill_reachability");
assert!(
check.message.contains('2'),
"the count belongs in the clean message: {}",
check.message
);
}
#[test]
fn a_rostered_skill_deployed_nowhere_fails_and_names_it() {
let tmp = tempfile::tempdir().expect("tempdir");
let (paths, project) = fixture(tmp.path(), &["tm-workflow", "cto-kb-ingest"]);
deploy(&paths.skill_deploy_dir(), "tm-workflow");
let check = check_skill_reachability(&paths, Some(&project));
assert_eq!(
check.status,
CheckStatus::Fail,
"a rostered skill nothing deployed must FAIL, not warn: {}",
check.message
);
assert!(
check.message.contains("cto-kb-ingest"),
"the unreachable skill must be named: {}",
check.message
);
assert!(
check.message.contains("#4947"),
"the message must cite the issue: {}",
check.message
);
}
#[test]
fn malformed_frontmatter_fails_and_names_the_path() {
let tmp = tempfile::tempdir().expect("tempdir");
let (paths, project) = fixture(tmp.path(), &["tm-workflow"]);
let entry = deploy_with(
&paths.skill_deploy_dir(),
"tm-workflow",
"---\nname: tm-workflow\ndescription: broken: unquoted colon\n---\n\nbody\n",
);
let check = check_skill_reachability(&paths, Some(&project));
assert_eq!(check.status, CheckStatus::Fail, "{}", check.message);
assert!(
check.message.contains(&entry.display().to_string()),
"the message must name the file: {}",
check.message
);
assert!(
check.message.contains("does not parse"),
"and say what is wrong with it: {}",
check.message
);
}
#[test]
fn a_frontmatter_name_that_disagrees_with_the_directory_fails() {
let tmp = tempfile::tempdir().expect("tempdir");
let (paths, project) = fixture(tmp.path(), &["tm-workflow"]);
deploy_with(
&paths.skill_deploy_dir(),
"tm-workflow",
&skill_doc("tm-workflow-v2"),
);
let check = check_skill_reachability(&paths, Some(&project));
assert_eq!(check.status, CheckStatus::Fail, "{}", check.message);
assert!(
check.message.contains("tm-workflow-v2"),
"the declared name must be quoted back: {}",
check.message
);
}
#[test]
fn frontmatter_without_a_name_fails() {
let tmp = tempfile::tempdir().expect("tempdir");
let (paths, project) = fixture(tmp.path(), &["tm-workflow"]);
deploy_with(
&paths.skill_deploy_dir(),
"tm-workflow",
"---\ndescription: nameless\n---\n\nbody\n",
);
let check = check_skill_reachability(&paths, Some(&project));
assert_eq!(check.status, CheckStatus::Fail, "{}", check.message);
assert!(
check.message.contains("declares no `name`"),
"the message must say what is missing: {}",
check.message
);
}
#[test]
fn a_project_copy_shadowing_the_user_copy_warns_and_names_both() {
let tmp = tempfile::tempdir().expect("tempdir");
let (paths, project) = fixture(tmp.path(), &["tm-ticketing"]);
let winner = deploy(&paths.skill_deploy_dir(), "tm-ticketing");
let loser = deploy(&project.join(".claude").join("skills"), "tm-ticketing");
let check = check_skill_reachability(&paths, Some(&project));
assert_eq!(
check.status,
CheckStatus::Warn,
"a shadowed duplicate still loads, so it warns rather than fails: {}",
check.message
);
assert!(
check.message.contains(&winner.display().to_string())
&& check.message.contains(&loser.display().to_string()),
"both paths must be named: {}",
check.message
);
assert!(
check.message.contains("delete the shadowed copy"),
"and the message must say which copy to delete: {}",
check.message
);
}
#[test]
fn an_unreachable_skill_outranks_a_shadow() {
let tmp = tempfile::tempdir().expect("tempdir");
let (paths, project) = fixture(tmp.path(), &["tm-workflow", "tm-ticketing"]);
deploy(&paths.skill_deploy_dir(), "tm-ticketing");
deploy(&project.join(".claude").join("skills"), "tm-ticketing");
let check = check_skill_reachability(&paths, Some(&project));
assert_eq!(check.status, CheckStatus::Fail, "{}", check.message);
assert!(
check.message.contains("tm-workflow") && check.message.contains("tm-ticketing"),
"both findings must survive into the message: {}",
check.message
);
}
#[test]
fn an_empty_roster_is_unknown_not_ok() {
let tmp = tempfile::tempdir().expect("tempdir");
let (paths, project) = fixture(tmp.path(), &[]);
let check = check_skill_reachability(&paths, Some(&project));
assert_eq!(check.status, CheckStatus::Unknown, "{check:?}");
assert_ne!(check.status, CheckStatus::Ok);
}
#[test]
#[cfg(unix)]
fn an_unreadable_tier_is_unknown_not_ok() {
use std::os::unix::fs::PermissionsExt;
let tmp = tempfile::tempdir().expect("tempdir");
let (paths, project) = fixture(tmp.path(), &["tm-workflow"]);
let tier = paths.skill_deploy_dir();
deploy(&tier, "tm-workflow");
let original = std::fs::metadata(&tier)
.expect("tier metadata")
.permissions();
std::fs::set_permissions(&tier, std::fs::Permissions::from_mode(0o000)).expect("chmod 000");
let readable_anyway = std::fs::read_dir(&tier).is_ok();
let check = check_skill_reachability(&paths, Some(&project));
std::fs::set_permissions(&tier, original).expect("restore tier permissions");
if readable_anyway {
return;
}
assert_eq!(
check.status,
CheckStatus::Unknown,
"an unreadable tier is undetermined, never healthy: {check:?}"
);
assert!(
check.message.contains("could not be read"),
"the message must say what it could not do: {}",
check.message
);
}
#[test]
fn the_probe_removes_nothing_it_reports() {
let tmp = tempfile::tempdir().expect("tempdir");
let (paths, project) = fixture(tmp.path(), &["tm-ticketing"]);
let winner = deploy(&paths.skill_deploy_dir(), "tm-ticketing");
let loser = deploy(&project.join(".claude").join("skills"), "tm-ticketing");
let check = check_skill_reachability(&paths, Some(&project));
assert_eq!(check.status, CheckStatus::Warn);
assert!(
winner.exists() && loser.exists(),
"the probe writes nothing"
);
}
#[test]
fn resolves_without_a_project_directory() {
let tmp = tempfile::tempdir().expect("tempdir");
let (paths, _project) = fixture(tmp.path(), &["tm-workflow"]);
deploy(&paths.skill_deploy_dir(), "tm-workflow");
let check = check_skill_reachability(&paths, None);
assert_eq!(check.status, CheckStatus::Ok, "{check:?}");
}