mod reads;
use super::*;
use crate::test_support::FakeFs;
use std::path::PathBuf;
use std::sync::Mutex;
struct FakeRunner {
outcome: BzOutcome,
providers: Vec<ProviderRow>,
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(),
},
providers: Vec::new(),
log: Mutex::new(Vec::new()),
}
}
fn listing(names: &[&str]) -> Self {
Self {
providers: names
.iter()
.map(|n| ProviderRow {
name: (*n).to_string(),
protocol: "openai_chat".to_owned(),
auth: "none".to_owned(),
credential: "not required".to_owned(),
effort: true,
priority: true,
tools: Some(true),
device: String::new(),
})
.collect(),
..Self::ok()
}
}
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 providers(&self) -> Vec<ProviderRow> {
self.log().push("providers".into());
self.providers.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 set_draft_replaces_the_buffer() {
let mut ed = loaded(&FakeFs::default());
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 refresh_follows_disk_only_while_the_draft_is_pristine() {
let fs = FakeFs::seed(&cfg(), b"A");
let mut ed = loaded(&fs);
fs.map().insert(cfg(), b"outside = 1".to_vec());
assert!(ed.refresh(&fs).unwrap());
assert_eq!(ed.draft(), "outside = 1");
ed.set_draft("mine".into());
assert_eq!(ed.apply(&FakeRunner::ok(), &fs), Applied::Ok);
ed.set_draft("unsaved".into());
fs.map().insert(cfg(), b"theirs".to_vec());
assert!(!ed.refresh(&fs).unwrap());
assert_eq!(ed.draft(), "unsaved");
assert_eq!(ed.apply(&FakeRunner::ok(), &fs), Applied::Conflict);
}