use super::*;
fn write_settings(dir: &Path, json: &str) {
let claude_dir = dir.join(".claude");
std::fs::create_dir_all(&claude_dir).unwrap();
std::fs::write(claude_dir.join("settings.json"), json).unwrap();
}
fn write_local_settings(dir: &Path, json: &str) {
let claude_dir = dir.join(".claude");
std::fs::create_dir_all(&claude_dir).unwrap();
std::fs::write(claude_dir.join("settings.local.json"), json).unwrap();
}
fn deploy_default_style(home: &Path) {
let styles_dir = home.join(".claude").join("output-styles");
std::fs::create_dir_all(&styles_dir).unwrap();
let default = OUTPUT_STYLES[0];
std::fs::write(styles_dir.join(default.file_name), default.content).unwrap();
}
fn deploy_all_styles(home: &Path) {
let styles_dir = home.join(".claude").join("output-styles");
std::fs::create_dir_all(&styles_dir).unwrap();
for style in OUTPUT_STYLES {
std::fs::write(styles_dir.join(style.file_name), style.content).unwrap();
}
}
#[test]
fn output_style_ok_when_style_resolves() {
let home = tempfile::tempdir().unwrap();
deploy_default_style(home.path());
write_settings(home.path(), r#"{"outputStyle": "trusty-mpm"}"#);
let check = check_output_style(None, home.path());
assert_eq!(check.status, CheckStatus::Ok);
}
#[test]
fn output_style_fail_when_id_unknown() {
let home = tempfile::tempdir().unwrap();
write_settings(home.path(), r#"{"outputStyle": "claude_mpm"}"#);
let check = check_output_style(None, home.path());
assert_eq!(check.status, CheckStatus::Fail);
assert!(check.message.contains("claude_mpm"));
assert!(
check.message.contains("trusty-mpm"),
"valid id list must be present"
);
}
#[test]
fn output_style_warn_when_key_absent() {
let home = tempfile::tempdir().unwrap();
write_settings(home.path(), r#"{}"#);
let check = check_output_style(None, home.path());
assert_eq!(check.status, CheckStatus::Warn);
}
#[test]
fn output_style_warn_when_settings_missing() {
let home = tempfile::tempdir().unwrap();
let check = check_output_style(None, home.path());
assert_eq!(check.status, CheckStatus::Warn);
}
#[test]
fn output_style_fail_when_file_missing() {
let home = tempfile::tempdir().unwrap();
write_settings(home.path(), r#"{"outputStyle": "trusty-mpm"}"#);
let check = check_output_style(None, home.path());
assert_eq!(check.status, CheckStatus::Fail);
}
#[test]
fn output_style_fail_on_malformed_json() {
let home = tempfile::tempdir().unwrap();
write_settings(home.path(), "{not valid json");
let check = check_output_style(None, home.path());
assert_eq!(check.status, CheckStatus::Fail);
assert!(check.message.contains("not valid JSON"));
}
#[test]
fn output_style_prefers_project_over_global() {
let home = tempfile::tempdir().unwrap();
let project = tempfile::tempdir().unwrap();
deploy_default_style(home.path());
write_settings(home.path(), r#"{"outputStyle": "claude_mpm"}"#);
write_settings(project.path(), r#"{"outputStyle": "trusty-mpm"}"#);
let check = check_output_style(Some(project.path()), home.path());
assert_eq!(check.status, CheckStatus::Ok);
}
#[test]
fn output_style_falls_back_to_global_when_project_silent() {
let home = tempfile::tempdir().unwrap();
let project = tempfile::tempdir().unwrap();
write_settings(home.path(), r#"{"outputStyle": "claude_mpm"}"#);
write_settings(project.path(), r#"{"theme": "dark"}"#);
let check = check_output_style(Some(project.path()), home.path());
assert_eq!(check.status, CheckStatus::Fail);
assert!(check.message.contains("claude_mpm"));
assert!(
check.message.contains("user"),
"message must name the resolved scope: {}",
check.message
);
}
#[test]
fn output_style_falls_back_to_global_when_project_missing() {
let home = tempfile::tempdir().unwrap();
let project = tempfile::tempdir().unwrap();
write_settings(home.path(), r#"{"outputStyle": "claude_mpm"}"#);
let check = check_output_style(Some(project.path()), home.path());
assert_eq!(check.status, CheckStatus::Fail);
assert!(check.message.contains("claude_mpm"));
}
#[test]
fn output_style_reports_project_scope_on_success() {
let home = tempfile::tempdir().unwrap();
let project = tempfile::tempdir().unwrap();
deploy_default_style(home.path());
write_settings(project.path(), r#"{"outputStyle": "trusty-mpm"}"#);
let check = check_output_style(Some(project.path()), home.path());
assert_eq!(check.status, CheckStatus::Ok);
assert!(
check.message.contains("project"),
"message must name the resolved scope: {}",
check.message
);
}
#[test]
fn output_style_project_local_wins_even_when_project_settings_correct() {
let home = crate::test_support::hermetic_temp_dir();
let project = crate::test_support::hermetic_temp_dir();
deploy_default_style(home.path());
write_settings(project.path(), r#"{"outputStyle": "trusty-mpm"}"#); write_local_settings(project.path(), r#"{"outputStyle": "claude_mpm"}"#); let check = check_output_style(Some(project.path()), home.path());
assert_eq!(
check.status,
CheckStatus::Fail,
"message: {}",
check.message
);
assert!(check.message.contains("claude_mpm"));
assert!(
check.message.contains("project-local"),
"message must name the resolved scope: {}",
check.message
);
}
#[test]
fn output_style_project_local_overrides_project() {
let home = crate::test_support::hermetic_temp_dir();
let project = crate::test_support::hermetic_temp_dir();
deploy_all_styles(home.path());
write_settings(project.path(), r#"{"outputStyle": "trusty-mpm"}"#);
write_local_settings(project.path(), r#"{"outputStyle": "trusty-mpm-teacher"}"#);
let check = check_output_style(Some(project.path()), home.path());
assert_eq!(check.status, CheckStatus::Ok, "message: {}", check.message);
assert!(check.message.contains("trusty-mpm-teacher"));
assert!(check.message.contains("project-local"));
}
#[test]
fn output_style_user_local_overrides_user() {
let home = crate::test_support::hermetic_temp_dir();
deploy_all_styles(home.path());
write_settings(home.path(), r#"{"outputStyle": "trusty-mpm"}"#);
write_local_settings(home.path(), r#"{"outputStyle": "trusty-mpm-teacher"}"#);
let check = check_output_style(None, home.path());
assert_eq!(check.status, CheckStatus::Ok, "message: {}", check.message);
assert!(check.message.contains("trusty-mpm-teacher"));
assert!(check.message.contains("user-local"));
}
#[test]
fn output_style_project_scope_still_outranks_user_local() {
let home = crate::test_support::hermetic_temp_dir();
let project = crate::test_support::hermetic_temp_dir();
deploy_default_style(home.path());
write_local_settings(home.path(), r#"{"outputStyle": "claude_mpm"}"#); write_settings(project.path(), r#"{"outputStyle": "trusty-mpm"}"#); let check = check_output_style(Some(project.path()), home.path());
assert_eq!(check.status, CheckStatus::Ok, "message: {}", check.message);
assert!(check.message.contains("project"));
}
#[test]
fn output_style_warn_names_all_checked_layers_when_none_configured() {
let home = crate::test_support::hermetic_temp_dir();
let project = crate::test_support::hermetic_temp_dir();
write_settings(project.path(), r#"{"theme": "dark"}"#);
let check = check_output_style(Some(project.path()), home.path());
assert_eq!(
check.status,
CheckStatus::Warn,
"message: {}",
check.message
);
assert!(check.message.contains("settings.local.json"));
assert!(check.message.contains("settings.json"));
}
#[test]
fn staleness_ok_when_in_sync() {
let home = tempfile::tempdir().unwrap();
deploy_all_styles(home.path());
let check = check_output_style_staleness(None, home.path());
assert_eq!(check.status, CheckStatus::Ok, "message: {}", check.message);
}
#[test]
fn staleness_warns_on_drift() {
let home = tempfile::tempdir().unwrap();
deploy_all_styles(home.path());
let styles_dir = home.path().join(".claude").join("output-styles");
let first = &OUTPUT_STYLES[0];
std::fs::write(
styles_dir.join(first.file_name),
"stale pre-#2328 identity text",
)
.unwrap();
let check = check_output_style_staleness(None, home.path());
assert_eq!(check.status, CheckStatus::Warn);
assert!(check.message.contains(first.file_name));
assert!(check.message.contains("tm install"));
}
#[test]
fn staleness_warns_on_orphan() {
let home = tempfile::tempdir().unwrap();
deploy_all_styles(home.path());
let styles_dir = home.path().join(".claude").join("output-styles");
std::fs::write(styles_dir.join("claude-mpm.md"), "pre-rebrand lineage").unwrap();
let check = check_output_style_staleness(None, home.path());
assert_eq!(check.status, CheckStatus::Warn);
assert!(check.message.contains("claude-mpm.md"));
assert!(
styles_dir.join("claude-mpm.md").exists(),
"orphan detection must never delete the foreign file"
);
}
#[test]
fn staleness_ok_when_dir_missing() {
let home = tempfile::tempdir().unwrap();
let check = check_output_style_staleness(None, home.path());
assert_eq!(check.status, CheckStatus::Ok);
}
#[test]
fn staleness_ok_when_file_never_deployed() {
let home = tempfile::tempdir().unwrap();
deploy_default_style(home.path()); let check = check_output_style_staleness(None, home.path());
assert_eq!(check.status, CheckStatus::Ok, "message: {}", check.message);
}
#[test]
fn staleness_orphan_exempts_configured_custom_id() {
let home = tempfile::tempdir().unwrap();
deploy_all_styles(home.path());
write_settings(home.path(), r#"{"outputStyle": "my-custom-style"}"#);
let styles_dir = home.path().join(".claude").join("output-styles");
std::fs::write(
styles_dir.join("my-custom-style.md"),
"operator's own style",
)
.unwrap();
let check = check_output_style_staleness(None, home.path());
assert_eq!(check.status, CheckStatus::Ok, "message: {}", check.message);
}
#[test]
fn legacy_ids_warns_on_shadowed_layer() {
let home = crate::test_support::hermetic_temp_dir();
let project = crate::test_support::hermetic_temp_dir();
deploy_default_style(home.path());
write_settings(project.path(), r#"{"outputStyle": "trusty-mpm"}"#); write_settings(home.path(), r#"{"outputStyle": "claude_mpm"}"#);
let effective = check_output_style(Some(project.path()), home.path());
assert_eq!(
effective.status,
CheckStatus::Ok,
"sanity: effective layer must resolve fine, message: {}",
effective.message
);
let check = check_output_style_legacy_ids(Some(project.path()), home.path());
assert_eq!(
check.status,
CheckStatus::Warn,
"message: {}",
check.message
);
assert!(check.message.contains("claude_mpm"));
assert!(
check.message.contains(
&home
.path()
.join(".claude")
.join("settings.json")
.display()
.to_string()
),
"message must name the exact offending file: {}",
check.message
);
}
#[test]
fn legacy_ids_does_not_double_report_the_effective_layer() {
let home = crate::test_support::hermetic_temp_dir();
let project = crate::test_support::hermetic_temp_dir();
write_local_settings(project.path(), r#"{"outputStyle": "claude_mpm"}"#); write_settings(home.path(), r#"{"outputStyle": "claude_mpm"}"#);
let check = check_output_style_legacy_ids(Some(project.path()), home.path());
assert_eq!(
check.status,
CheckStatus::Warn,
"message: {}",
check.message
);
let user_path = home.path().join(".claude").join("settings.json");
let project_local_path = project.path().join(".claude").join("settings.local.json");
assert!(
check.message.contains(&user_path.display().to_string()),
"must name the shadowed offender: {}",
check.message
);
assert!(
!check
.message
.contains(&project_local_path.display().to_string()),
"must NOT double-report the effective layer (already `Fail`ed by \
check_output_style): {}",
check.message
);
}
#[test]
fn legacy_ids_ok_when_all_known() {
let home = crate::test_support::hermetic_temp_dir();
let project = crate::test_support::hermetic_temp_dir();
deploy_all_styles(home.path());
write_settings(project.path(), r#"{"outputStyle": "trusty-mpm"}"#); write_settings(home.path(), r#"{"outputStyle": "trusty-mpm-teacher"}"#);
let check = check_output_style_legacy_ids(Some(project.path()), home.path());
assert_eq!(check.status, CheckStatus::Ok, "message: {}", check.message);
}
#[test]
fn legacy_ids_ok_when_none_configured() {
let home = crate::test_support::hermetic_temp_dir();
let project = crate::test_support::hermetic_temp_dir();
let check = check_output_style_legacy_ids(Some(project.path()), home.path());
assert_eq!(check.status, CheckStatus::Ok, "message: {}", check.message);
}