use std::path::Path;
use tempfile::TempDir;
use super::{PROJECT_CONFIG_FILE, ProjectConfigError, ProjectLevelConfig, load_or_report};
fn project_with(body: &str) -> TempDir {
let dir = TempDir::new().expect("tempdir");
std::fs::write(dir.path().join(PROJECT_CONFIG_FILE), body).expect("write config");
dir
}
#[test]
fn project_config_path_is_a_root_dotfile() {
assert_eq!(PROJECT_CONFIG_FILE, ".trusty-mpm.toml");
assert!(
!PROJECT_CONFIG_FILE.contains('/'),
"the project config must sit at the project root, not under a subdirectory"
);
}
#[test]
fn project_config_parses_worktree() {
let cfg = ProjectLevelConfig::from_toml("worktree = false\n", Path::new("t.toml"))
.expect("valid config");
assert_eq!(cfg.worktree, Some(false));
assert_eq!(cfg.default_model, None);
}
#[test]
fn project_config_parses_agent_worktree() {
let cfg = ProjectLevelConfig::from_toml("agent_worktree = false\n", Path::new("t.toml"))
.expect("valid config");
assert_eq!(cfg.agent_worktree, Some(false));
assert_eq!(
cfg.worktree, None,
"the session-placement key must stay undecided"
);
let both = ProjectLevelConfig::from_toml(
"worktree = true\nagent_worktree = false\n",
Path::new("t.toml"),
)
.expect("valid config");
assert_eq!(both.worktree, Some(true));
assert_eq!(both.agent_worktree, Some(false));
}
#[test]
fn project_config_empty_file_is_all_none() {
let cfg = ProjectLevelConfig::from_toml("# nothing yet\n", Path::new("t.toml"))
.expect("an empty config is valid");
assert_eq!(cfg, ProjectLevelConfig::default());
}
#[test]
fn project_config_rejects_unknown_key() {
let err = ProjectLevelConfig::from_toml("worktre = false\n", Path::new("t.toml"))
.expect_err("a misspelled key must be rejected, not silently ignored");
let ProjectConfigError::Malformed { source, .. } = &err else {
panic!("expected a Malformed error, got: {err:?}");
};
let msg = source.to_string();
assert!(
msg.contains("worktre"),
"the error must name the offending key so it can be fixed: {msg}"
);
}
#[test]
fn project_config_rejects_unknown_key_alongside_valid_ones() {
let err = ProjectLevelConfig::from_toml(
"worktree = false\nmodel_default = \"opus\"\n",
Path::new("t.toml"),
)
.expect_err("one bad key invalidates the file");
assert!(matches!(err, ProjectConfigError::Malformed { .. }));
}
#[test]
fn project_config_rejects_wrong_type() {
let err = ProjectLevelConfig::from_toml("worktree = \"false\"\n", Path::new("t.toml"))
.expect_err("a string is not a bool");
assert!(matches!(err, ProjectConfigError::Malformed { .. }));
}
#[test]
fn project_config_absent_is_none() {
let dir = TempDir::new().expect("tempdir");
assert_eq!(
ProjectLevelConfig::load(dir.path()).expect("absent is not an error"),
None
);
}
#[test]
fn project_config_reads_from_disk() {
let dir = project_with("worktree = false\ndefault_model = \"opus\"\n");
let cfg = ProjectLevelConfig::load(dir.path())
.expect("valid config")
.expect("file is present");
assert_eq!(cfg.worktree, Some(false));
assert_eq!(cfg.default_model.as_deref(), Some("opus"));
}
#[test]
fn project_config_load_surfaces_a_bad_file() {
let dir = project_with("worktre = false\n");
let err = ProjectLevelConfig::load(dir.path()).expect_err("a bad file must not be Ok");
assert!(matches!(err, ProjectConfigError::Malformed { .. }));
}
#[test]
fn load_or_report_returns_none_for_unknown_key() {
let dir = project_with("worktre = false\n");
assert_eq!(
load_or_report(dir.path()),
None,
"a rejected file must contribute nothing"
);
}
#[test]
fn load_or_report_returns_none_when_absent() {
let dir = TempDir::new().expect("tempdir");
assert_eq!(load_or_report(dir.path()), None);
}
#[test]
fn load_or_report_returns_a_valid_config() {
let dir = project_with("worktree = true\n");
assert_eq!(
load_or_report(dir.path()).and_then(|c| c.worktree),
Some(true)
);
}
#[test]
fn project_config_parses_session_scope() {
let raw = "[session]\nmcp_servers = [\"slack-mcp\"]\nplugins = [\"aws-core\"]\n";
let cfg = ProjectLevelConfig::from_toml(raw, Path::new("/p/.trusty-mpm.toml")).unwrap();
let session = cfg.session.expect("the table parses");
assert_eq!(session.mcp_servers, Some(vec!["slack-mcp".to_owned()]));
assert_eq!(session.plugins, Some(vec!["aws-core".to_owned()]));
}
#[test]
fn project_config_session_defaults_to_none() {
let cfg = ProjectLevelConfig::from_toml("worktree = true\n", Path::new("/p")).unwrap();
assert_eq!(cfg.session, None);
}
#[test]
fn project_config_rejects_unknown_session_key() {
let raw = "[session]\nmcp_server = [\"slack-mcp\"]\n";
let err = ProjectLevelConfig::from_toml(raw, Path::new("/p")).unwrap_err();
assert!(
matches!(err, ProjectConfigError::Malformed { .. }),
"expected a Malformed error, got {err:?}"
);
}
#[test]
fn project_config_parses_prompt_self_improvement() {
let cfg = ProjectLevelConfig::from_toml(
"prompt_self_improvement = true\n",
Path::new("/p/.trusty-mpm.toml"),
)
.unwrap();
assert_eq!(cfg.prompt_self_improvement, Some(true));
let off = ProjectLevelConfig::from_toml("prompt_self_improvement = false\n", Path::new("/p"))
.unwrap();
assert_eq!(off.prompt_self_improvement, Some(false));
}
#[test]
fn project_config_parses_style() {
let cfg = ProjectLevelConfig::from_toml(
"[style]\nactive = \"trusty-supervisor\"\n",
Path::new("/p/.trusty-mpm.toml"),
)
.unwrap();
assert_eq!(
cfg.style.and_then(|s| s.active).as_deref(),
Some("trusty-supervisor")
);
assert!(ProjectLevelConfig::from_toml("[style]\nactiv = \"x\"\n", Path::new("/p")).is_err());
}
#[test]
fn project_config_parses_profile() {
let cfg = ProjectLevelConfig::from_toml("profile = \"supervisor\"\n", Path::new("/p")).unwrap();
assert_eq!(cfg.profile.as_deref(), Some("supervisor"));
let cfg = ProjectLevelConfig::from_toml("worktree = true\n", Path::new("/p")).unwrap();
assert_eq!(cfg.profile, None);
assert!(
ProjectLevelConfig::from_toml("[session]\nprofile = \"supervisor\"\n", Path::new("/p"))
.is_err()
);
}
#[test]
fn project_config_prompt_self_improvement_defaults_to_none() {
let cfg = ProjectLevelConfig::from_toml("worktree = true\n", Path::new("/p")).unwrap();
assert_eq!(cfg.prompt_self_improvement, None);
}
#[test]
fn project_config_rejects_prompt_self_improvement_inside_session() {
let raw = "[session]\nprompt_self_improvement = true\n";
let err = ProjectLevelConfig::from_toml(raw, Path::new("/p")).unwrap_err();
assert!(
matches!(err, ProjectConfigError::Malformed { .. }),
"expected a Malformed error, got {err:?}"
);
}
#[test]
fn this_repositorys_project_config_enables_prompt_self_improvement() {
let path = trusty_common::test_harness::test_repo_root()
.expect("resolve the checkout")
.join(PROJECT_CONFIG_FILE);
let raw = std::fs::read_to_string(&path).expect("the committed project config is readable");
let cfg = ProjectLevelConfig::from_toml(&raw, &path).expect("it parses");
assert_eq!(cfg.prompt_self_improvement, Some(true));
}