use super::support::*;
use tempfile::TempDir;
#[test]
fn install_dispatches_to_its_run_wiring() {
assert_eq!(run_in(&TempDir::new().unwrap(), &["install", "--from", "balls/tasks"]), 1);
}
#[test]
fn a_read_verb_renders_the_store_and_exits_zero() {
let tmp = TempDir::new().unwrap();
assert_eq!(run_in(&tmp, &["prime", "--as", "me"]), 0);
assert_eq!(run_in(&tmp, &["create", "A task", "--as", "me"]), 0);
let id = sole_task_id(&store(&tmp).join("tasks"));
for a in [&["list"][..], &["list", "--status", "ready"], &["show", &id]] {
assert_eq!(run_in(&tmp, a), 0);
}
assert_eq!(run_in(&TempDir::new().unwrap(), &["list"]), 0);
assert_eq!(run_in(&tmp, &["show", "bl-nope"]), 1);
}
#[test]
fn a_mutating_verb_before_prime_is_an_error() {
assert_eq!(run_in(&TempDir::new().unwrap(), &["create", "A task"]), 1);
}
#[test]
fn create_claim_update_close_round_trip_through_the_engine() {
let tmp = TempDir::new().unwrap();
assert_eq!(run_in(&tmp, &["prime", "--as", "me"]), 0);
assert_eq!(run_in(&tmp, &["create", "A task", "--as", "me"]), 0);
let tasks = store(&tmp).join("tasks");
let id = sole_task_id(&tasks);
assert_eq!(run_in(&tmp, &["claim", &id, "--as", "me"]), 0);
assert_eq!(run_in(&tmp, &["update", &id, "state=doing", "--as", "me"]), 0);
assert_eq!(run_in(&tmp, &["close", &id, "--as", "me"]), 0);
assert!(!tasks.join(format!("{id}.md")).exists());
}
#[test]
fn create_stamps_the_repo_root_and_claim_accepts_the_matching_checkout() {
let tmp = TempDir::new().unwrap();
let root = git_root(&tmp, "one\n");
assert_eq!(run_in(&tmp, &["prime", "--as", "me"]), 0);
assert_eq!(run_in(&tmp, &["create", "A task", "--as", "me"]), 0);
let tasks = store(&tmp).join("tasks");
let id = sole_task_id(&tasks);
let md = std::fs::read_to_string(tasks.join(format!("{id}.md"))).unwrap();
assert!(md.contains(&format!("root_commit = \"{root}\"")), "frontmatter records the root:\n{md}");
assert_eq!(run_in(&tmp, &["claim", &id, "--as", "me"]), 0);
}
#[test]
fn claim_rejects_a_ball_created_against_a_different_repo_root() {
let tmp = TempDir::new().unwrap();
git_root(&tmp, "one\n"); assert_eq!(run_in(&tmp, &["prime", "--as", "me"]), 0);
assert_eq!(run_in(&tmp, &["create", "A task", "--as", "me"]), 0);
let id = sole_task_id(&store(&tmp).join("tasks"));
std::fs::remove_dir_all(tmp.path().join("proj").join(".git")).unwrap();
git_root(&tmp, "two\n"); assert_eq!(run_in(&tmp, &["claim", &id, "--as", "me"]), 1);
}
#[test]
fn subtask_of_claim_gates_the_epic_and_close_notices_open_children() {
let tmp = TempDir::new().unwrap();
assert_eq!(run_in(&tmp, &["prime", "--as", "me"]), 0);
let tasks = store(&tmp).join("tasks");
let new_id = |known: &[&str]| -> String {
std::fs::read_dir(&tasks)
.unwrap()
.filter_map(|e| e.unwrap().file_name().to_string_lossy().strip_suffix(".md").map(str::to_string))
.find(|id| !known.contains(&id.as_str()))
.unwrap()
};
assert_eq!(run_in(&tmp, &["create", "Epic", "--as", "me"]), 0);
let epic = new_id(&[]);
assert_eq!(run_in(&tmp, &["create", "Child", "--parent", &epic, "--as", "me"]), 0);
let child = new_id(&[&epic]);
assert_eq!(run_in(&tmp, &["create", "Gate", "--subtask-of", &epic, "--as", "me"]), 0);
let gate = new_id(&[&epic, &child]);
assert_eq!(run_in(&tmp, &["claim", &epic, "--as", "me"]), 1);
assert_eq!(run_in(&tmp, &["close", &gate, "--as", "me"]), 0);
assert_eq!(run_in(&tmp, &["claim", &epic, "--as", "me"]), 0);
assert_eq!(run_in(&tmp, &["close", &epic, "--as", "me"]), 0);
assert!(!tasks.join(format!("{epic}.md")).exists());
assert!(tasks.join(format!("{child}.md")).exists());
}
#[test]
fn prime_founds_a_landing_then_converges_on_re_run() {
let tmp = TempDir::new().unwrap();
assert_eq!(run_in(&tmp, &["prime", "--as", "me"]), 0);
assert!(landing(&tmp).join("config").join("balls.toml").is_file());
assert!(store(&tmp).join("tasks").is_dir());
assert_eq!(run_in(&tmp, &["prime"]), 0);
}
#[test]
fn sync_before_prime_is_an_error() {
assert_eq!(run_in(&TempDir::new().unwrap(), &["sync"]), 1);
}
#[test]
fn sync_after_prime_targets_the_store() {
let tmp = TempDir::new().unwrap();
assert_eq!(run_in(&tmp, &["prime"]), 0);
assert_eq!(run_in(&tmp, &["sync"]), 0);
assert_eq!(run_in(&tmp, &["sync", "landing"]), 0); }
#[test]
fn a_bad_flag_is_an_op_error() {
assert_eq!(run_in(&TempDir::new().unwrap(), &["prime", "--center"]), 1);
}
#[test]
fn the_op_instant_dates_both_the_frontmatter_and_the_store_seal() {
let tmp = TempDir::new().unwrap();
let t = 1_700_000_000;
assert_eq!(run_clocked(&tmp, t, &["prime", "--as", "me"]), 0);
assert_eq!(run_clocked(&tmp, t, &["create", "A task", "--as", "me"]), 0);
let tasks = store(&tmp).join("tasks");
let id = sole_task_id(&tasks);
let md = std::fs::read_to_string(tasks.join(format!("{id}.md"))).unwrap();
assert!(md.contains("created = 1700000000"), "frontmatter created not T: {md}");
assert!(md.contains("updated = 1700000000"), "frontmatter updated not T: {md}");
let dates = crate::git::run(&store(&tmp), &["log", "-1", "--format=%at%n%ct"], None).unwrap();
for line in dates.lines() {
assert_eq!(line, "1700000000", "store seal date not T: {dates}");
}
}
#[test]
fn a_configured_but_unresolvable_clock_provider_falls_open_and_logs_the_note() {
let tmp = TempDir::new().unwrap();
assert_eq!(run_in(&tmp, &["prime", "--as", "me"]), 0);
assert_eq!(run_in(&tmp, &["conf", "set", "clock-provider", "ghost"]), 0);
assert_eq!(run_in(&tmp, &["create", "A task", "--as", "me"]), 0); let log = std::fs::read_to_string(op_log(&tmp)).unwrap();
assert!(log.contains("clock_provider ghost not found"), "note missing from op log: {log}");
}