#![cfg(unix)]
use super::*;
use crate::git;
use crate::layout::Xdg;
use crate::substrate;
use std::fs;
use std::os::unix::fs::PermissionsExt;
use tempfile::TempDir;
pub(crate) fn edge(tmp: &TempDir, exe_dir: Option<PathBuf>) -> 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,
held: Vec::new(),
exe_dir,
path_dirs: Vec::new(),
color: false,
log_level: None,
balls_clock: None,
}
}
pub(crate) fn found(e: &Edge) -> (PathBuf, PathBuf) {
let clone = e.xdg.clone_dir(&e.invocation_path);
substrate::found(&clone.landing(), &clone.store(), &e.xdg, e.exe_dir.as_deref()).unwrap();
(clone.landing(), clone.store())
}
pub(crate) fn g(cwd: &Path, args: &[&str]) {
git::run(cwd, args, None).unwrap();
}
pub(crate) fn head(checkout: &Path) -> String {
git::run(checkout, &["rev-parse", "HEAD"], None).unwrap()
}
pub(crate) fn run_install(e: &Edge, args: &[&str]) -> io::Result<()> {
run(e, &args.iter().map(ToString::to_string).collect::<Vec<_>>())
}
fn side_branch(tmp: &TempDir, landing: &Path) -> &'static str {
let wt = tmp.path().join("side-wt");
g(landing, &["branch", "side"]);
g(landing, &["worktree", "add", "-q", &wt.to_string_lossy(), "side"]);
fs::write(wt.join("config/balls.toml"), "tasks_branch = \"balls/elsewhere\"\n").unwrap();
g(&wt, &["commit", "-q", "-am", "side config"]);
"side"
}
#[test]
fn install_before_prime_is_an_error() {
let tmp = TempDir::new().unwrap();
let err = run_install(&edge(&tmp, None), &["--from", "balls/tasks"]).unwrap_err();
assert!(err.to_string().contains("bl prime"), "{err}");
}
#[test]
fn an_unresolvable_to_ref_is_refused() {
let tmp = TempDir::new().unwrap();
let e = edge(&tmp, None);
found(&e);
let err = run_install(&e, &["--from", "balls/tasks", "--to", "nope"]).unwrap_err();
assert!(err.to_string().contains("--to must name"), "{err}");
}
#[test]
fn an_option_like_from_ref_is_refused() {
let tmp = TempDir::new().unwrap();
let e = edge(&tmp, None);
found(&e);
let err = run_install(&e, &["--from", "--upload-pack=evil"]).unwrap_err();
assert!(err.to_string().contains("refusing"), "{err}");
}
#[test]
fn a_missing_from_ref_is_a_git_error() {
let tmp = TempDir::new().unwrap();
let e = edge(&tmp, None);
found(&e);
let err = run_install(&e, &["--from", "no-such-ref"]).unwrap_err();
assert!(err.to_string().contains("worktree add"), "{err}");
}
#[test]
fn install_seals_a_path_copy_onto_the_landing_tip() {
let tmp = TempDir::new().unwrap();
let e = edge(&tmp, None);
let (landing, _store) = found(&e);
let from = side_branch(&tmp, &landing);
let before = head(&landing);
run_install(&e, &["config", "--from", from, "--to", LANDING_BRANCH]).unwrap();
let cfg = fs::read_to_string(landing.join("config/balls.toml")).unwrap();
assert!(cfg.contains("balls/elsewhere"), "installed config: {cfg}");
assert_ne!(before, head(&landing), "a commit sealed");
let clone = e.xdg.clone_dir(&e.invocation_path);
assert!(!clone.change("install-src").exists());
assert!(!clone.change("install").exists());
}
#[test]
fn an_install_seal_carries_the_checkout_scoped_trailers() {
let tmp = TempDir::new().unwrap();
let e = edge(&tmp, None);
let (landing, _store) = found(&e);
let from = side_branch(&tmp, &landing);
run_install(&e, &["config", "--from", from, "--as", "me"]).unwrap();
let msg = git::run(&landing, &["log", "-1", "--format=%B"], None).unwrap();
let md = crate::message::parse(&msg).unwrap();
assert_eq!(md["bl-protocol"], ["1"], "{msg}");
assert_eq!(md["bl-op"], ["install"], "{msg}");
assert_eq!(md["bl-actor"], ["me"], "{msg}");
assert!(!md.contains_key("bl-id"), "{msg}");
}
#[test]
fn reinstalling_identical_content_converges_on_the_tip() {
let tmp = TempDir::new().unwrap();
let e = edge(&tmp, None);
let (landing, _store) = found(&e);
let from = side_branch(&tmp, &landing);
run_install(&e, &["config", "--from", from]).unwrap(); let sealed = head(&landing);
run_install(&e, &["config", "--from", from]).unwrap();
assert_eq!(sealed, head(&landing), "the no-op seal lands no empty commit");
}
#[test]
fn install_can_target_the_configured_store_branch() {
let tmp = TempDir::new().unwrap();
let e = edge(&tmp, None);
let (_landing, store) = found(&e);
let before = head(&store);
run_install(&e, &["tasks", "--from", crate::DEFAULT_TASKS_BRANCH, "--to", crate::DEFAULT_TASKS_BRANCH]).unwrap();
assert_eq!(before, head(&store));
}
#[test]
fn a_failing_pre_plugin_aborts_the_seal_and_cleans_up() {
let tmp = TempDir::new().unwrap();
let bin = tmp.path().join("bin");
fs::create_dir_all(&bin).unwrap();
let plugin = bin.join("bl-tracker");
fs::write(
&plugin,
"#!/bin/sh\nif [ \"$1\" = protocol ]; then printf '{\"protocol\":[1],\"ops\":[\"install\"]}'; exit 0; fi\nexit 1\n",
)
.unwrap();
fs::set_permissions(&plugin, fs::Permissions::from_mode(0o755)).unwrap();
let e = edge(&tmp, Some(bin));
let (landing, _store) = found(&e);
let before = head(&landing);
let err = run_install(&e, &["config", "--from", LANDING_BRANCH]).unwrap_err();
assert!(err.to_string().contains("tracker"), "{err}");
assert_eq!(before, head(&landing), "the abort unwound — nothing sealed");
let clone = e.xdg.clone_dir(&e.invocation_path);
assert!(!clone.change("install-src").exists(), "the source worktree is torn down on abort");
}