use super::*;
fn tmp_config() -> (tempfile::TempDir, std::path::PathBuf) {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("config.toml");
(dir, path)
}
#[test]
fn update_global_at_preserves_customized_and_persists_change() {
let (_dir, path) = tmp_config();
std::fs::write(
&path,
"max_ram_percent = 30\ncompression_level = \"standard\"\n",
)
.unwrap();
let returned = Config::update_global_at(&path, |c| c.proxy_enabled = Some(true))
.expect("update_global_at must succeed");
assert_eq!(returned.proxy_enabled, Some(true));
let reloaded: Config = toml::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap();
assert_eq!(
reloaded.max_ram_percent, 30,
"customized value must survive"
);
assert_eq!(reloaded.compression_level, CompressionLevel::Standard);
assert_eq!(reloaded.proxy_enabled, Some(true));
}
#[test]
fn load_global_from_reads_only_the_given_file() {
let (_dir, path) = tmp_config();
std::fs::write(&path, "theme = \"global-theme\"\n").unwrap();
let cfg = Config::load_global_from(&path);
assert_eq!(cfg.theme, "global-theme");
}
#[test]
fn merged_load_then_save_leaks_local_override_root_cause_marker() {
let (_dir, path) = tmp_config();
std::fs::write(&path, "checkpoint_interval = 40\n").unwrap();
let mut cfg = Config::load_global_from(&path);
cfg.merge_local("checkpoint_interval = 3\n", true);
cfg.save_to(&path).unwrap();
let reloaded: Config = toml::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap();
assert_eq!(
reloaded.checkpoint_interval, 3,
"OLD load+merge_local+save leaks the project-local value into global (#443)"
);
}
#[test]
fn update_global_at_refuses_unparseable_and_leaves_file_untouched() {
let (_dir, path) = tmp_config();
let corrupt = "max_ram_percent = = =\n";
std::fs::write(&path, corrupt).unwrap();
let result = Config::update_global_at(&path, |c| c.proxy_enabled = Some(true));
assert!(
result.is_err(),
"must refuse to modify an unparseable config"
);
assert_eq!(
std::fs::read_to_string(&path).unwrap(),
corrupt,
"the corrupt file must be left exactly as-is"
);
}
#[test]
fn update_global_at_loads_its_own_base_not_the_callers() {
let (_dir, path) = tmp_config();
std::fs::write(&path, "[telemetry]\nenabled = true\n").unwrap();
let stale = Config::load_global_from(&path);
assert!(stale.telemetry.enabled);
std::fs::write(&path, "[telemetry]\nenabled = false\n").unwrap();
let mut seen_enabled = None;
Config::update_global_at(&path, |on_disk| {
seen_enabled = Some(on_disk.telemetry.enabled);
on_disk.max_ram_percent = 42;
})
.expect("update must succeed");
assert_eq!(
seen_enabled,
Some(false),
"the closure's base is loaded inside the call, so it reflects the file"
);
let reloaded: Config = toml::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap();
assert!(!reloaded.telemetry.enabled);
assert_eq!(reloaded.max_ram_percent, 42);
}
#[test]
fn a_second_update_cannot_load_until_the_first_closure_commits() {
use std::sync::mpsc;
use std::time::Duration;
let guard_against_hang = Duration::from_secs(10);
let (_dir, path) = tmp_config();
std::fs::write(&path, "max_ram_percent = 10\n").unwrap();
let (a_entered_tx, a_entered_rx) = mpsc::channel::<()>();
let (a_release_tx, a_release_rx) = mpsc::channel::<()>();
let (b_base_tx, b_base_rx) = mpsc::channel::<u8>();
std::thread::scope(|scope| {
let path_a = &path;
let writer_a = scope.spawn(move || {
Config::update_global_at(path_a, |cfg| {
cfg.max_ram_percent = 20;
a_entered_tx.send(()).unwrap();
a_release_rx.recv_timeout(guard_against_hang).unwrap();
})
});
a_entered_rx
.recv_timeout(guard_against_hang)
.expect("writer A must reach its closure");
let probe = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(crate::config_io::config_migration_lock_path(&path).unwrap())
.unwrap();
let exclusion = fs2::FileExt::try_lock_exclusive(&probe);
if exclusion.is_ok() {
fs2::FileExt::unlock(&probe).unwrap();
a_release_tx.send(()).unwrap();
writer_a.join().unwrap().unwrap();
panic!("config lock must cover the update closure, not only the write");
}
assert!(crate::core::file_lock::is_contended(
&exclusion.unwrap_err()
));
let writer_b = scope.spawn(|| {
Config::update_global_at(&path, |cfg| {
b_base_tx.send(cfg.max_ram_percent).unwrap();
cfg.proxy_enabled = Some(true);
})
});
a_release_tx.send(()).unwrap();
writer_a.join().unwrap().expect("writer A must succeed");
writer_b.join().unwrap().expect("writer B must succeed");
});
assert_eq!(
b_base_rx.recv_timeout(guard_against_hang).unwrap(),
20,
"writer B must load a base that already contains writer A's commit"
);
let reloaded: Config = toml::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap();
assert_eq!(reloaded.max_ram_percent, 20, "writer A's value survived");
assert_eq!(
reloaded.proxy_enabled,
Some(true),
"writer B's value survived"
);
}
#[test]
fn load_global_from_missing_or_empty_yields_defaults() {
let (_dir, path) = tmp_config();
let cfg = Config::load_global_from(&path);
assert_eq!(cfg.max_ram_percent, Config::default().max_ram_percent);
std::fs::write(&path, " \n").unwrap();
let cfg2 = Config::load_global_from(&path);
assert_eq!(cfg2.max_ram_percent, Config::default().max_ram_percent);
}