#![cfg(unix)]
use super::tests::{findings_at, loaded, store, was_scanned};
use super::{ScanRunError, scan_version};
use crate::candidate_error::CandidateFailure;
use crate::candidate_object::parse_object_id;
use crate::candidate_store::CandidateStore;
use crate::candidate_version::{CandidateSource, CandidateVersion};
use crate::scanner_adapter::{CandidateScanner, ScannerFailure};
use crate::scanner_selection::rules_candidate_path;
use crate::scanner_test_support::{needle, rules_file, run_isolated};
use crate::test_support::{git, repository};
use forbidden_strings::{CandidateScan, ScanFinding};
use std::path::{Path, PathBuf};
use std::rc::Rc;
fn ineligible_candidates_are_not_scanned_body(directory: &Path) {
let token: String = needle();
let leak: String = format!("{token}\n");
let repo: PathBuf = repository(directory, "repo");
let named_by_token: String = format!("{token}.txt");
std::fs::write(repo.join(named_by_token.as_str()), "clean\n").expect("file named by the token");
git(repo.as_path(), &["add", "--all"]);
git(repo.as_path(), &["commit", "--quiet", "--message=base"]);
git(repo.as_path(), &["rm", "--quiet", named_by_token.as_str()]);
std::fs::create_dir(repo.join("config")).expect("config directory");
let rules: PathBuf = repo.join("config/rules.txt");
std::fs::write(&rules, leak.as_str()).expect("tracked rules file");
let sources: PathBuf = repo.join("package/cli/forbidden-strings/data");
std::fs::create_dir_all(&sources).expect("rule source directory");
std::fs::write(sources.join("builtin-rules.txt"), leak.as_str()).expect("rule source");
std::fs::write(repo.join("ordinary.txt"), leak.as_str()).expect("ordinary file");
git(repo.as_path(), &["add", "--all"]);
let scanner: CandidateScanner = loaded(rules.as_path());
let rules_path: Vec<u8> = rules_candidate_path(rules.as_path(), repo.as_path())
.expect("rules file is inside the repository");
assert_eq!(rules_path, b"config/rules.txt");
let mut subject: CandidateStore = store(repo.as_path());
let version: Rc<CandidateVersion> = subject
.version(&CandidateSource::StagedAgainstHead)
.expect("staged version");
assert_eq!(version.candidates().len(), 4);
let scans: Vec<CandidateScan> = scan_version(
&scanner,
&mut subject,
&version,
Some(rules_path.as_slice()),
)
.expect("scan pass");
assert_eq!(scans.len(), 1);
assert_eq!(
findings_at(&version, &scans, b"ordinary.txt"),
vec![ScanFinding::Content {
line: 1,
rule: String::from("0")
}]
);
assert!(!was_scanned(&version, &scans, named_by_token.as_bytes()));
assert!(!was_scanned(
&version,
&scans,
b"package/cli/forbidden-strings/data/builtin-rules.txt"
));
assert!(!was_scanned(&version, &scans, b"config/rules.txt"));
let unexcluded: Vec<CandidateScan> =
scan_version(&scanner, &mut subject, &version, None).expect("scan pass");
assert_eq!(unexcluded.len(), 2);
assert_eq!(
findings_at(&version, &unexcluded, b"config/rules.txt"),
vec![ScanFinding::Content {
line: 1,
rule: String::from("0")
}]
);
}
#[test]
fn ineligible_candidates_are_not_scanned() {
run_isolated(
"scanner_run::selection_tests::ineligible_candidates_are_not_scanned",
"run-eligibility",
ineligible_candidates_are_not_scanned_body,
);
}
fn failures_end_the_pass_body(directory: &Path) {
let scanner: CandidateScanner = loaded(rules_file(directory).as_path());
let repo: PathBuf = repository(directory, "repo");
std::fs::write(repo.join("first.txt"), "clean\n").expect("file");
std::fs::write(repo.join("second.txt"), "clean\n").expect("file");
git(repo.as_path(), &["add", "--all"]);
let mut subject: CandidateStore = store(repo.as_path());
let version: Rc<CandidateVersion> = subject
.version(&CandidateSource::StagedAgainstHead)
.expect("staged version");
assert_eq!(
scan_version(&scanner, &mut subject, &version, None)
.expect("scan pass")
.len(),
2
);
subject.invalidate();
let stale: ScanRunError = match scan_version(&scanner, &mut subject, &version, None) {
Ok(_) => panic!("a stale version must not be scanned"),
Err(failure) => failure,
};
match &stale {
ScanRunError::Candidate(error) => {
assert_eq!(error.failure, CandidateFailure::StaleCandidate);
assert_eq!(stale.to_string(), error.message);
}
ScanRunError::Scanner(_) => panic!("the candidate layer must be named as the cause"),
}
let unnamed: CandidateVersion = crate::candidate_version::build_version(
1,
vec![crate::candidate_record::CandidateRecord {
path: Vec::new(),
mode: crate::candidate_object::CandidateMode::Regular,
change: crate::candidate_record::CandidateChange::Deleted,
object: None,
}],
);
assert!(
scan_version(&scanner, &mut subject, &unnamed, None)
.expect("skipped")
.is_empty()
);
let gitlink: CandidateVersion = crate::candidate_version::build_version(
1,
vec![crate::candidate_record::CandidateRecord {
path: Vec::new(),
mode: crate::candidate_object::CandidateMode::Gitlink,
change: crate::candidate_record::CandidateChange::Added,
object: parse_object_id(&[b'a'; 40]),
}],
);
let refused: ScanRunError = match scan_version(&scanner, &mut subject, &gitlink, None) {
Ok(_) => panic!("an unnamed candidate must not be scanned"),
Err(failure) => failure,
};
match &refused {
ScanRunError::Scanner(error) => {
assert_eq!(error.failure, ScannerFailure::PathnameUnrepresentable);
assert_eq!(refused.to_string(), error.message);
}
ScanRunError::Candidate(_) => panic!("the scanner must be named as the cause"),
}
}
#[test]
fn failures_end_the_pass() {
run_isolated(
"scanner_run::selection_tests::failures_end_the_pass",
"run-failures",
failures_end_the_pass_body,
);
}