use super::*;
use crate::layout::Xdg;
use crate::substrate;
use std::fs;
use tempfile::TempDir;
fn edge(tmp: &TempDir) -> Edge {
Edge {
xdg: Xdg::with(tmp.path(), None, Some(&tmp.path().join("state").to_string_lossy())),
invocation_path: tmp.path().join("proj"),
default_actor: "tester".into(),
depth: 0,
exe_dir: None,
path_dirs: Vec::new(),
color: false,
log_level: None,
}
}
fn founded(e: &Edge) -> CloneDir {
let clone = e.xdg.clone_dir(&e.invocation_path);
substrate::found(&clone.landing(), &clone.store(), &e.xdg, None).unwrap();
clone
}
fn args(a: &[&str]) -> Vec<String> {
a.iter().map(ToString::to_string).collect()
}
fn user_file(e: &Edge, name: &str, body: &str) {
let path = e.xdg.user_config().with_file_name(name);
fs::create_dir_all(path.parent().unwrap()).unwrap();
fs::write(path, body).unwrap();
}
fn landing_file(clone: &CloneDir, name: &str, body: &str) {
fs::write(clone.landing().join("config").join(name), body).unwrap();
}
fn res(e: &Edge, clone: &CloneDir, key: &str) -> (String, String) {
let r = prov::resolve(e, clone, &Key::parse(key, &Hooks::default()).unwrap()).unwrap();
(r.value, r.layer)
}
#[test]
fn the_key_namespace_accepts_each_home_and_rejects_the_rest() {
let none = Hooks::default();
for ok in ["task-remote", "task-branch", "log-level", "claim.pre", "close.post", "show", "list"] {
assert!(Key::parse(ok, &none).is_ok(), "{ok} should parse");
}
for bad in ["frob", "conf.pre", "show.pre", "list.post", "claim.mid", "claim"] {
let err = Key::parse(bad, &none).unwrap_err().to_string();
assert!(err.contains("unknown key"), "{bad}: {err}");
}
let wired = Hooks::parse("[hooks]\n\"drop.post\" = [\"bl-delivery\"]\n").unwrap();
assert!(Key::parse("drop.post", &wired).is_ok(), "a wired retired-verb key parses");
assert!(Key::parse("drop.post", &none).unwrap_err().to_string().contains("unknown key"));
}
#[test]
fn conf_requires_a_founded_checkout() {
let tmp = TempDir::new().unwrap();
let err = run(&edge(&tmp), &[]).unwrap_err().to_string();
assert!(err.contains("run `bl prime` first"), "{err}");
}
#[test]
fn a_scalar_resolves_innermost_wins_and_names_the_layer() {
let tmp = TempDir::new().unwrap();
let e = edge(&tmp);
let clone = founded(&e);
assert_eq!(res(&e, &clone, "task-branch"), ("balls/tasks".into(), "landing".into()));
user_file(&e, "config.toml", "tasks_branch = \"balls/x\"\n");
assert_eq!(res(&e, &clone, "task-branch"), ("balls/x".into(), "xdg".into()));
user_file(&e, "config.toml", "");
landing_file(&clone, "balls.toml", "");
assert_eq!(res(&e, &clone, "task-branch"), ("balls/tasks".into(), "default".into()));
}
#[test]
fn the_log_level_cli_tier_outranks_every_file_layer() {
let tmp = TempDir::new().unwrap();
let e = edge(&tmp);
let clone = founded(&e);
assert_eq!(res(&e, &clone, "log-level"), ("info".into(), "landing".into()));
let cli = Edge { log_level: Some("debug".into()), ..e };
assert_eq!(res(&cli, &clone, "log-level"), ("debug".into(), "cli".into()));
}
#[test]
fn task_remote_reads_the_durable_ladder_and_shows_stealth() {
let tmp = TempDir::new().unwrap();
let e = edge(&tmp);
let clone = founded(&e);
assert_eq!(res(&e, &clone, "task-remote"), ("(none)".into(), "stealth".into()));
fs::create_dir_all(&e.invocation_path).unwrap();
crate::git::run(&e.invocation_path, &["init", "-q"], None).unwrap();
crate::git::run(&e.invocation_path, &["remote", "add", "origin", "git@hub:o"], None).unwrap();
assert_eq!(res(&e, &clone, "task-remote"), ("git@hub:o".into(), "origin".into()));
user_file(&e, "config.toml", "remote = \"git@hub:x\"\n");
assert_eq!(res(&e, &clone, "task-remote"), ("git@hub:x".into(), "xdg (global)".into()));
fs::write(clone.binding(), "remote = \"git@hub:b\"\n").unwrap();
assert_eq!(res(&e, &clone, "task-remote"), ("git@hub:b".into(), "binding".into()));
let balls_toml = clone.landing().join("config").join("balls.toml");
let mut body = fs::read_to_string(&balls_toml).unwrap_or_default();
body.push_str("task_remote = \"none\"\n");
fs::write(&balls_toml, body).unwrap();
assert_eq!(res(&e, &clone, "task-remote"), ("(none)".into(), "landing".into()));
}
#[test]
fn a_hooks_key_resolves_the_composed_list_and_names_its_layers() {
let tmp = TempDir::new().unwrap();
let e = edge(&tmp);
let clone = founded(&e);
landing_file(&clone, "plugins.toml", "[hooks]\n\"close.pre\" = [\"x\", \"y\"]\n");
assert_eq!(res(&e, &clone, "close.pre"), ("x, y".into(), "landing".into()));
user_file(&e, "plugins.toml", "[hooks]\n\"close.pre_append\" = [\"z\"]\n\"claim.post\" = [\"w\"]\n");
assert_eq!(res(&e, &clone, "close.pre"), ("x, y, z".into(), "landing+xdg".into()));
assert_eq!(res(&e, &clone, "claim.post"), ("w".into(), "xdg".into()));
assert_eq!(res(&e, &clone, "update.pre"), ("(none)".into(), "default".into()));
}
#[test]
fn a_non_table_hooks_entry_mentions_nothing() {
let tmp = TempDir::new().unwrap();
let e = edge(&tmp);
let clone = founded(&e);
landing_file(&clone, "plugins.toml", "hooks = \"nope\"\n");
assert_eq!(res(&e, &clone, "close.pre"), ("(none)".into(), "default".into()));
}
#[test]
fn the_dump_renders_scalars_hook_rows_and_paths() {
let tmp = TempDir::new().unwrap();
let e = edge(&tmp);
let clone = founded(&e);
landing_file(&clone, "plugins.toml", "[hooks]\n\"close.pre\" = [\"x\"]\n\"claim.post\" = []\n");
run(&e, &[]).unwrap();
}
#[test]
fn the_dump_grows_an_unbound_row_per_dangling_name_with_its_hint() {
let tmp = TempDir::new().unwrap();
let e = edge(&tmp);
let clone = founded(&e);
landing_file(
&clone,
"plugins.toml",
"[hooks]\n\"close.pre\" = [\"hinted\", \"hintless\"]\n[source]\nhinted = \"cargo install hinted\"\n",
);
run(&e, &[]).unwrap();
}
#[test]
fn a_single_key_read_resolves_and_rejects_stray_values() {
let tmp = TempDir::new().unwrap();
let e = edge(&tmp);
founded(&e);
run(&e, &args(&["task-branch"])).unwrap();
let err = run(&e, &args(&["bogus"])).unwrap_err().to_string();
assert!(err.contains("unknown key"), "{err}");
let err = run(&e, &args(&["task-branch", "extra"])).unwrap_err().to_string();
assert!(err.contains("takes no value on a read"), "{err}");
}
#[test]
fn the_conf_verb_dispatches_through_the_front_door() {
let tmp = TempDir::new().unwrap();
let e = edge(&tmp);
assert_eq!(crate::run(&e, &args(&["conf"])), 1); founded(&e);
assert_eq!(crate::run(&e, &args(&["conf"])), 0);
}