use super::paths::{ENV_CONFIG_DIR, ENV_DATA_DIR};
use super::*;
fn write(dir: &Path, name: &str, body: &str) -> PathBuf {
let p = dir.join(name);
std::fs::write(&p, body).unwrap();
p
}
#[test]
fn missing_global_config_is_all_defaults() {
let tmp = tempfile::tempdir().unwrap();
let cfg = GlobalConfig::load_from(&tmp.path().join("nope.toml")).unwrap();
assert_eq!(cfg.plugin.family_priority[0], "node");
assert_eq!(cfg.plugin.priority[0], "pnpm");
assert!(cfg.discovery.walk_up);
assert_eq!(cfg.discovery.max_depth, 8);
assert!(cfg.discovery.stop_at_git);
assert!(cfg.plugin_store.effective_prefer_prebuilt());
}
#[test]
fn unknown_keys_in_the_global_config_are_kept() {
let tmp = tempfile::tempdir().unwrap();
let path = write(
tmp.path(),
"config.toml",
r#"
[plugin]
family_priority = ["rust", "node"]
# added by the user, pmpx does not know it
[my_own_thing]
keep = "me"
"#,
);
let cfg = GlobalConfig::load_from(&path).unwrap();
assert_eq!(cfg.plugin.family_priority, vec!["rust", "node"]);
assert_eq!(cfg.plugin.priority[0], "pnpm");
assert!(cfg.extra.contains_key("my_own_thing"));
}
#[test]
fn a_broken_global_config_is_an_error_not_a_silent_default() {
let tmp = tempfile::tempdir().unwrap();
let path = write(tmp.path(), "config.toml", "this is not toml = = =");
let err = GlobalConfig::load_from(&path).unwrap_err();
assert!(
err.to_string()
.contains("failed to parse the global config"),
"{err}"
);
}
#[test]
fn project_config_missing_is_none_not_default() {
let tmp = tempfile::tempdir().unwrap();
assert!(ProjectConfig::load_from(&tmp.path().join("nope.toml"))
.unwrap()
.is_none());
}
#[test]
fn near_config_wins_over_far_one() {
let tmp = tempfile::tempdir().unwrap();
let far = write(
tmp.path(),
"far.toml",
"[plugin]\nnode = \"npm\"\nrust = \"cargo\"\n",
);
let near = write(tmp.path(), "near.toml", "[plugin]\nnode = \"pnpm\"\n");
let merged = MergedProjectConfig::from_paths_near_to_far(&[near.clone(), far.clone()]).unwrap();
assert_eq!(
merged.pinned_plugin("node"),
Some("pnpm"),
"the nearer one wins"
);
assert_eq!(
merged.pinned_plugin("rust"),
Some("cargo"),
"the farther one is not clobbered"
);
assert_eq!(merged.pinned_plugin("python"), None);
}
#[test]
fn overlaying_unknown_keys_does_not_clobber_known_ones() {
let mut base = ProjectConfig::default();
base.plugin.insert("node".into(), "npm".into());
let mut other = ProjectConfig::default();
other.plugin.insert("node".into(), "pnpm".into());
base.overlay(other);
assert_eq!(base.plugin.get("node").map(String::as_str), Some("pnpm"));
}
#[test]
fn scripts_merge_across_layers() {
let tmp = tempfile::tempdir().unwrap();
let far = write(
tmp.path(),
"far.toml",
"[scripts]\nfmt = \"run format\"\nlint = \"run lint\"\n",
);
let near = write(tmp.path(), "near.toml", "[scripts]\nfmt = \"run f\"\n");
let merged = MergedProjectConfig::from_paths_near_to_far(&[near, far]).unwrap();
assert_eq!(merged.scripts.get("fmt").map(String::as_str), Some("run f"));
assert_eq!(
merged.scripts.get("lint").map(String::as_str),
Some("run lint")
);
}
#[test]
fn sources_are_recorded_near_to_far() {
let tmp = tempfile::tempdir().unwrap();
let far = write(tmp.path(), "far.toml", "[plugin]\nrust = \"cargo\"\n");
let near = write(tmp.path(), "near.toml", "[plugin]\nnode = \"pnpm\"\n");
let merged = MergedProjectConfig::from_paths_near_to_far(&[near.clone(), far.clone()]).unwrap();
assert_eq!(merged.sources, vec![near, far], "near to far");
}
#[test]
fn a_config_file_that_vanished_mid_walk_is_skipped() {
let tmp = tempfile::tempdir().unwrap();
let real = write(tmp.path(), "real.toml", "[plugin]\nrust = \"cargo\"\n");
let ghost = tmp.path().join("ghost.toml");
let merged = MergedProjectConfig::from_paths_near_to_far(&[ghost, real]).unwrap();
assert_eq!(merged.pinned_plugin("rust"), Some("cargo"));
assert_eq!(merged.sources.len(), 1);
}
#[test]
fn expand_tilde_expands_to_home() {
let home = directories::UserDirs::new()
.unwrap()
.home_dir()
.to_path_buf();
assert_eq!(expand_tilde("~/x/y"), home.join("x").join("y"));
assert_eq!(expand_tilde("~"), home);
assert_eq!(expand_tilde("/abs/path"), PathBuf::from("/abs/path"));
assert_eq!(
expand_tilde("relative/path"),
PathBuf::from("relative/path")
);
}
#[test]
fn pinned_families_lists_every_pin() {
let tmp = tempfile::tempdir().unwrap();
let a = write(
tmp.path(),
"a.toml",
"[plugin]\nnode = \"pnpm\"\nrust = \"cargo\"\n",
);
let merged = MergedProjectConfig::from_paths_near_to_far(&[a]).unwrap();
let mut fams = merged.pinned_families();
fams.sort_unstable();
assert_eq!(fams, vec!["node", "rust"]);
}
#[test]
fn env_overrides_take_effect() {
let tmp = tempfile::tempdir().unwrap();
let cfg_dir = tmp.path().join("cfg");
let data_dir = tmp.path().join("data");
let old_cfg = std::env::var_os(ENV_CONFIG_DIR);
let old_data = std::env::var_os(ENV_DATA_DIR);
unsafe {
std::env::set_var(ENV_CONFIG_DIR, &cfg_dir);
std::env::set_var(ENV_DATA_DIR, &data_dir);
}
let got_cfg = global_config_path().unwrap();
let got_data = default_data_dir().unwrap();
unsafe {
match old_cfg {
Some(v) => std::env::set_var(ENV_CONFIG_DIR, v),
None => std::env::remove_var(ENV_CONFIG_DIR),
}
match old_data {
Some(v) => std::env::set_var(ENV_DATA_DIR, v),
None => std::env::remove_var(ENV_DATA_DIR),
}
}
assert_eq!(got_cfg, cfg_dir.join("config.toml"));
assert_eq!(got_data, data_dir);
}
#[test]
fn without_overrides_the_platform_paths_are_used() {
if std::env::var_os(ENV_CONFIG_DIR).is_none() {
let p = global_config_path().unwrap();
assert!(p.ends_with("config.toml"), "{p:?}");
assert!(
p.to_string_lossy().contains("pmpx"),
"the path should contain pmpx: {p:?}"
);
}
if std::env::var_os(ENV_DATA_DIR).is_none() {
let p = default_data_dir().unwrap();
assert_eq!(p.file_name().unwrap().to_string_lossy(), ".pmpx");
}
}