use crate::cli::init::config_file;
use crate::cli::init::presets;
#[test]
fn openrouter_render_parses_and_carries_literal_api_key_reference() {
let preset = presets::preset("openrouter").expect("openrouter");
let body = super::support::render_one(preset, "m", "http://e/v1");
let value: toml::Value = toml::from_str(&body).expect("renders as valid TOML");
let entries = value
.get("llm")
.and_then(|v| v.as_array())
.expect("[[llm]] is an array");
assert_eq!(entries.len(), 1, "exactly one [[llm]] entry");
let entry = &entries[0];
assert_eq!(
entry.get("api_key").and_then(|v| v.as_str()),
Some("${OPENROUTER_API_KEY}"),
"the api_key is the literal variable name, not an expansion"
);
}
#[test]
fn local_render_omits_api_key_and_timeout_secs_but_openrouter_has_both() {
let local = presets::preset("local").expect("local");
let local_body = super::support::render_one(local, "m", "http://e/v1");
let local_value: toml::Value = toml::from_str(&local_body).expect("local parses");
let local_entry = &local_value
.get("llm")
.and_then(|v| v.as_array())
.expect("array")[0];
assert!(
local_entry.get("api_key").is_none(),
"local preset does not write api_key, got {local_body:?}"
);
assert!(
local_entry.get("timeout_secs").is_none(),
"local preset does not write timeout_secs, got {local_body:?}"
);
let openrouter = presets::preset("openrouter").expect("openrouter");
let or_body = super::support::render_one(openrouter, "m", "http://e/v1");
let or_value: toml::Value = toml::from_str(&or_body).expect("openrouter parses");
let or_entry = &or_value
.get("llm")
.and_then(|v| v.as_array())
.expect("array")[0];
assert!(
or_entry.get("api_key").is_some(),
"openrouter preset writes api_key, got {or_body:?}"
);
assert!(
or_entry.get("timeout_secs").is_some(),
"openrouter preset writes timeout_secs, got {or_body:?}"
);
}
#[test]
fn rendered_config_loads_through_config_load() {
let preset = presets::preset("local").expect("local");
let body = super::support::render_one(preset, "m", "http://e/v1");
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("drep.toml");
std::fs::write(&path, body).expect("write");
let loaded = crate::config::load(&path).expect("config loads");
assert_eq!(loaded.llm.len(), 1);
let primary = *loaded.providers().first().expect("a provider is written");
assert_eq!(primary.model.as_deref(), Some("m"));
assert_eq!(primary.endpoint.as_deref(), Some("http://e/v1"));
}
#[test]
fn render_names_the_api_key_env_var_rather_than_the_secret() {
let preset = presets::preset("openrouter").expect("openrouter");
let body = super::support::render_one(preset, "m", "http://e/v1");
assert!(
body.contains(r#"api_key = "${OPENROUTER_API_KEY}""#),
"the file is meant to be committed, so it names the variable: {body:?}"
);
let value: toml::Value = toml::from_str(&body).expect("the rendered file parses");
let entry = &value.get("llm").and_then(|v| v.as_array()).expect("array")[0];
assert_eq!(
entry.get("api_key").and_then(|v| v.as_str()),
Some("${OPENROUTER_API_KEY}"),
"unexpanded in the file itself; `config::load` is what substitutes it"
);
}
#[test]
fn render_escapes_quote_and_backslash_in_model_names() {
let preset = presets::preset("local").expect("local");
let nasty = "a\"b\\c";
let body = super::support::render_one(preset, nasty, "http://e/v1");
let value: toml::Value = toml::from_str(&body).expect("still parses as TOML");
let entry = &value.get("llm").and_then(|v| v.as_array()).expect("array")[0];
assert_eq!(
entry.get("model").and_then(|v| v.as_str()),
Some(nasty),
"round-tripped model equals input"
);
}
#[test]
fn write_refuses_to_overwrite_existing_drep_toml_without_force() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("drep.toml");
let original = "original contents\n";
std::fs::write(&path, original).expect("seed");
let result = config_file::write(dir.path(), "new body\n", false);
let err = result.expect_err("refuses to overwrite");
let msg = format!("{err:#}");
assert!(
msg.contains(path.display().to_string().as_str()),
"error names the path; got: {msg}"
);
assert!(
msg.contains("--force"),
"error mentions --force; got: {msg}"
);
let unchanged = std::fs::read_to_string(&path).expect("read");
assert_eq!(unchanged, original, "file contents unchanged on refusal");
}
#[test]
fn write_with_force_replaces_contents() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("drep.toml");
std::fs::write(&path, "original\n").expect("seed");
let returned = config_file::write(dir.path(), "new body\n", true).expect("force ok");
assert_eq!(returned, path);
let after = std::fs::read_to_string(&path).expect("read");
assert_eq!(after, "new body\n");
}
#[cfg(unix)]
#[test]
fn force_replaces_a_symlink_without_writing_through_it() {
let dir = tempfile::tempdir().expect("tempdir");
let target = dir.path().join("unrelated.toml");
std::fs::write(&target, "must survive\n").expect("target");
let config = dir.path().join("drep.toml");
std::os::unix::fs::symlink(&target, &config).expect("symlink");
config_file::write(dir.path(), "new body\n", true).expect("force replaces config path");
assert_eq!(
std::fs::read_to_string(&target).expect("target remains"),
"must survive\n"
);
assert_eq!(
std::fs::read_to_string(&config).expect("new config"),
"new body\n"
);
assert!(
!std::fs::symlink_metadata(&config)
.expect("metadata")
.file_type()
.is_symlink()
);
}
#[test]
fn a_rendered_config_always_parses_whatever_the_model_and_endpoint_contain() {
let preset = crate::cli::init::presets::preset("openrouter").expect("preset");
let nasty = [
"plain",
"with \"quote\"",
"with\\backslash",
"with\nnewline",
"with\rcarriage",
"with\ttab",
"with\u{7f}delete",
"with\u{1}control",
"unicode-\u{e9}\u{4e2d}",
];
for value in nasty {
for (model, endpoint) in [(value, "http://h/v1"), ("m", value)] {
let body = super::support::render_one(preset, model, endpoint);
let parsed: toml::Value = toml::from_str(&body).unwrap_or_else(|e| {
panic!("super::support::render_one({model:?}, {endpoint:?}) is not TOML: {e}")
});
let entry = &parsed["llm"].as_array().expect("array")[0];
assert_eq!(
entry["model"].as_str(),
Some(model),
"the model must round-trip exactly"
);
assert_eq!(
entry["endpoint"].as_str(),
Some(endpoint),
"and so must the endpoint"
);
}
}
}