use super::*;
use crate::edge::Edge;
use crate::git;
use crate::layout::Xdg;
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use tempfile::TempDir;
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,
exe_dir,
path_dirs: Vec::new(),
color: false,
log_level: None,
}
}
fn found(e: &Edge) -> (PathBuf, PathBuf) {
let clone = e.xdg.clone_dir(&e.invocation_path);
crate::substrate::found(&clone.landing(), &clone.store(), &e.xdg, e.exe_dir.as_deref()).unwrap();
(clone.landing(), clone.store())
}
fn center(dir: &Path, tasks_branch: &str, plugins: &str) -> String {
let repo = dir.join("center");
fs::create_dir_all(&repo).unwrap();
g(&repo, &["init", "-q", "-b", LANDING_BRANCH]);
g(&repo, &["config", "user.name", "c"]);
g(&repo, &["config", "user.email", "c@c"]);
let config = repo.join("config");
fs::create_dir_all(&config).unwrap();
fs::write(config.join("balls.toml"), format!("tasks_branch = \"{tasks_branch}\"\n")).unwrap();
fs::write(config.join("plugins.toml"), plugins).unwrap();
g(&repo, &["add", "-A"]);
g(&repo, &["commit", "-q", "-m", "center config"]);
repo.to_string_lossy().into_owned()
}
fn g(cwd: &Path, args: &[&str]) {
git::run(cwd, args, None).unwrap();
}
fn sim_fetch(landing: &Path, center: &str) {
g(landing, &["fetch", center, LANDING_BRANCH]);
}
fn head(landing: &Path) -> String {
git::run(landing, &["rev-parse", "HEAD"], None).unwrap()
}
fn plugin(dir: &Path, name: &str, ops: &str) {
let path = dir.join(name);
let body = format!(
"#!/bin/sh\nif [ \"$1\" = protocol ]; then printf '{{\"protocol\":[1],\"ops\":{ops}}}'; fi\n"
);
fs::write(&path, body).unwrap();
fs::set_permissions(&path, fs::Permissions::from_mode(0o755)).unwrap();
}
const NO_HOOKS: &str = "[hooks]\n";
#[test]
fn install_local_mirrors_the_fetched_config_into_the_landing_and_commits() {
let tmp = TempDir::new().unwrap();
let e = edge(&tmp, None);
let (landing, _store) = found(&e);
let before = head(&landing);
sim_fetch(&landing, ¢er(tmp.path(), "balls/shared", NO_HOOKS));
install_local(&e, &landing).unwrap();
let cfg = fs::read_to_string(landing.join("config/balls.toml")).unwrap();
assert!(cfg.contains("balls/shared"), "adopted config: {cfg}");
assert_ne!(before, head(&landing), "a commit landed");
}
#[test]
fn re_adopting_identical_config_skips_the_commit() {
let tmp = TempDir::new().unwrap();
let e = edge(&tmp, None);
let (landing, _store) = found(&e);
let c = center(tmp.path(), "balls/shared", NO_HOOKS);
sim_fetch(&landing, &c);
install_local(&e, &landing).unwrap();
let after_first = head(&landing);
sim_fetch(&landing, &c);
install_local(&e, &landing).unwrap(); assert_eq!(after_first, head(&landing), "idempotent re-adopt skips the commit");
}
#[test]
fn install_local_binds_a_referenced_plugin_whose_binary_is_present() {
let tmp = TempDir::new().unwrap();
let bin = tmp.path().join("bin");
fs::create_dir_all(&bin).unwrap();
plugin(&bin, "tracker", "[\"install\"]");
let e = edge(&tmp, Some(bin.clone()));
let (landing, _store) = found(&e);
sim_fetch(&landing, ¢er(tmp.path(), "balls/shared", "[hooks]\n\"install.pre\" = [\"tracker\"]\n"));
install_local(&e, &landing).unwrap();
let link = landing.join("config/plugins/bin/tracker");
assert_eq!(fs::read_link(&link).unwrap(), bin.join("tracker"));
}
#[test]
fn install_local_refuses_a_referenced_plugin_the_binary_cannot_serve() {
let tmp = TempDir::new().unwrap();
let bin = tmp.path().join("bin");
fs::create_dir_all(&bin).unwrap();
plugin(&bin, "tracker", "[\"install\"]"); let e = edge(&tmp, Some(bin));
let (landing, _store) = found(&e);
sim_fetch(&landing, ¢er(tmp.path(), "balls/shared", "[hooks]\n\"sync.pre\" = [\"tracker\"]\n"));
let err = install_local(&e, &landing).unwrap_err();
assert!(err.to_string().contains("sync"), "{err}");
}
#[test]
fn adopt_without_an_install_pre_plugin_is_an_error() {
let tmp = TempDir::new().unwrap();
let e = edge(&tmp, None);
let (landing, store) = found(&e);
let c = center(tmp.path(), "balls/shared", NO_HOOKS);
let err = adopt(&e, &landing, &store, "me", &c).unwrap_err();
assert!(err.to_string().contains("install.pre"), "{err}");
}
#[test]
fn adopt_runs_the_install_pre_chain_then_the_local_copy() {
let tmp = TempDir::new().unwrap();
let bin = tmp.path().join("bin");
fs::create_dir_all(&bin).unwrap();
plugin(&bin, "tracker", "[\"install\"]");
let e = edge(&tmp, Some(bin));
let (landing, store) = found(&e);
let c = center(tmp.path(), "balls/shared", NO_HOOKS);
let err = adopt(&e, &landing, &store, "me", &c).unwrap_err();
assert!(!err.to_string().contains("install.pre"), "the chain ran: {err}");
}