use super::*;
use std::io::Write;
use tempfile::NamedTempFile;
#[test]
fn backendless_config_deserializes_empty_but_default_keeps_fallback() {
let cfg: Config = toml::from_str("providers = []\n").unwrap();
assert!(
cfg.backends.is_empty(),
"absent [[backends]] deserializes to empty, got {:?}",
cfg.backends
);
assert_eq!(Config::default().backends.len(), 1);
assert_eq!(Config::default().backends[0].name, "ollama");
let inline: Config =
toml::from_str("[[backends]]\nname=\"x\"\nendpoint=\"http://h:1\"\nmodel=\"m\"\n").unwrap();
assert_eq!(inline.backends.len(), 1);
assert_eq!(inline.backends[0].name, "x");
}
#[serial_test::serial(real_fs)] #[test]
fn ollama_host_names_the_fallback_backend() {
let _env = crate::test_guard::GlobalSettingsGuard::acquire();
crate::process_env::set_var("OLLAMA_HOST", "http://127.0.0.1:65535");
let cfg = Config::default();
assert_eq!(cfg.backends.len(), 1);
assert_eq!(cfg.backends[0].endpoint, "http://127.0.0.1:65535");
assert_eq!(cfg.backends[0].kind, Some(BackendKind::Ollama));
crate::process_env::set_var("OLLAMA_HOST", "");
assert_eq!(
Config::default().backends[0].endpoint,
"http://127.0.0.1:11434"
);
}
#[test]
fn defaults_are_sensible() {
let cfg = Config::default();
assert_eq!(cfg.backends.len(), 1);
assert_eq!(cfg.providers.len(), 0);
assert_eq!(cfg.default_tier_order.len(), 4);
}
#[test]
fn load_happy_path() {
let toml_text = r#"
[[backends]]
name = "local-ollama"
endpoint = "http://localhost:11434"
model = "mistral:7b"
tiers = ["FAST", "STANDARD"]
[[providers]]
name = "cloud"
command = "newt-cloud-shim"
model = "gpt-4.1-mini"
env_pass = ["CLOUD_TOKEN"]
tiers = ["COMPLEX", "REVIEW"]
default_tier_order = ["FAST", "STANDARD", "COMPLEX", "REVIEW"]
"#;
let mut f = NamedTempFile::new().unwrap();
f.write_all(toml_text.as_bytes()).unwrap();
f.flush().unwrap();
let cfg = Config::load(f.path(), &mut |_| {}).unwrap();
assert_eq!(cfg.backends.len(), 1);
assert_eq!(cfg.backends[0].name, "local-ollama");
assert_eq!(cfg.backends[0].effective_model(), Some("mistral:7b"));
assert_eq!(cfg.backends[0].tiers, vec![Tier::Fast, Tier::Standard]);
assert_eq!(cfg.providers.len(), 1);
assert_eq!(cfg.providers[0].name, "cloud");
assert_eq!(cfg.providers[0].model.as_deref(), Some("gpt-4.1-mini"));
assert_eq!(cfg.providers[0].env_pass, vec!["CLOUD_TOKEN".to_string()]);
}
#[test]
fn missing_file_returns_io_error() {
let result = Config::load(
Path::new("/tmp/newt-does-not-exist-12345.toml"),
&mut |_| {},
);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(err, NewtError::Io(_)),
"expected Io error, got: {err:?}"
);
}
#[test]
fn malformed_toml_returns_config_error() {
let mut f = NamedTempFile::new().unwrap();
f.write_all(b"{{{{").unwrap();
f.flush().unwrap();
let result = Config::load(f.path(), &mut |_| {});
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(err, NewtError::Config(_)),
"expected Config error, got: {err:?}"
);
}
#[serial_test::serial(real_fs)]
#[test]
fn resolve_returns_default_when_no_file() {
let _guard = crate::test_guard::GlobalSettingsGuard::acquire();
let dir = tempfile::tempdir().unwrap();
let saved_config = std::env::var("NEWT_CONFIG").ok();
let saved_home = std::env::var("HOME").ok();
std::env::remove_var("NEWT_CONFIG");
std::env::set_var("HOME", dir.path());
let prev_dir = std::env::current_dir().unwrap();
std::env::set_current_dir(dir.path()).unwrap();
let cfg = Config::resolve(&mut |_| {}).unwrap();
std::env::set_current_dir(prev_dir).unwrap();
if let Some(v) = saved_home {
std::env::set_var("HOME", v);
}
if let Some(v) = saved_config {
std::env::set_var("NEWT_CONFIG", v);
}
assert_eq!(cfg.backends.len(), 1);
assert_eq!(cfg.backends[0].name, "ollama");
assert!(
cfg.is_unconfigured(),
"a resolve with no config anywhere is the unboxing state"
);
}
#[test]
fn default_config_is_unconfigured() {
assert!(
Config::default().is_unconfigured(),
"the struct default's sole backend is the compiled-in fallback"
);
}
#[test]
fn dropin_merge_clears_the_unconfigured_flag() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(
dir.path().join("gpu.toml"),
"endpoint = \"http://gpu:11434\"\n",
)
.unwrap();
let mut cfg = Config::default();
assert!(cfg.is_unconfigured());
merge_for_test(&mut cfg, &[dir.path()]).unwrap();
assert!(
!cfg.is_unconfigured(),
"a successfully merged drop-in is operator configuration"
);
}
#[test]
fn skipped_and_malformed_dropins_do_not_clear_the_unconfigured_flag() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("bad.toml"), "endpoint = 42\n").unwrap();
std::fs::write(dir.path().join("hollow.toml"), "model = \"m\"\n").unwrap();
let mut cfg = Config::default();
merge_for_test(&mut cfg, &[dir.path()]).unwrap();
assert!(
cfg.is_unconfigured(),
"only a drop-in that actually merges counts as configuration"
);
}
#[serial_test::serial(real_fs)] #[test]
fn cli_backend_override_clears_the_unconfigured_flag() {
let _env = crate::test_guard::GlobalSettingsGuard::acquire();
crate::process_env::remove_var("NEWT_PROVIDER");
let mut cfg = Config::default();
BackendOverride {
model: Some("qwen3:32b".into()),
..Default::default()
}
.apply(&mut cfg);
assert!(
!cfg.is_unconfigured(),
"an explicit --backend-* flag is operator configuration"
);
let mut untouched = Config::default();
BackendOverride::default().apply(&mut untouched);
assert!(untouched.is_unconfigured());
}
#[serial_test::serial(real_fs)]
#[test]
fn resolve_reports_unconfigured_only_without_operator_backends() {
let _guard = crate::test_guard::GlobalSettingsGuard::acquire();
let dir = tempfile::tempdir().unwrap();
let saved_config = std::env::var_os("NEWT_CONFIG");
let saved_config_dir = std::env::var_os(NEWT_CONFIG_DIR_ENV);
let saved_home = std::env::var_os("HOME");
std::env::remove_var("NEWT_CONFIG");
std::env::set_var(NEWT_CONFIG_DIR_ENV, dir.path());
std::env::set_var("HOME", dir.path());
let prev_dir = std::env::current_dir().unwrap();
std::env::set_current_dir(dir.path()).unwrap();
let config_toml = dir.path().join("config.toml");
std::fs::write(&config_toml, "providers = []\n").unwrap();
let bare = Config::resolve(&mut |_| {}).unwrap();
std::fs::write(
&config_toml,
"[[backends]]\nname = \"gpu\"\nendpoint = \"http://gpu:8000\"\n",
)
.unwrap();
let inline = Config::resolve(&mut |_| {}).unwrap();
std::fs::write(&config_toml, "providers = []\n").unwrap();
std::fs::create_dir_all(dir.path().join("backends")).unwrap();
std::fs::write(
dir.path().join("backends").join("gpu.toml"),
"endpoint = \"http://gpu:11434\"\n",
)
.unwrap();
let dropin = Config::resolve(&mut |_| {}).unwrap();
std::env::set_current_dir(prev_dir).unwrap();
match saved_home {
Some(v) => std::env::set_var("HOME", v),
None => std::env::remove_var("HOME"),
}
match saved_config_dir {
Some(v) => std::env::set_var(NEWT_CONFIG_DIR_ENV, v),
None => std::env::remove_var(NEWT_CONFIG_DIR_ENV),
}
if let Some(v) = saved_config {
std::env::set_var("NEWT_CONFIG", v);
}
assert!(
bare.is_unconfigured(),
"a backend-less config file is as bare as no file"
);
assert!(!inline.is_unconfigured(), "inline [[backends]] configure");
assert!(!dropin.is_unconfigured(), "a drop-in configures");
}