use std::{fs, path::Path};
use assert_cmd::Command;
use predicates::prelude::*;
use truth_mirror::ledger::{LedgerEntry, LedgerStore, ReviewerConfig, Verdict};
use truth_mirror::reviewer::{QueuedReview, ReviewRun, ReviewRunStatus};
fn git(repo: &Path, args: &[&str]) {
let status = std::process::Command::new("git")
.args(args)
.current_dir(repo)
.status()
.unwrap();
assert!(status.success(), "git {args:?} failed");
}
fn git_stdout(repo: &Path, args: &[&str]) -> String {
let output = std::process::Command::new("git")
.args(args)
.current_dir(repo)
.output()
.unwrap();
assert!(output.status.success(), "git {args:?} failed");
String::from_utf8(output.stdout).unwrap().trim().to_owned()
}
#[test]
fn status_reports_hooks_queue_runs_ledger_and_entire_ref() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "user.email", "truth@example.invalid"]);
git(&repo, &["config", "user.name", "Truth Mirror Test"]);
fs::create_dir_all(repo.join(".entire/checkpoints")).unwrap();
fs::write(
repo.join(".entire/checkpoints/a1b2c3d4e5f6.json"),
r#"{"id":"a1b2c3d4e5f6"}"#,
)
.unwrap();
fs::write(repo.join("file.txt"), "hello\n").unwrap();
git(&repo, &["add", "file.txt"]);
git(
&repo,
&["add", "-f", ".entire/checkpoints/a1b2c3d4e5f6.json"],
);
git(
&repo,
&[
"commit",
"-m",
"feat: base",
"-m",
"CLAIM: base | verified: cargo test | evidence: tests:status\n\nEntire-Checkpoint: a1b2c3d4e5f6",
],
);
let sha = git_stdout(&repo, &["rev-parse", "HEAD"]);
git(
&repo,
&["update-ref", "refs/heads/entire/checkpoints/v1", "HEAD"],
);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks"])
.assert()
.success();
let state = repo.join(".truth");
let queued_review = QueuedReview {
run_id: String::new(),
commit_sha: sha.clone(),
enqueued_at_unix: 1,
petition_for: None,
};
fs::write(
state.join("review-queue.jsonl"),
format!("{}\n", serde_json::to_string(&queued_review).unwrap()),
)
.unwrap();
fs::create_dir_all(state.join("runs")).unwrap();
let run = ReviewRun {
id: "run-1".to_owned(),
commit_sha: sha.clone(),
target: "commit".to_owned(),
status: ReviewRunStatus::Queued,
phase: "queued".to_owned(),
ledger_entries: 0,
error: None,
worker_pid: None,
created_at_unix: 1,
updated_at_unix: 1,
started_at_unix: None,
completed_at_unix: None,
entire_checkpoint: None,
};
fs::write(
state.join("runs/run-1.json"),
serde_json::to_string(&run).unwrap(),
)
.unwrap();
LedgerStore::new(&state)
.append_entry(&LedgerEntry::new_at(
&sha,
Verdict::Reject,
"CLAIM: rejected | verified: cargo test | evidence: tests:status",
vec!["tests:status".to_owned()],
ReviewerConfig::new("claude", "model-b", false),
vec!["unsupported".to_owned()],
100,
))
.unwrap();
let subdir = repo.join("nested");
fs::create_dir(&subdir).unwrap();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&subdir)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: plain"))
.stdout(predicate::str::contains("hook commit-msg: installed"))
.stdout(predicate::str::contains("queue: pending=1"))
.stdout(predicate::str::contains("review_runs: queued=1"))
.stdout(predicate::str::contains(
"review_run_liveness: not_reconciled_read_only",
))
.stdout(predicate::str::contains(
"ledger: blocking_rejections=1 (needs_human=0)",
))
.stdout(predicate::str::contains(
"entire: ref=entire/checkpoints/v1",
));
}
#[test]
fn status_treats_missing_entire_checkpoint_as_optional() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("entire: none (optional)"));
}
#[test]
fn truth_alias_status_missing_git_repo_uses_alias_prefix() {
let temp = tempfile::tempdir().unwrap();
Command::cargo_bin("truth")
.unwrap()
.current_dir(temp.path())
.args(["status"])
.assert()
.failure()
.stderr(predicate::str::contains(
"this command requires a Git repository",
))
.stderr(predicate::str::contains("truth-mirror status").not());
}
#[test]
fn status_runs_when_config_is_malformed() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
fs::create_dir(repo.join(".truth")).unwrap();
fs::write(repo.join(".truth/config.toml"), "[policy\n").unwrap();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: plain"))
.stdout(predicate::str::contains("queue: pending=0"));
}
#[test]
fn status_degrades_malformed_queue_to_warning() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
let state = repo.join(".truth");
fs::create_dir(&state).unwrap();
fs::write(state.join("review-queue.jsonl"), "{not-json\n").unwrap();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: plain"))
.stdout(predicate::str::contains("queue: unavailable warning="))
.stdout(predicate::str::contains("review_runs: queued=0"))
.stdout(predicate::str::contains(
"ledger: blocking_rejections=0 (needs_human=0)",
));
}
#[test]
fn status_warns_when_review_run_record_is_malformed() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
let runs = repo.join(".truth/runs");
fs::create_dir_all(&runs).unwrap();
fs::write(runs.join("bad.json"), "{not-json\n").unwrap();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains(
"review_runs: queued=0 running=0 completed=0 failed=0 cancelled=0 warning=skipped_records:1",
));
}
#[test]
fn status_recognizes_relative_managed_hooks_path() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", ".truth/hooks"]);
let hooks = repo.join(".truth/hooks");
fs::create_dir_all(&hooks).unwrap();
fs::write(
hooks.join("commit-msg"),
"#!/bin/sh\n# truth-mirror managed hook\nexec truth-mirror hook-dispatch commit-msg \"$@\"\n",
)
.unwrap();
make_executable(&hooks.join("commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains(
"hook_mode: plain-legacy-truth-mirror",
))
.stdout(predicate::str::contains("hook commit-msg: installed"));
}
#[test]
fn status_recognizes_normalized_managed_hooks_path() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", "./.truth/./hooks"]);
let hooks = repo.join(".truth/hooks");
fs::create_dir_all(&hooks).unwrap();
fs::write(
hooks.join("commit-msg"),
"#!/bin/sh\n# truth-mirror managed hook\nexec truth-mirror hook-dispatch commit-msg \"$@\"\n",
)
.unwrap();
make_executable(&hooks.join("commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains(
"hook_mode: plain-legacy-truth-mirror",
))
.stdout(predicate::str::contains("hook commit-msg: installed"));
}
#[cfg(unix)]
#[test]
fn status_reports_managed_hook_missing_when_not_executable() {
use std::os::unix::fs::PermissionsExt;
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks"])
.assert()
.success();
let hook = repo.join(".git/hooks/commit-msg");
let mut permissions = fs::metadata(&hook).unwrap().permissions();
permissions.set_mode(0o644);
fs::set_permissions(&hook, permissions).unwrap();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook commit-msg: missing"))
.stdout(predicate::str::contains("hook post-commit: installed"));
}
fn make_executable(path: &Path) {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut permissions = fs::metadata(path).unwrap().permissions();
permissions.set_mode(0o755);
fs::set_permissions(path, permissions).unwrap();
}
}
fn make_not_executable(path: &Path) {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut permissions = fs::metadata(path).unwrap().permissions();
permissions.set_mode(0o644);
fs::set_permissions(path, permissions).unwrap();
}
}
#[test]
fn status_recognizes_custom_forwarder_hooks() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", "githooks"]);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks", "--inject-forwarder"])
.assert()
.success();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: custom-committed"))
.stdout(predicate::str::contains("hook commit-msg: installed"))
.stdout(predicate::str::contains("hook post-commit: installed"))
.stdout(predicate::str::contains("hook pre-push: installed"));
}
#[test]
fn status_requires_custom_forwarder_helper() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", "githooks"]);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks", "--inject-forwarder"])
.assert()
.success();
fs::remove_file(repo.join("githooks/_forward-local.sh")).unwrap();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: custom-committed"))
.stdout(predicate::str::contains("hook commit-msg: missing"))
.stdout(predicate::str::contains("hook post-commit: missing"));
}
#[test]
fn status_requires_custom_forwarder_local_hook() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", "githooks"]);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks", "--inject-forwarder"])
.assert()
.success();
fs::remove_file(repo.join(".git/hooks/commit-msg")).unwrap();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: custom-committed"))
.stdout(predicate::str::contains("hook commit-msg: missing"))
.stdout(predicate::str::contains("hook post-commit: installed"));
}
#[test]
fn status_rejects_custom_hook_that_only_mentions_git_common_dir() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", "githooks"]);
fs::create_dir(repo.join("githooks")).unwrap();
fs::write(
repo.join("githooks/commit-msg"),
"#!/bin/sh\ngit rev-parse --git-common-dir >/dev/null\nexit 0\n",
)
.unwrap();
make_executable(&repo.join("githooks/commit-msg"));
fs::write(
repo.join(".git/hooks/commit-msg"),
"#!/bin/sh\n# truth-mirror managed hook\nexec truth-mirror hook-dispatch commit-msg \"$@\"\n",
)
.unwrap();
make_executable(&repo.join(".git/hooks/commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: custom-committed"))
.stdout(predicate::str::contains("hook commit-msg: missing"));
}
#[test]
fn status_recognizes_direct_custom_forwarder_hook() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", "githooks"]);
fs::create_dir(repo.join("githooks")).unwrap();
fs::write(
repo.join("githooks/commit-msg"),
"#!/bin/sh\ngit_dir=\"$(git rev-parse --git-common-dir)\"\nexec \"$git_dir/hooks/commit-msg\" \"$@\"\n",
)
.unwrap();
make_executable(&repo.join("githooks/commit-msg"));
fs::write(
repo.join(".git/hooks/commit-msg"),
"#!/bin/sh\n# truth-mirror managed hook\nexec truth-mirror hook-dispatch commit-msg \"$@\"\n",
)
.unwrap();
make_executable(&repo.join(".git/hooks/commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: custom-committed"))
.stdout(predicate::str::contains("hook commit-msg: installed"))
.stdout(predicate::str::contains("hook post-commit: missing"));
}
#[test]
fn status_recognizes_variable_based_custom_forwarder_hook() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", "githooks"]);
fs::create_dir(repo.join("githooks")).unwrap();
fs::write(
repo.join("githooks/commit-msg"),
"#!/bin/sh\nname=$(basename \"$0\")\ngit_dir=\"$(git rev-parse --git-common-dir)\"\nexec \"$git_dir/hooks/$name\" \"$@\"\n",
)
.unwrap();
make_executable(&repo.join("githooks/commit-msg"));
fs::write(
repo.join(".git/hooks/commit-msg"),
"#!/bin/sh\n# truth-mirror managed hook\nexec truth-mirror hook-dispatch commit-msg \"$@\"\n",
)
.unwrap();
make_executable(&repo.join(".git/hooks/commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: custom-committed"))
.stdout(predicate::str::contains("hook commit-msg: installed"))
.stdout(predicate::str::contains("hook post-commit: missing"));
}
#[test]
fn status_recognizes_direct_custom_truth_mirror_hook() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", "githooks"]);
fs::create_dir(repo.join("githooks")).unwrap();
fs::write(
repo.join("githooks/commit-msg"),
"#!/bin/sh\nexec truth-mirror hook-dispatch commit-msg \"$@\"\n",
)
.unwrap();
make_executable(&repo.join("githooks/commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: custom-committed"))
.stdout(predicate::str::contains("hook commit-msg: installed"));
}
#[test]
fn status_recognizes_direct_custom_truth_alias_hook() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", "githooks"]);
fs::create_dir(repo.join("githooks")).unwrap();
fs::write(
repo.join("githooks/commit-msg"),
"#!/bin/sh\nexec truth hook-dispatch commit-msg \"$@\"\n",
)
.unwrap();
make_executable(&repo.join("githooks/commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: custom-committed"))
.stdout(predicate::str::contains("hook commit-msg: installed"));
}
#[test]
fn status_recognizes_path_qualified_custom_truth_alias_hook() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", "githooks"]);
fs::create_dir(repo.join("githooks")).unwrap();
fs::write(
repo.join("githooks/commit-msg"),
"#!/bin/sh\nexec /usr/local/bin/truth hook-dispatch commit-msg \"$@\"\n",
)
.unwrap();
make_executable(&repo.join("githooks/commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: custom-committed"))
.stdout(predicate::str::contains("hook commit-msg: installed"));
}
#[test]
fn status_rejects_commented_direct_truth_mirror_hook() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
fs::write(
repo.join(".git/hooks/commit-msg"),
"#!/bin/sh\n# exec truth-mirror hook-dispatch commit-msg \"$@\"\nexit 0\n",
)
.unwrap();
make_executable(&repo.join(".git/hooks/commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: plain"))
.stdout(predicate::str::contains("hook commit-msg: missing"));
}
#[test]
fn status_rejects_wrong_hook_name_suffix() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
fs::write(
repo.join(".git/hooks/commit-msg"),
"#!/bin/sh\nexec truth-mirror hook-dispatch commit-msg-old \"$@\"\n",
)
.unwrap();
make_executable(&repo.join(".git/hooks/commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: plain"))
.stdout(predicate::str::contains("hook commit-msg: missing"));
}
#[test]
fn status_rejects_truth_and_dispatch_in_different_shell_segments() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
fs::write(
repo.join(".git/hooks/commit-msg"),
"#!/bin/sh\nexec truth --version && hook-dispatch commit-msg \"$@\"\n",
)
.unwrap();
make_executable(&repo.join(".git/hooks/commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: plain"))
.stdout(predicate::str::contains("hook commit-msg: missing"));
}
#[test]
fn status_rejects_custom_forwarder_that_drops_arguments() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", "githooks"]);
fs::create_dir_all(repo.join("githooks")).unwrap();
fs::create_dir_all(repo.join(".git/hooks")).unwrap();
fs::write(
repo.join("githooks/commit-msg"),
"#!/bin/sh\ngit_dir=\"$(git rev-parse --git-common-dir)\"\nexec \"$git_dir/hooks/commit-msg\"\n",
)
.unwrap();
make_executable(&repo.join("githooks/commit-msg"));
fs::write(
repo.join(".git/hooks/commit-msg"),
"#!/bin/sh\n# truth-mirror managed hook\nexec truth-mirror hook-dispatch commit-msg \"$@\"\n",
)
.unwrap();
make_executable(&repo.join(".git/hooks/commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: custom-committed"))
.stdout(predicate::str::contains("hook commit-msg: missing"));
}
#[test]
fn status_recognizes_husky_entrypoint_and_content_hook() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", ".husky/_"]);
write_husky_entrypoints(&repo);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks"])
.assert()
.success();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: husky"))
.stdout(predicate::str::contains("hook commit-msg: installed"))
.stdout(predicate::str::contains("hook post-commit: installed"));
}
#[cfg(unix)]
#[test]
fn status_requires_executable_husky_helper_when_entrypoint_execs_helper() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", ".husky/_"]);
write_husky_entrypoints(&repo);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks"])
.assert()
.success();
make_not_executable(&repo.join(".husky/_/h"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: husky"))
.stdout(predicate::str::contains("hook commit-msg: missing"))
.stdout(predicate::str::contains("hook post-commit: missing"));
}
#[test]
fn status_recognizes_direct_husky_entrypoint_without_helper() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", ".husky/_"]);
let internal = repo.join(".husky/_");
fs::create_dir_all(&internal).unwrap();
fs::write(
internal.join("commit-msg"),
"#!/bin/sh\nexec \"$(dirname \"$0\")/../commit-msg\" \"$@\"\n",
)
.unwrap();
make_executable(&internal.join("commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks"])
.assert()
.success();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: husky"))
.stdout(predicate::str::contains("hook commit-msg: installed"));
}
#[test]
fn status_recognizes_husky_hooks_that_dispatch_through_a_resolved_binary() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", ".husky"]);
let husky = repo.join(".husky");
fs::create_dir_all(&husky).unwrap();
for hook in ["commit-msg", "post-commit", "pre-push"] {
fs::write(
husky.join(hook),
format!(
"#!/bin/sh\ntruth_mirror_bin=\"$(command -v truth-mirror || true)\"\n[ -n \"$truth_mirror_bin\" ] || exit 0\n\"$truth_mirror_bin\" hook-dispatch {hook} \"$@\"\n"
),
)
.unwrap();
make_executable(&husky.join(hook));
}
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: husky"))
.stdout(predicate::str::contains("hook commit-msg: installed"))
.stdout(predicate::str::contains("hook post-commit: installed"))
.stdout(predicate::str::contains("hook pre-push: installed"));
}
#[test]
fn status_rejects_commented_husky_entrypoint_delegate() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", ".husky/_"]);
let internal = repo.join(".husky/_");
fs::create_dir_all(&internal).unwrap();
fs::write(
internal.join("commit-msg"),
"#!/bin/sh\n# exec \"$(dirname \"$0\")/../commit-msg\" \"$@\"\nexit 0\n",
)
.unwrap();
make_executable(&internal.join("commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks"])
.assert()
.success();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: husky"))
.stdout(predicate::str::contains("hook commit-msg: missing"));
}
#[test]
fn status_requires_husky_entrypoint() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", ".husky/_"]);
write_husky_entrypoints(&repo);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks"])
.assert()
.success();
fs::remove_file(repo.join(".husky/_/commit-msg")).unwrap();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: husky"))
.stdout(predicate::str::contains("hook commit-msg: missing"))
.stdout(predicate::str::contains("hook post-commit: installed"));
}
#[test]
fn status_requires_husky_entrypoint_to_forward() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", ".husky/_"]);
write_husky_entrypoints(&repo);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks"])
.assert()
.success();
fs::write(repo.join(".husky/_/commit-msg"), "#!/bin/sh\necho stale\n").unwrap();
make_executable(&repo.join(".husky/_/commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: husky"))
.stdout(predicate::str::contains("hook commit-msg: missing"))
.stdout(predicate::str::contains("hook post-commit: installed"));
}
#[cfg(unix)]
#[test]
fn status_recognizes_stock_husky_helper() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", ".husky/_"]);
write_stock_husky_entrypoints(&repo);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks"])
.assert()
.success();
make_not_executable(&repo.join(".husky/_/h"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: husky"))
.stdout(predicate::str::contains("hook commit-msg: installed"))
.stdout(predicate::str::contains("hook post-commit: installed"))
.stdout(predicate::str::contains("hook pre-push: installed"));
}
#[cfg(unix)]
#[test]
fn status_recognizes_parameter_expansion_husky_helper() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", ".husky/_"]);
write_parameter_expansion_husky_entrypoints(&repo);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks"])
.assert()
.success();
make_not_executable(&repo.join(".husky/_/h"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: husky"))
.stdout(predicate::str::contains("hook commit-msg: installed"))
.stdout(predicate::str::contains("hook post-commit: installed"))
.stdout(predicate::str::contains("hook pre-push: installed"));
}
#[cfg(unix)]
#[test]
fn status_recognizes_non_executable_stock_husky_content_hook() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", ".husky/_"]);
write_stock_husky_entrypoints(&repo);
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks"])
.assert()
.success();
make_not_executable(&repo.join(".husky/commit-msg"));
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: husky"))
.stdout(predicate::str::contains("hook commit-msg: installed"))
.stdout(predicate::str::contains("hook post-commit: installed"));
}
fn write_husky_entrypoints(repo: &Path) {
let internal = repo.join(".husky/_");
fs::create_dir_all(&internal).unwrap();
fs::write(
internal.join("h"),
"#!/bin/sh\nhook_name=\"$(basename \"$0\")\"\ncontent_dir=\"$(dirname \"$(dirname \"$0\")\")\"\nexec \"$content_dir/$hook_name\" \"$@\"\n",
)
.unwrap();
make_executable(&internal.join("h"));
for hook in ["commit-msg", "post-commit", "pre-push"] {
fs::write(
internal.join(hook),
"#!/bin/sh\nexec \"$(dirname \"$0\")/h\" \"$@\"\n",
)
.unwrap();
make_executable(&internal.join(hook));
}
}
#[cfg(unix)]
#[test]
fn status_reports_installed_when_sourced_husky_h_is_not_executable() {
let temp = tempfile::tempdir().unwrap();
let repo = temp.path().join("repo");
fs::create_dir(&repo).unwrap();
git(&repo, &["init"]);
git(&repo, &["config", "core.hooksPath", ".husky/_"]);
let internal = repo.join(".husky/_");
fs::create_dir_all(&internal).unwrap();
fs::write(
internal.join("h"),
"#!/bin/sh\nhook_name=\"$(basename \"$0\")\"\ncontent_dir=\"$(dirname \"$(dirname \"$0\")\")\"\nexec \"$content_dir/$hook_name\" \"$@\"\n",
)
.unwrap();
for hook in ["commit-msg", "post-commit", "pre-push"] {
fs::write(
internal.join(hook),
"#!/bin/sh\n. \"$(dirname \"$0\")/h\"\n",
)
.unwrap();
make_executable(&internal.join(hook));
}
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["install-hooks"])
.assert()
.success();
Command::cargo_bin("truth-mirror")
.unwrap()
.current_dir(&repo)
.args(["status"])
.assert()
.success()
.stdout(predicate::str::contains("hook_mode: husky"))
.stdout(predicate::str::contains("hook commit-msg: installed"))
.stdout(predicate::str::contains("hook post-commit: installed"))
.stdout(predicate::str::contains("hook pre-push: installed"));
}
fn write_stock_husky_entrypoints(repo: &Path) {
let internal = repo.join(".husky/_");
fs::create_dir_all(&internal).unwrap();
fs::write(
internal.join("h"),
r#"#!/bin/sh
n=$(basename "$0")
s=$(dirname "$(dirname "$0")")/$n
[ ! -f "$s" ] && exit 0
sh -e "$s" "$@"
"#,
)
.unwrap();
for hook in ["commit-msg", "post-commit", "pre-push"] {
fs::write(
internal.join(hook),
"#!/bin/sh\n. \"$(dirname \"$0\")/h\"\n",
)
.unwrap();
make_executable(&internal.join(hook));
}
}
fn write_parameter_expansion_husky_entrypoints(repo: &Path) {
let internal = repo.join(".husky/_");
fs::create_dir_all(&internal).unwrap();
fs::write(
internal.join("h"),
r#"#!/bin/sh
n=${0##*/}
s=${0%/*/*}/$n
[ ! -f "$s" ] && exit 0
sh -e "$s" "$@"
"#,
)
.unwrap();
for hook in ["commit-msg", "post-commit", "pre-push"] {
fs::write(
internal.join(hook),
"#!/bin/sh\n. \"$(dirname \"$0\")/h\"\n",
)
.unwrap();
make_executable(&internal.join(hook));
}
}