mod common;
use common::Repo;
use std::io::Write;
use std::process::{Command, Stdio};
fn aws_key() -> String {
format!("{}{}{}", "AK", "IA", "IOSFODNN7EXAMPLE")
}
fn run_check(r: &Repo, check: &str, stdin_line: Option<String>) -> (i32, String) {
let mut cmd = Command::new(env!("CARGO_BIN_EXE_amont"));
cmd.arg("--hooks-dir")
.arg(r.path(".git/hooks"))
.arg(check)
.current_dir(&r.dir)
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let mut child = if stdin_line.is_some() {
cmd.stdin(Stdio::piped()).spawn().expect("spawn")
} else {
cmd.stdin(Stdio::null()).spawn().expect("spawn")
};
if let Some(line) = stdin_line {
child
.stdin
.as_mut()
.unwrap()
.write_all(line.as_bytes())
.unwrap();
}
let out = child.wait_with_output().expect("wait");
(
out.status.code().unwrap_or(-1),
format!(
"{}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
),
)
}
fn head(r: &Repo) -> String {
String::from_utf8_lossy(&r.git(&["rev-parse", "HEAD"]).stdout)
.trim()
.to_string()
}
fn push_line(r: &Repo, from: &str) -> String {
format!("refs/heads/feat/x {} refs/heads/feat/x {from}\n", head(r))
}
#[test]
fn a_staged_secret_blocks_and_is_never_echoed() {
let r = Repo::new();
let key = aws_key();
r.stage("config.ini", &format!("access_key = {key}\n"));
let (code, out) = run_check(&r, "pre-commit-secrets", None);
assert_ne!(code, 0, "{out}");
assert!(out.contains("AWS access key id"), "{out}");
assert!(out.contains("config.ini:1"), "{out}");
assert!(!out.contains(&key), "the report leaked the secret: {out}");
}
#[test]
fn clean_staged_content_passes() {
let r = Repo::new();
r.stage("a.txt", "nothing secret here\n");
let (code, out) = run_check(&r, "pre-commit-secrets", None);
assert_eq!(code, 0, "{out}");
}
#[test]
fn the_allow_pragma_is_honoured() {
let r = Repo::new();
r.stage(
"fixture.txt",
&format!("{} # {}\n", aws_key(), "amont:allow-secret"),
);
let (code, out) = run_check(&r, "pre-commit-secrets", None);
assert_eq!(code, 0, "{out}");
}
#[test]
fn a_no_verify_secret_is_caught_at_push() {
let r = Repo::new();
r.stage("a.txt", "base\n");
r.commit("chore: base");
let base = head(&r);
let key = aws_key();
r.stage("leaked.env", &format!("AWS_KEY={key}\n"));
r.commit("feat: oops"); let line = push_line(&r, &base);
let (code, out) = run_check(&r, "pre-push-secrets", Some(line));
assert_ne!(code, 0, "{out}");
assert!(out.contains("AWS access key id"), "{out}");
assert!(out.contains("leaked.env"), "{out}");
assert!(out.contains("rotating"), "the remedy is named: {out}");
assert!(!out.contains(&key), "the report leaked the secret: {out}");
}
#[test]
fn a_secret_removed_later_in_the_range_is_still_caught() {
let r = Repo::new();
r.stage("a.txt", "base\n");
r.commit("chore: base");
let base = head(&r);
r.stage("cfg.txt", &format!("{}\n", aws_key()));
r.commit("feat: add");
r.stage("cfg.txt", "rotated away\n");
r.commit("fix: remove");
let (code, out) = run_check(&r, "pre-push-secrets", Some(push_line(&r, &base)));
assert_ne!(code, 0, "history still carries it: {out}");
}
#[test]
fn a_clean_push_passes_and_a_delete_is_nothing() {
let r = Repo::new();
r.stage("a.txt", "base\n");
r.commit("chore: base");
let base = head(&r);
r.stage("b.txt", "more\n");
r.commit("feat: clean");
let (code, out) = run_check(&r, "pre-push-secrets", Some(push_line(&r, &base)));
assert_eq!(code, 0, "{out}");
let zeros = "0".repeat(40);
let line = format!("(delete) {zeros} refs/heads/feat/x {}\n", head(&r));
let (code, out) = run_check(&r, "pre-push-secrets", Some(line));
assert_eq!(code, 0, "{out}");
}