kingfisher/tests/pre_commit_installer.rs
2025-12-09 12:56:55 -08:00

351 lines
11 KiB
Rust

use assert_cmd::assert::OutputAssertExt;
use assert_cmd::Command;
use predicates::str::contains;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command as StdCommand;
use tempfile::TempDir;
fn project_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}
fn copy_scripts(dest: &Path) {
let scripts_dir = dest.join("scripts");
fs::create_dir_all(&scripts_dir).unwrap();
let src = project_root().join("scripts").join("install-kingfisher-pre-commit.sh");
let dst = scripts_dir.join("install-kingfisher-pre-commit.sh");
fs::copy(src, dst).unwrap();
}
fn init_repo() -> (TempDir, PathBuf, PathBuf) {
let dir = tempfile::tempdir().unwrap();
let repo = dir.path().to_path_buf();
copy_scripts(&repo);
Command::new("git").arg("init").current_dir(&repo).assert().success();
let hooks_path = repo.join(".git/hooks");
fs::create_dir_all(&hooks_path).unwrap();
(dir, repo.clone(), hooks_path)
}
fn install(repo: &Path, hooks_path: &Path) {
Command::new("bash")
.arg(repo.join("scripts/install-kingfisher-pre-commit.sh"))
.arg("--hooks-path")
.arg(hooks_path)
.current_dir(repo)
.assert()
.success()
.stdout(contains("Kingfisher pre-commit hook installed"));
}
//
// =====================================================
// REPO-MODE TESTS (original ones, unchanged)
// =====================================================
//
#[test]
fn installs_wrapper_without_existing_hook() {
let (_tmp, repo, hooks_path) = init_repo();
install(&repo, &hooks_path);
let pre_commit = hooks_path.join("pre-commit");
let kf_wrapper = hooks_path.join("kingfisher-pre-commit");
let legacy = hooks_path.join("pre-commit.legacy.kingfisher");
let wrapper = fs::read_to_string(&pre_commit).unwrap();
let kf_script = fs::read_to_string(&kf_wrapper).unwrap();
assert!(wrapper.contains("# Kingfisher pre-commit wrapper"));
assert!(wrapper.contains("kingfisher-pre-commit"));
assert!(kf_script
.contains("kingfisher scan . --staged --quiet --redact --only-valid --no-update-check"));
assert!(!legacy.exists());
}
#[test]
fn preserves_existing_hook_and_runs_it_first() {
let (_tmp, repo, hooks_path) = init_repo();
let log = repo.join("hook.log");
let legacy = hooks_path.join("pre-commit");
fs::write(&legacy, format!("#!/usr/bin/env bash\necho legacy >> {}\n", log.display())).unwrap();
StdCommand::new("chmod").args(["+x", legacy.to_str().unwrap()]).assert().success();
let bin_dir = repo.join("bin");
fs::create_dir_all(&bin_dir).unwrap();
let fake_kingfisher = bin_dir.join("kingfisher");
fs::write(
&fake_kingfisher,
format!("#!/usr/bin/env bash\necho \"kingfisher $*\" >> {}\n", log.display()),
)
.unwrap();
StdCommand::new("chmod").args(["+x", fake_kingfisher.to_str().unwrap()]).assert().success();
install(&repo, &hooks_path);
// Execute wrapper
let wrapper = hooks_path.join("pre-commit");
StdCommand::new(wrapper)
.current_dir(&repo)
.env("PATH", format!("{}:{}", bin_dir.display(), std::env::var("PATH").unwrap()))
.assert()
.success();
let log_contents = fs::read_to_string(&log).unwrap();
let lines: Vec<_> = log_contents.lines().collect();
assert_eq!(lines[0], "legacy");
assert!(lines[1]
.contains("kingfisher scan . --staged --quiet --redact --only-valid --no-update-check"));
assert!(hooks_path.join("pre-commit.legacy.kingfisher").exists());
}
#[test]
fn uninstall_restores_original_hook() {
let (_tmp, repo, hooks_path) = init_repo();
let legacy = hooks_path.join("pre-commit");
fs::write(&legacy, "#!/usr/bin/env bash\necho legacy\n").unwrap();
StdCommand::new("chmod").args(["+x", legacy.to_str().unwrap()]).assert().success();
install(&repo, &hooks_path);
Command::new("bash")
.arg(repo.join("scripts/install-kingfisher-pre-commit.sh"))
.arg("--uninstall")
.arg("--hooks-path")
.arg(&hooks_path)
.current_dir(&repo)
.assert()
.success();
let restored = hooks_path.join("pre-commit");
let restored_content = fs::read_to_string(&restored).unwrap();
assert!(restored_content.contains("legacy"));
assert!(!restored_content.contains("Kingfisher pre-commit wrapper"));
assert!(!hooks_path.join("kingfisher-pre-commit").exists());
assert!(!hooks_path.join("pre-commit.legacy.kingfisher").exists());
}
#[test]
fn uninstall_removes_wrapper_when_no_previous_hook() {
let (_tmp, repo, hooks_path) = init_repo();
install(&repo, &hooks_path);
Command::new("bash")
.arg(repo.join("scripts/install-kingfisher-pre-commit.sh"))
.arg("--uninstall")
.arg("--hooks-path")
.arg(&hooks_path)
.current_dir(&repo)
.assert()
.success();
assert!(!hooks_path.join("pre-commit").exists());
assert!(!hooks_path.join("kingfisher-pre-commit").exists());
assert!(!hooks_path.join("pre-commit.legacy.kingfisher").exists());
}
#[test]
fn errors_outside_git_repository() {
let dir = tempfile::tempdir().unwrap();
copy_scripts(dir.path());
Command::new("bash")
.arg(dir.path().join("scripts/install-kingfisher-pre-commit.sh"))
.current_dir(dir.path())
.assert()
.failure()
.stderr(contains("must be run inside a Git repository"));
}
#[test]
fn pre_commit_framework_invokes_kingfisher() {
let (_tmp, repo, hooks_path) = init_repo();
let log = repo.join("hook.log");
let bin_dir = repo.join("bin");
fs::create_dir_all(&bin_dir).unwrap();
let fake_kingfisher = bin_dir.join("kingfisher");
fs::write(&fake_kingfisher, format!("#!/usr/bin/env bash\necho \"$@\" > {}\n", log.display()))
.unwrap();
StdCommand::new("chmod").args(["+x", fake_kingfisher.to_str().unwrap()]).assert().success();
fs::write(
repo.join(".pre-commit-config.yaml"),
r#"repos:
- repo: local
hooks:
- id: kingfisher-local
name: kingfisher (local binary)
entry: kingfisher
language: system
args: ["scan", ".", "--staged", "--quiet", "--redact", "--only-valid", "--no-update-check"]
pass_filenames: false
always_run: true
"#,
)
.unwrap();
fs::write(repo.join("README.md"), "demo").unwrap();
StdCommand::new("uv")
.args(["run", "--no-config", "--with", "pre-commit", "pre-commit", "run", "--all-files"])
.current_dir(&repo)
.env("PATH", format!("{}:{}", bin_dir.display(), std::env::var("PATH").unwrap()))
.assert()
.success()
.stdout(contains("kingfisher (local binary)"));
let log_contents = fs::read_to_string(&log).unwrap();
assert!(log_contents.contains("scan"));
assert!(log_contents.contains("--staged"));
assert!(log_contents.contains("--quiet"));
assert!(log_contents.contains("--redact"));
}
#[test]
fn installer_hook_executes_kingfisher_command() {
let (_tmp, repo, hooks_path) = init_repo();
fs::write(repo.join("canary.txt"), "secret").unwrap();
StdCommand::new("git").args(["add", "canary.txt"]).current_dir(&repo).assert().success();
let log = repo.join("hook.log");
let bin_dir = repo.join("bin");
fs::create_dir_all(&bin_dir).unwrap();
let fake_kingfisher = bin_dir.join("kingfisher");
fs::write(
&fake_kingfisher,
format!("#!/usr/bin/env bash\necho \"kingfisher $@\" >> {}\n", log.display()),
)
.unwrap();
StdCommand::new("chmod").args(["+x", fake_kingfisher.to_str().unwrap()]).assert().success();
install(&repo, &hooks_path);
let wrapper = hooks_path.join("pre-commit");
StdCommand::new(wrapper)
.current_dir(&repo)
.env("PATH", format!("{}:{}", bin_dir.display(), std::env::var("PATH").unwrap()))
.assert()
.success();
let log_contents = fs::read_to_string(&log).unwrap();
assert!(log_contents
.contains("kingfisher scan . --staged --quiet --redact --only-valid --no-update-check"));
}
//
// =====================================================
// "GLOBAL" SEMANTICS TESTS USING --hooks-path
// (deterministic, no real global config)
// =====================================================
//
fn init_fake_global() -> (TempDir, PathBuf, PathBuf) {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path().to_path_buf();
let fake_global_hooks = root.join("fake-global-hooks");
fs::create_dir_all(&fake_global_hooks).unwrap();
copy_scripts(&root);
(tmp, root, fake_global_hooks)
}
#[test]
fn global_semantics_installs_wrapper_and_inner_hook() {
let (_tmp, root, hooks) = init_fake_global();
Command::new("bash")
.arg(root.join("scripts/install-kingfisher-pre-commit.sh"))
.arg("--hooks-path")
.arg(&hooks)
.assert()
.success();
assert!(hooks.join("pre-commit").exists());
assert!(hooks.join("kingfisher-pre-commit").exists());
}
#[test]
fn global_semantics_preserves_existing_hook_and_backup() {
let (_tmp, root, hooks) = init_fake_global();
let legacy = hooks.join("pre-commit");
fs::write(&legacy, "#!/usr/bin/env bash\necho global-legacy\n").unwrap();
StdCommand::new("chmod").args(["+x", legacy.to_str().unwrap()]).assert().success();
Command::new("bash")
.arg(root.join("scripts/install-kingfisher-pre-commit.sh"))
.arg("--hooks-path")
.arg(&hooks)
.assert()
.success();
assert!(hooks.join("pre-commit").exists());
assert!(hooks.join("pre-commit.legacy.kingfisher").exists());
}
#[test]
fn global_semantics_uninstall_restores_or_removes() {
let (_tmp, root, hooks) = init_fake_global();
// case 1: with existing legacy
let legacy = hooks.join("pre-commit");
fs::write(&legacy, "#!/usr/bin/env bash\necho global-legacy\n").unwrap();
StdCommand::new("chmod").args(["+x", legacy.to_str().unwrap()]).assert().success();
Command::new("bash")
.arg(root.join("scripts/install-kingfisher-pre-commit.sh"))
.arg("--hooks-path")
.arg(&hooks)
.assert()
.success();
Command::new("bash")
.arg(root.join("scripts/install-kingfisher-pre-commit.sh"))
.arg("--uninstall")
.arg("--hooks-path")
.arg(&hooks)
.assert()
.success();
// After uninstall with legacy, pre-commit should exist and contain legacy content
let restored = fs::read_to_string(hooks.join("pre-commit")).unwrap();
assert!(restored.contains("global-legacy"));
// case 2: no existing legacy, fresh install then uninstall
let (_tmp2, root2, hooks2) = init_fake_global();
Command::new("bash")
.arg(root2.join("scripts/install-kingfisher-pre-commit.sh"))
.arg("--hooks-path")
.arg(&hooks2)
.assert()
.success();
Command::new("bash")
.arg(root2.join("scripts/install-kingfisher-pre-commit.sh"))
.arg("--uninstall")
.arg("--hooks-path")
.arg(&hooks2)
.assert()
.success();
assert!(!hooks2.join("pre-commit").exists());
assert!(!hooks2.join("kingfisher-pre-commit").exists());
assert!(!hooks2.join("pre-commit.legacy.kingfisher").exists());
}