use super::*;
use crate::git::run as git;
use crate::hooks::Hooks;
use crate::layout::Xdg;
use crate::DEFAULT_TASKS_BRANCH;
use tempfile::TempDir;
fn paths(tmp: &TempDir) -> (std::path::PathBuf, std::path::PathBuf) {
(tmp.path().join("config"), tmp.path().join("tasks"))
}
fn xdg(tmp: &TempDir) -> Xdg {
Xdg::with(tmp.path(), Some(&tmp.path().join("cfg").to_string_lossy()), Some(&tmp.path().join("st").to_string_lossy()))
}
fn exe_dir(tmp: &TempDir, names: &[&str]) -> std::path::PathBuf {
let dir = tmp.path().join("bin");
fs::create_dir_all(&dir).unwrap();
for name in names {
fs::write(dir.join(name), "#!/bin/sh\n").unwrap();
}
dir
}
#[test]
fn found_landing_makes_the_config_branch_with_seeded_config_and_no_store() {
let tmp = TempDir::new().unwrap();
let (landing, store) = paths(&tmp);
found_landing(&landing, &xdg(&tmp), None, "tester").unwrap();
assert_eq!(git(&landing, &["rev-parse", "--abbrev-ref", "HEAD"], None).unwrap().trim(), LANDING_BRANCH);
assert!(landing.join("config").join("balls.toml").is_file());
assert!(landing.join("config").join("plugins.toml").is_file());
assert!(landing.join(".gitignore").is_file());
let log = git(&landing, &["log", "--oneline"], None).unwrap();
assert_eq!(log.lines().count(), 1);
assert!(log.contains("balls: found"));
assert!(!store.exists());
assert!(git(&landing, &["show-ref", "--verify", "--quiet", &format!("refs/heads/{DEFAULT_TASKS_BRANCH}")], None).is_err());
}
#[test]
fn materialize_founds_an_orphan_store_when_the_branch_is_absent() {
let tmp = TempDir::new().unwrap();
let (landing, store) = paths(&tmp);
found_landing(&landing, &xdg(&tmp), None, "tester").unwrap();
materialize(&landing, &store, DEFAULT_TASKS_BRANCH, "tester").unwrap();
assert_eq!(git(&store, &["rev-parse", "--abbrev-ref", "HEAD"], None).unwrap().trim(), DEFAULT_TASKS_BRANCH);
assert!(store.join("tasks").join(".gitkeep").is_file());
assert_eq!(git(&store, &["log", "--oneline"], None).unwrap().lines().count(), 1);
assert!(git(&landing, &["merge-base", LANDING_BRANCH, DEFAULT_TASKS_BRANCH], None).is_err());
}
#[test]
fn materialize_checks_out_an_existing_branch_instead_of_founding() {
let tmp = TempDir::new().unwrap();
let (landing, store) = paths(&tmp);
found_landing(&landing, &xdg(&tmp), None, "tester").unwrap();
let head = git(&landing, &["rev-parse", "HEAD"], None).unwrap().trim().to_string();
git(&landing, &["branch", DEFAULT_TASKS_BRANCH, &head], None).unwrap();
materialize(&landing, &store, DEFAULT_TASKS_BRANCH, "tester").unwrap();
assert_eq!(git(&store, &["rev-parse", "HEAD"], None).unwrap().trim(), head);
}
#[test]
fn materialize_is_idempotent_once_the_store_exists() {
let tmp = TempDir::new().unwrap();
let (landing, store) = paths(&tmp);
found_landing(&landing, &xdg(&tmp), None, "tester").unwrap();
materialize(&landing, &store, DEFAULT_TASKS_BRANCH, "tester").unwrap();
let before = git(&store, &["rev-parse", "HEAD"], None).unwrap();
materialize(&landing, &store, DEFAULT_TASKS_BRANCH, "tester").unwrap(); assert_eq!(git(&store, &["rev-parse", "HEAD"], None).unwrap(), before);
}
#[test]
fn materialize_switches_an_existing_store_onto_a_repointed_branch_ref() {
let tmp = TempDir::new().unwrap();
let (landing, store) = paths(&tmp);
found_landing(&landing, &xdg(&tmp), None, "tester").unwrap();
materialize(&landing, &store, DEFAULT_TASKS_BRANCH, "tester").unwrap();
let head = git(&store, &["rev-parse", "HEAD"], None).unwrap().trim().to_string();
git(&landing, &["branch", "balls/other", &head], None).unwrap();
materialize(&landing, &store, "balls/other", "tester").unwrap();
assert_eq!(git(&store, &["rev-parse", "--abbrev-ref", "HEAD"], None).unwrap().trim(), "balls/other");
assert_eq!(git(&store, &["rev-parse", "HEAD"], None).unwrap().trim(), head);
}
#[test]
fn materialize_founds_the_repointed_branch_when_no_ref_exists() {
let tmp = TempDir::new().unwrap();
let (landing, store) = paths(&tmp);
found_landing(&landing, &xdg(&tmp), None, "tester").unwrap();
materialize(&landing, &store, DEFAULT_TASKS_BRANCH, "tester").unwrap();
materialize(&landing, &store, "balls/fresh", "tester").unwrap();
assert_eq!(git(&store, &["rev-parse", "--abbrev-ref", "HEAD"], None).unwrap().trim(), "balls/fresh");
assert!(store.join("tasks").join(".gitkeep").is_file());
assert_eq!(git(&store, &["log", "--oneline"], None).unwrap().lines().count(), 1);
assert!(git(&store, &["rev-parse", "--verify", "HEAD^"], None).is_err());
}
#[test]
fn the_founding_seeds_carry_the_checkout_scoped_trailers() {
let tmp = TempDir::new().unwrap();
let (landing, store) = paths(&tmp);
found_landing(&landing, &xdg(&tmp), None, "tester").unwrap();
materialize(&landing, &store, DEFAULT_TASKS_BRANCH, "tester").unwrap();
for checkout in [&landing, &store] {
let msg = git(checkout, &["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"], ["prime"], "{msg}");
assert_eq!(md["bl-actor"], ["tester"], "{msg}");
assert!(!md.contains_key("bl-id"), "{msg}");
}
}
#[test]
fn found_landing_without_any_plugin_binary_seeds_an_empty_schedule() {
let tmp = TempDir::new().unwrap();
let (landing, _store) = paths(&tmp);
found_landing(&landing, &xdg(&tmp), None, "tester").unwrap();
let hooks = Hooks::load(&landing).unwrap();
assert!(hooks.names("prime", "pre").is_empty());
assert!(!landing.join("config/plugins/bin").exists());
}
#[test]
fn found_landing_with_the_shipped_binaries_keeps_and_binds_them() {
let tmp = TempDir::new().unwrap();
let (landing, _store) = paths(&tmp);
let exe = exe_dir(&tmp, &["tracker", "bl-delivery"]);
found_landing(&landing, &xdg(&tmp), Some(&exe), "tester").unwrap();
let hooks = Hooks::load(&landing).unwrap();
assert_eq!(hooks.names("create", "post"), ["tracker"]);
assert_eq!(hooks.names("close", "post"), ["bl-delivery", "tracker"]);
assert!(landing.join("config/plugins/bin/tracker").symlink_metadata().is_ok());
assert!(landing.join("config/plugins/bin/bl-delivery").symlink_metadata().is_ok());
let tracked = git(&landing, &["ls-files", "config"], None).unwrap();
assert!(tracked.contains("config/plugins.toml"));
assert!(!tracked.contains("bin/tracker"), "bin/ must not be committed");
}