use super::{
CandidateRequest, LifecycleContent, PolicyFinding, PolicyOutcome, ScannerSettings,
add, check, content_match, git, needle, repository, run_isolated, settings,
};
use crate::candidate_object::{ObjectId, parse_object_id};
use crate::candidate_prediction::PushedScope;
use crate::event_path::EventPath;
use crate::policy_forbidden_strings::FORBIDDEN_STRING_CODE;
use crate::scanner_test_support::rules_file;
use crate::test_support::git_text;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
fn clean() -> PolicyOutcome {
return PolicyOutcome::Findings(Vec::new());
}
fn commit_all(repo: &Path, message: &str) {
git(repo, &["add", "--all"]);
git(repo, &["commit", "--quiet", "--message", message]);
}
fn oid(repo: &Path, revision: &str) -> ObjectId {
let text: String = git_text(repo, &["rev-parse", revision]);
return parse_object_id(text.as_bytes()).expect("an object name");
}
fn landed(repo: &Path, revision: &str) -> LifecycleContent {
return LifecycleContent::Requested(CandidateRequest::Landed(oid(repo, revision)));
}
fn pushed(commits: Vec<ObjectId>, trees: Vec<ObjectId>) -> LifecycleContent {
return LifecycleContent::Requested(CandidateRequest::Pushed(PushedScope { commits, trees }));
}
fn repository_with_known_file(work: &Path) -> (PathBuf, ScannerSettings, String) {
let repo: PathBuf = repository(work, "repo");
let rules: PathBuf = rules_file(work);
let body: String = format!("first\n{}\n", needle());
std::fs::write(repo.join("a.txt"), body.as_bytes()).expect("known file");
commit_all(repo.as_path(), "known");
return (
repo,
settings(None, Some(rules.as_os_str()), false),
body,
);
}
#[test]
fn a_rename_or_copy_of_content_in_head_is_not_scanned_again() {
run_isolated(
"policy_forbidden_strings::tests::known_content::a_rename_or_copy_of_content_in_head_is_not_scanned_again",
"forbidden-known-head",
known_in_head,
);
}
fn known_in_head(work: &Path) {
let (repo, chosen, body): (PathBuf, ScannerSettings, String) = repository_with_known_file(work);
std::fs::create_dir_all(repo.join("moved")).expect("moved");
std::fs::create_dir_all(repo.join("changed")).expect("changed");
std::fs::create_dir_all(repo.join("dir")).expect("dir");
std::fs::rename(repo.join("a.txt"), repo.join("moved/a.txt")).expect("rename");
assert_eq!(
check(repo.as_path(), &add(&["moved/a.txt"]), &chosen),
clean()
);
std::fs::write(
repo.join("changed/a.txt"),
body.replacen("first", "firsT", 1).as_bytes(),
)
.expect("changed");
assert_eq!(
check(repo.as_path(), &add(&["changed/a.txt"]), &chosen),
PolicyOutcome::Findings(vec![content_match("changed/a.txt", 2)])
);
std::fs::write(
repo.join("fresh.txt"),
format!("brand new\n{}\n", needle()).as_bytes(),
)
.expect("fresh");
assert_eq!(
check(repo.as_path(), &add(&["fresh.txt"]), &chosen),
PolicyOutcome::Findings(vec![content_match("fresh.txt", 2)])
);
std::fs::write(repo.join("copied.txt"), body.as_bytes()).expect("copied");
assert_eq!(
check(repo.as_path(), &add(&["copied.txt"]), &chosen),
clean()
);
let named: String = format!("dir/{}.txt", needle());
std::fs::write(repo.join(&named), body.as_bytes()).expect("named");
assert_eq!(
check(repo.as_path(), &add(&[named.as_str()]), &chosen),
PolicyOutcome::Findings(vec![PolicyFinding {
code: FORBIDDEN_STRING_CODE,
message: String::from("Forbidden string matched in pathname segment 2 (rule 0)."),
path: Some(EventPath::from_git_bytes("dir/[REDACTED]".as_bytes())),
location: None,
fix_available: false,
}])
);
}
#[test]
fn staged_entries_are_compared_with_the_recorded_base() {
run_isolated(
"policy_forbidden_strings::tests::known_content::staged_entries_are_compared_with_the_recorded_base",
"forbidden-known-staged",
known_staged,
);
}
fn known_staged(work: &Path) {
let (repo, chosen, body): (PathBuf, ScannerSettings, String) = repository_with_known_file(work);
let base: ObjectId = oid(repo.as_path(), "HEAD");
std::fs::create_dir_all(repo.join("moved")).expect("moved");
git(repo.as_path(), &["mv", "a.txt", "moved/a.txt"]);
let staged = |path: &str| -> LifecycleContent {
let mut pathspec: OsString = OsString::from(":(top,literal)");
pathspec.push(path);
return LifecycleContent::Requested(CandidateRequest::StagedAtBase {
base: Some(base.clone()),
pathspecs: vec![pathspec],
});
};
assert_eq!(
check(repo.as_path(), &staged("moved/a.txt"), &chosen),
clean()
);
std::fs::write(
repo.join("moved/a.txt"),
body.replacen("first", "firsT", 1).as_bytes(),
)
.expect("edit");
git(repo.as_path(), &["add", "moved/a.txt"]);
assert_eq!(
check(repo.as_path(), &staged("moved/a.txt"), &chosen),
PolicyOutcome::Findings(vec![content_match("moved/a.txt", 2)])
);
}
#[test]
fn a_root_commit_and_an_unborn_branch_are_scanned() {
run_isolated(
"policy_forbidden_strings::tests::known_content::a_root_commit_and_an_unborn_branch_are_scanned",
"forbidden-known-root",
known_root,
);
}
fn known_root(work: &Path) {
let rules: PathBuf = rules_file(work);
let chosen: ScannerSettings = settings(None, Some(rules.as_os_str()), false);
let repo: PathBuf = work.join("unborn");
std::fs::create_dir(&repo).expect("repository directory");
git(repo.as_path(), &["init", "--quiet", "--initial-branch=main"]);
std::fs::write(
repo.join("a.txt"),
format!("first\n{}\n", needle()).as_bytes(),
)
.expect("a");
assert_eq!(
check(repo.as_path(), &add(&["a.txt"]), &chosen),
PolicyOutcome::Findings(vec![content_match("a.txt", 2)])
);
commit_all(repo.as_path(), "root");
assert_eq!(
check(repo.as_path(), &landed(repo.as_path(), "HEAD"), &chosen),
PolicyOutcome::Findings(vec![content_match("a.txt", 2)])
);
}
#[test]
fn a_merge_is_judged_against_every_parent_and_a_push_against_the_published_ones() {
run_isolated(
"policy_forbidden_strings::tests::known_content::a_merge_is_judged_against_every_parent_and_a_push_against_the_published_ones",
"forbidden-known-merge",
known_merge,
);
}
fn known_merge(work: &Path) {
let repo: PathBuf = repository(work, "repo");
let rules: PathBuf = rules_file(work);
let chosen: ScannerSettings = settings(None, Some(rules.as_os_str()), false);
let a_match: PolicyOutcome = PolicyOutcome::Findings(vec![content_match("a.txt", 2)]);
git(repo.as_path(), &["switch", "--quiet", "--create", "left"]);
std::fs::write(
repo.join("a.txt"),
format!("first\n{}\n", needle()).as_bytes(),
)
.expect("a");
commit_all(repo.as_path(), "left");
let introducing: ObjectId = oid(repo.as_path(), "HEAD");
git(repo.as_path(), &["switch", "--quiet", "--create", "right", "main"]);
std::fs::write(repo.join("other.txt"), b"clean\n").expect("other");
commit_all(repo.as_path(), "right");
git(repo.as_path(), &["switch", "--quiet", "left"]);
git(repo.as_path(), &["merge", "--quiet", "--no-ff", "--no-edit", "right"]);
assert_eq!(
check(repo.as_path(), &landed(repo.as_path(), "HEAD"), &chosen),
clean()
);
assert_eq!(
check(
repo.as_path(),
&LifecycleContent::Requested(CandidateRequest::Landed(introducing.clone())),
&chosen
),
a_match
);
assert_eq!(
check(
repo.as_path(),
&pushed(vec![oid(repo.as_path(), "HEAD")], Vec::new()),
&chosen
),
clean()
);
assert_eq!(
check(
repo.as_path(),
&pushed(
vec![introducing.clone(), oid(repo.as_path(), "HEAD")],
Vec::new()
),
&chosen
),
a_match
);
assert_eq!(
check(
repo.as_path(),
&pushed(
vec![oid(repo.as_path(), "HEAD")],
vec![oid(repo.as_path(), "HEAD")]
),
&chosen
),
a_match
);
git(repo.as_path(), &["switch", "--quiet", "--create", "evil", introducing.as_str()]);
git(
repo.as_path(),
&["merge", "--quiet", "--no-ff", "--no-commit", "right"],
);
std::fs::write(
repo.join("evil.txt"),
format!("novel\n{}\n", needle()).as_bytes(),
)
.expect("evil");
git(repo.as_path(), &["add", "evil.txt"]);
git(repo.as_path(), &["commit", "--quiet", "--no-edit"]);
assert_eq!(
check(repo.as_path(), &landed(repo.as_path(), "HEAD"), &chosen),
PolicyOutcome::Findings(vec![content_match("evil.txt", 2)])
);
}