use super::*;
use crate::test_support::FakeFs;
use crate::xdg::{Env, Os};
use std::path::PathBuf;
use std::sync::Mutex;
struct FakeRunner {
outcome: BzOutcome,
log: Mutex<Vec<String>>,
}
impl FakeRunner {
fn ok() -> Self {
Self::with(true, "", "")
}
fn with(success: bool, stdout: &str, stderr: &str) -> Self {
Self {
outcome: BzOutcome {
success,
stdout: stdout.into(),
stderr: stderr.into(),
},
log: Mutex::new(Vec::new()),
}
}
fn log(&self) -> std::sync::MutexGuard<'_, Vec<String>> {
self.log
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
}
impl BzRunner for FakeRunner {
fn dump_config_at(&self, _config: &Path) -> BzOutcome {
self.log().push("at".into());
self.outcome.clone()
}
fn dump_config_effective(&self) -> BzOutcome {
self.log().push("effective".into());
self.outcome.clone()
}
fn list_models(&self, provider: &str) -> BzOutcome {
self.log().push(format!("list:{provider}"));
self.outcome.clone()
}
}
fn cfg() -> PathBuf {
PathBuf::from("/cfg/brazen/config.toml")
}
fn paths() -> BrazenPaths {
BrazenPaths {
config: cfg(),
credentials_dir: PathBuf::from("/creds"),
models_cache_dir: PathBuf::from("/cache/models"),
}
}
fn loaded(fs: &FakeFs) -> BrazenEditor {
BrazenEditor::load(paths(), fs).unwrap()
}
#[test]
fn load_missing_file_is_empty_draft() {
let ed = loaded(&FakeFs::default());
assert_eq!(ed.draft(), "");
}
#[test]
fn load_reads_existing_bytes_into_draft() {
let ed = loaded(&FakeFs::seed(&cfg(), b"name = \"openai\"\n"));
assert_eq!(ed.draft(), "name = \"openai\"\n");
}
#[test]
fn draft_mut_and_set_draft_edit_the_buffer() {
let mut ed = loaded(&FakeFs::default());
ed.draft_mut().push_str("x = 1");
assert_eq!(ed.draft(), "x = 1");
ed.set_draft("y = 2".into());
assert_eq!(ed.draft(), "y = 2");
}
#[test]
fn apply_ok_validates_guards_and_renames() {
let fs = FakeFs::seed(&cfg(), b"A");
let mut ed = loaded(&fs);
ed.set_draft("B".into());
let runner = FakeRunner::ok();
assert_eq!(ed.apply(&runner, &fs), Applied::Ok);
assert_eq!(fs.get(&cfg()), Some(b"B".to_vec()));
ed.set_draft("C".into());
assert_eq!(ed.apply(&runner, &fs), Applied::Ok);
assert_eq!(fs.get(&cfg()), Some(b"C".to_vec()));
}
#[test]
fn apply_rejects_on_nonzero_bz_exit_keeping_draft() {
let fs = FakeFs::seed(&cfg(), b"A");
let mut ed = loaded(&fs);
ed.set_draft("bad".into());
let runner = FakeRunner::with(false, "", "MalformedFile at line 3");
assert_eq!(
ed.apply(&runner, &fs),
Applied::Rejected {
stderr: "MalformedFile at line 3".into()
}
);
assert_eq!(ed.draft(), "bad");
assert_eq!(fs.get(&cfg()), Some(b"A".to_vec()));
}
#[test]
fn apply_refuses_on_concurrent_edit_with_conflict() {
let fs = FakeFs::seed(&cfg(), b"A");
let mut ed = loaded(&fs);
ed.set_draft("B".into());
fs.map().insert(cfg(), b"C".to_vec());
assert_eq!(ed.apply(&FakeRunner::ok(), &fs), Applied::Conflict);
assert_eq!(fs.get(&cfg()), Some(b"C".to_vec()));
}
#[test]
fn apply_maps_a_filesystem_error_to_io() {
let fs = FakeFs {
fail_write: true,
..FakeFs::default()
};
let mut ed = loaded(&fs);
ed.set_draft("B".into());
assert!(matches!(
ed.apply(&FakeRunner::ok(), &fs),
Applied::Io { .. }
));
}
#[test]
fn reload_recovers_the_on_disk_content_and_hash() {
let fs = FakeFs::seed(&cfg(), b"A");
let mut ed = loaded(&fs);
ed.set_draft("stale".into());
fs.map().insert(cfg(), b"D".to_vec());
ed.reload(&fs).unwrap();
assert_eq!(ed.draft(), "D");
ed.set_draft("E".into());
assert_eq!(ed.apply(&FakeRunner::ok(), &fs), Applied::Ok);
}
#[test]
fn effective_runs_dump_config_against_real_env() {
let ed = loaded(&FakeFs::default());
let runner = FakeRunner::with(true, "merged config", "");
let out = ed.effective(&runner);
assert_eq!(out.stdout, "merged config");
assert_eq!(*runner.log(), vec!["effective".to_string()]);
}
#[test]
fn credential_presence_scans_providers_and_stats_files() {
let text = "[[providers]]\nname = \"openai\"\nurl = \"x\"\n\
name = \"anthropic\"\nname = \"openai\"\nnotname = \"z\"\nname = bare\n";
let fs = FakeFs::seed(&cfg(), text.as_bytes());
fs.map()
.insert(PathBuf::from("/creds/openai.json"), b"{}".to_vec());
let ed = loaded(&fs);
assert_eq!(
ed.credential_presence(&fs),
vec![
("openai".to_string(), true),
("anthropic".to_string(), false),
]
);
}
#[test]
fn model_cache_reads_present_and_absent() {
let fs = FakeFs::seed(
&PathBuf::from("/cache/models/openai.json"),
b"[{\"id\":\"gpt\"}]",
);
let ed = loaded(&fs);
assert_eq!(
ed.model_cache("openai", &fs).unwrap().as_deref(),
Some("[{\"id\":\"gpt\"}]")
);
assert_eq!(ed.model_cache("gemini", &fs).unwrap(), None);
}
#[test]
fn refresh_models_dispatches_list_models() {
let ed = loaded(&FakeFs::default());
let runner = FakeRunner::ok();
let _ = ed.refresh_models("openai", &runner);
assert_eq!(*runner.log(), vec!["list:openai".to_string()]);
}
#[test]
fn built_in_rows_hint_is_a_static_line() {
assert!(BUILT_IN_ROWS_HINT.contains("built-in"));
}
#[test]
fn brazen_paths_resolve_folds_config_creds_and_cache() {
let env = Env::from_pairs([
("HOME", "/home/u"),
("XDG_CONFIG_HOME", "/home/u/.config"),
("XDG_DATA_HOME", "/home/u/.local/share"),
("XDG_CACHE_HOME", "/home/u/.cache"),
]);
let p = BrazenPaths::resolve(&env, Os::Linux);
assert_eq!(
p.config,
PathBuf::from("/home/u/.config/brazen/config.toml")
);
assert_eq!(
p.credentials_dir,
PathBuf::from("/home/u/.local/share/brazen/credentials")
);
assert_eq!(
p.models_cache_dir,
PathBuf::from("/home/u/.cache/brazen/models")
);
}