use std::fs;
use std::path::Path;
use assert_cmd::Command;
use tempfile::TempDir;
fn quorum() -> Command {
Command::cargo_bin("quorum").expect("quorum binary built")
}
fn init_repo() -> TempDir {
let td = TempDir::new().unwrap();
git2::Repository::init(td.path()).unwrap();
td
}
fn hook_dir(repo_root: &Path) -> std::path::PathBuf {
let repo = git2::Repository::discover(repo_root).unwrap();
repo.path().join("hooks")
}
#[test]
fn install_pre_commit_writes_hook_with_marker_in_first_5_lines() {
let td = init_repo();
quorum()
.current_dir(td.path())
.args(["install", "--hook=pre-commit"])
.assert()
.success();
let path = hook_dir(td.path()).join("pre-commit");
let body = fs::read_to_string(&path).expect("hook file written");
let first_five = body.lines().take(5).collect::<Vec<_>>().join("\n");
assert!(first_five.contains("# quorum-managed-hook"));
assert!(body.starts_with("#!/bin/sh"));
assert!(body.contains("QUORUM_SKIP=1 git commit"));
}
#[test]
fn install_pre_push_writes_hook_with_marker_and_push_bypass() {
let td = init_repo();
quorum()
.current_dir(td.path())
.args(["install", "--hook=pre-push"])
.assert()
.success();
let body = fs::read_to_string(hook_dir(td.path()).join("pre-push")).unwrap();
let first_five = body.lines().take(5).collect::<Vec<_>>().join("\n");
assert!(first_five.contains("# quorum-managed-hook"));
assert!(body.contains("QUORUM_SKIP=1 git push"));
assert!(body.contains("quorum review --hook-mode=pre-push"));
}
#[test]
fn install_refuses_existing_non_managed_hook_exit_2() {
let td = init_repo();
let dir = hook_dir(td.path());
fs::create_dir_all(&dir).unwrap();
fs::write(
dir.join("pre-commit"),
"#!/bin/sh\n# pre-existing foreign hook\necho hi\nexit 0\n",
)
.unwrap();
let out = quorum()
.current_dir(td.path())
.args(["install", "--hook=pre-commit"])
.assert()
.failure();
out.code(predicates::ord::eq(2));
let body = fs::read_to_string(dir.join("pre-commit")).unwrap();
assert!(body.contains("foreign hook"));
}
#[test]
fn install_is_idempotent_for_managed_hook() {
let td = init_repo();
quorum()
.current_dir(td.path())
.args(["install", "--hook=pre-commit"])
.assert()
.success();
quorum()
.current_dir(td.path())
.args(["install", "--hook=pre-commit"])
.assert()
.success();
let body = fs::read_to_string(hook_dir(td.path()).join("pre-commit")).unwrap();
assert!(body.contains("# quorum-managed-hook"));
}
#[test]
fn install_resolves_hook_path_via_repo_dot_path_under_worktree() {
let td = init_repo();
let sub = td.path().join("subdir/nested");
fs::create_dir_all(&sub).unwrap();
quorum()
.current_dir(&sub)
.args(["install", "--hook=pre-commit"])
.assert()
.success();
assert!(hook_dir(td.path()).join("pre-commit").exists());
assert!(!sub.join(".git").exists());
}
#[test]
fn install_outside_a_git_repo_exits_2() {
let td = TempDir::new().unwrap(); let out = quorum()
.current_dir(td.path())
.args(["install", "--hook=pre-commit"])
.assert()
.failure();
out.code(predicates::ord::eq(2));
}
#[test]
fn uninstall_removes_only_managed_idempotent_on_absent() {
let td = init_repo();
quorum()
.current_dir(td.path())
.args(["uninstall", "--hook=pre-commit"])
.assert()
.success();
quorum()
.current_dir(td.path())
.args(["install", "--hook=pre-commit"])
.assert()
.success();
let path = hook_dir(td.path()).join("pre-commit");
assert!(path.exists());
quorum()
.current_dir(td.path())
.args(["uninstall", "--hook=pre-commit"])
.assert()
.success();
assert!(!path.exists());
fs::create_dir_all(hook_dir(td.path())).unwrap();
fs::write(&path, "#!/bin/sh\necho other\nexit 0\n").unwrap();
let out = quorum()
.current_dir(td.path())
.args(["uninstall", "--hook=pre-commit"])
.assert()
.failure();
out.code(predicates::ord::eq(2));
let body = fs::read_to_string(&path).unwrap();
assert!(body.contains("echo other"));
}
#[test]
fn unknown_hook_kind_exits_2() {
let td = init_repo();
let out = quorum()
.current_dir(td.path())
.args(["install", "--hook=pre-merge-commit"])
.assert()
.failure();
out.code(predicates::ord::eq(2));
}
#[cfg(unix)]
#[test]
fn install_sets_mode_0755_on_unix() {
use std::os::unix::fs::PermissionsExt;
let td = init_repo();
quorum()
.current_dir(td.path())
.args(["install", "--hook=pre-commit"])
.assert()
.success();
let path = hook_dir(td.path()).join("pre-commit");
let mode = fs::metadata(&path).unwrap().permissions().mode() & 0o777;
assert_eq!(mode, 0o755);
}