use super::*;
use crate::core::doctor::CheckStatus;
fn write_settings(project: &Path, contents: &serde_json::Value) -> PathBuf {
let claude_dir = project.join(".claude");
std::fs::create_dir_all(&claude_dir).unwrap();
let path = claude_dir.join("settings.json");
std::fs::write(&path, serde_json::to_string_pretty(contents).unwrap()).unwrap();
path
}
fn tm_hooks_value() -> serde_json::Value {
serde_json::json!({
"hooks": {
"PreToolUse": [{
"matcher": "*",
"hooks": [{ "type": "command", "command": "/usr/local/bin/tm hook", "timeout": 5 }]
}]
}
})
}
fn claude_mpm_hooks_value() -> serde_json::Value {
serde_json::json!({
"hooks": {
"SessionStart": [{
"matcher": "",
"hooks": [{ "type": "command", "command": "claude-mpm hooks fire SessionStart", "timeout": 5 }]
}]
}
})
}
#[test]
fn check_hooks_hygiene_ok_when_clean() {
let dir = tempfile::tempdir().unwrap();
let project = dir.path().join("clean-project");
write_settings(
&project,
&serde_json::json!({ "outputStyle": "trusty-mpm" }),
);
let (contamination, foreign) = check_hooks_hygiene(Some(&project), &[]);
assert_eq!(contamination.status, CheckStatus::Ok);
assert_eq!(foreign.status, CheckStatus::Ok);
}
#[test]
fn check_hooks_hygiene_warns_on_tm_contamination() {
let dir = tempfile::tempdir().unwrap();
let project = dir.path().join("contaminated-project");
write_settings(&project, &tm_hooks_value());
let (contamination, foreign) = check_hooks_hygiene(Some(&project), &[]);
assert_eq!(contamination.status, CheckStatus::Warn);
assert!(contamination.message.contains("tm hooks clean"));
assert_eq!(foreign.status, CheckStatus::Ok);
}
#[test]
fn check_hooks_hygiene_warns_on_foreign_conflict() {
let dir = tempfile::tempdir().unwrap();
let project = dir.path().join("foreign-project");
write_settings(&project, &claude_mpm_hooks_value());
let (contamination, foreign) = check_hooks_hygiene(Some(&project), &[]);
assert_eq!(contamination.status, CheckStatus::Ok);
assert_eq!(foreign.status, CheckStatus::Warn);
assert!(foreign.message.contains("informational"));
}
#[test]
fn check_hooks_hygiene_warns_on_mixed_group_both_checks() {
let dir = tempfile::tempdir().unwrap();
let project = dir.path().join("mixed-project");
write_settings(
&project,
&serde_json::json!({
"hooks": {
"PreToolUse": [{
"matcher": "*",
"hooks": [
{ "type": "command", "command": "/usr/local/bin/tm hook", "timeout": 5 },
{ "type": "command", "command": "claude-mpm hooks fire PreToolUse", "timeout": 5 }
]
}]
}
}),
);
let (contamination, foreign) = check_hooks_hygiene(Some(&project), &[]);
assert_eq!(
contamination.status,
CheckStatus::Warn,
"the tm entry in the mixed group must be flagged"
);
assert_eq!(
foreign.status,
CheckStatus::Warn,
"the foreign entry in the SAME mixed group must also be flagged"
);
}
#[test]
fn check_hooks_hygiene_never_double_counts_active_workspace_dupes() {
let dir = tempfile::tempdir().unwrap();
let project = dir.path().join("dupe-project");
write_settings(&project, &tm_hooks_value());
let (contamination, _foreign) =
check_hooks_hygiene(Some(&project), std::slice::from_ref(&project));
assert_eq!(contamination.status, CheckStatus::Warn);
assert_eq!(
contamination
.message
.matches(&*project.to_string_lossy())
.count(),
1,
"the same file must appear exactly once in the report: {}",
contamination.message
);
}
#[test]
fn check_hooks_hygiene_covers_active_workspace_paths_beyond_project_dir() {
let dir = tempfile::tempdir().unwrap();
let project = dir.path().join("current-project");
let other_workspace = dir.path().join("other-managed-workspace");
std::fs::create_dir_all(&project).unwrap();
write_settings(&other_workspace, &tm_hooks_value());
let (contamination, _foreign) =
check_hooks_hygiene(Some(&project), std::slice::from_ref(&other_workspace));
assert_eq!(
contamination.status,
CheckStatus::Warn,
"a live workspace outside project_dir must still be scanned"
);
}