use super::*;
use crate::core::config::{RulesInjection, RulesScope};
use std::path::Path;
fn write(home: &Path, rel: &str, content: &str) {
let p = home.join(rel);
std::fs::create_dir_all(p.parent().unwrap()).unwrap();
std::fs::write(p, content).unwrap();
}
fn check(home: &Path, scope: RulesScope, injection: RulesInjection) -> Outcome {
claude_instructions_check(home, scope, injection)
}
#[test]
fn capacity_hint_is_actionable_for_both_states() {
let warn = capacity_hint(false);
assert!(warn.contains("healthy by design"));
assert!(warn.contains("memory.*"));
let crit = capacity_hint(true);
assert!(crit.contains("lean-ctx knowledge consolidate --all"));
assert!(crit.contains("memory.*"));
assert_ne!(warn, crit);
}
#[test]
fn cwd_looks_like_agent_dir_matches_both_separators() {
for sep in ['/', '\\'] {
for dir in [".lmstudio", ".claude", ".codebuddy", ".codex"] {
let cwd = format!("C:{sep}Users{sep}me{sep}{dir}{sep}mcp");
assert!(
cwd_looks_like_agent_dir(&cwd),
"expected {cwd} to be flagged as an agent dir"
);
}
}
}
#[test]
fn cwd_looks_like_agent_dir_ignores_real_projects() {
for cwd in [
"/home/me/work/myproj",
"/Users/me/code/lean-ctx",
"C:\\src\\app",
] {
assert!(
!cwd_looks_like_agent_dir(cwd),
"{cwd} is a real project and must not be flagged"
);
}
}
#[test]
#[serial_test::serial(claude_config_dir)]
fn v3_layout_block_and_skill_passes() {
let tmp = tempfile::tempdir().unwrap();
write(
tmp.path(),
".claude/CLAUDE.md",
&format!(
"{}\ncontent\n{}",
crate::core::rules_canonical::AGENTS_BLOCK_START,
crate::core::rules_canonical::AGENTS_BLOCK_END,
),
);
write(tmp.path(), ".claude/skills/lean-ctx/SKILL.md", "skill");
let out = check(tmp.path(), RulesScope::Global, RulesInjection::Shared);
assert!(out.ok, "post-setup layout must pass: {}", out.line);
assert!(out.line.contains("CLAUDE.md block + skill"));
}
#[test]
#[serial_test::serial(claude_config_dir)]
fn block_without_skill_still_passes() {
let tmp = tempfile::tempdir().unwrap();
write(
tmp.path(),
".claude/CLAUDE.md",
&format!("{}\nx", crate::core::rules_canonical::AGENTS_BLOCK_START),
);
let out = check(tmp.path(), RulesScope::Global, RulesInjection::Shared);
assert!(out.ok, "{}", out.line);
}
#[test]
#[serial_test::serial(claude_config_dir)]
fn legacy_rules_file_passes() {
let tmp = tempfile::tempdir().unwrap();
write(tmp.path(), ".claude/rules/lean-ctx.md", "rules");
let out = check(tmp.path(), RulesScope::Global, RulesInjection::Shared);
assert!(out.ok, "{}", out.line);
assert!(out.line.contains("legacy rules file"));
}
#[test]
#[serial_test::serial(claude_config_dir)]
fn nothing_installed_fails_and_suggests_setup() {
let tmp = tempfile::tempdir().unwrap();
let out = check(tmp.path(), RulesScope::Global, RulesInjection::Shared);
assert!(!out.ok);
assert!(
out.line.contains("lean-ctx setup"),
"must suggest a command that actually fixes it: {}",
out.line
);
assert!(
!out.line.contains("init --agent claude"),
"init --agent claude no longer creates a Claude rules target"
);
}
#[test]
fn dedicated_injection_with_skill_passes_without_block() {
let tmp = tempfile::tempdir().unwrap();
write(tmp.path(), ".claude/skills/lean-ctx/SKILL.md", "skill");
let out = check(tmp.path(), RulesScope::Global, RulesInjection::Dedicated);
assert!(out.ok, "{}", out.line);
}
#[test]
fn dedicated_injection_without_skill_fails() {
let tmp = tempfile::tempdir().unwrap();
let out = check(tmp.path(), RulesScope::Global, RulesInjection::Dedicated);
assert!(!out.ok);
}
#[test]
fn project_scope_passes_without_global_files() {
let tmp = tempfile::tempdir().unwrap();
let out = check(tmp.path(), RulesScope::Project, RulesInjection::Shared);
assert!(out.ok, "{}", out.line);
}
#[test]
fn injection_off_passes_without_any_files() {
let tmp = tempfile::tempdir().unwrap();
let out = check(tmp.path(), RulesScope::Global, RulesInjection::Off);
assert!(out.ok, "{}", out.line);
}
#[test]
fn pinned_data_dir_parses_toml_json_yaml() {
let toml = "[mcp_servers.lean-ctx.env]\nLEAN_CTX_DATA_DIR = \"/abs/data/lean-ctx\"\n";
let json = "{ \"env\": { \"LEAN_CTX_DATA_DIR\": \"/abs/data/lean-ctx\" } }";
let yaml = " env:\n LEAN_CTX_DATA_DIR: \"/abs/data/lean-ctx\"\n";
for src in [toml, json, yaml] {
assert_eq!(
pinned_data_dir(src),
Some(std::path::PathBuf::from("/abs/data/lean-ctx")),
"failed to parse: {src:?}"
);
}
}
#[test]
fn pinned_data_dir_none_when_key_absent_or_empty() {
assert_eq!(pinned_data_dir("no pin here"), None);
assert_eq!(pinned_data_dir("LEAN_CTX_DATA_DIR = \"\""), None);
}
#[test]
fn pinned_data_dir_trims_trailing_separator() {
assert_eq!(
pinned_data_dir("LEAN_CTX_DATA_DIR = \"/abs/data/lean-ctx/\""),
Some(std::path::PathBuf::from("/abs/data/lean-ctx")),
);
}
#[test]
fn standard_data_pin_is_not_flagged_as_divergent() {
let _lock = crate::core::data_dir::test_env_lock();
let data_home = tempfile::tempdir().unwrap();
crate::test_env::set_var("XDG_DATA_HOME", data_home.path());
let standard = data_home.path().join("lean-ctx");
let custom = std::path::Path::new("/opt/custom/lean-ctx");
let standard_diverges = crate::core::paths::data_pin_diverges_config(&standard);
let custom_diverges = crate::core::paths::data_pin_diverges_config(custom);
crate::test_env::remove_var("XDG_DATA_HOME");
assert!(!standard_diverges, "standard XDG data pin must not diverge");
assert!(custom_diverges, "a custom data pin diverges config");
}