mod common;
use common::{fixture, pinoc, stdout};
use serde_json::Value;
fn findings(fixture_name: &str, args: &[&str]) -> Vec<Value> {
let mut all = vec!["check", "--json"];
all.extend(args);
let out = pinoc(&fixture(fixture_name), &all);
sorted(serde_json::from_str(&stdout(&out)).expect("--json stdout is a JSON array"))
}
fn sorted(mut findings: Vec<Value>) -> Vec<Value> {
findings.sort_by_key(|f| {
(
f["span"]["file"].as_str().unwrap().to_string(),
f["span"]["line"].as_u64().unwrap(),
f["code"].as_str().unwrap().to_string(),
)
});
findings
}
fn codes(findings: &[Value]) -> Vec<&str> {
findings
.iter()
.map(|f| f["code"].as_str().unwrap())
.collect()
}
fn in_file<'a>(findings: &'a [Value], file: &str) -> Vec<(&'a str, &'a str)> {
findings
.iter()
.filter(|f| f["span"]["file"] == file)
.map(|f| (f["code"].as_str().unwrap(), f["evidence"].as_str().unwrap()))
.collect()
}
#[test]
fn free_function_output_matches_0_2_5() {
let dir = fixture("check_free");
let expected: Vec<Value> =
serde_json::from_str(&std::fs::read_to_string(dir.join("expected/check.json")).unwrap())
.unwrap();
assert_eq!(findings("check_free", &[]), sorted(expected));
let lines = |s: &str| {
let mut v: Vec<String> = s.lines().map(str::to_string).collect();
v.sort();
v
};
let expected = std::fs::read_to_string(dir.join("expected/check.txt")).unwrap();
let actual = stdout(&pinoc(&dir, &["check"]));
assert_eq!(lines(&actual), lines(&expected));
}
#[test]
fn impl_methods_are_analysed_like_free_functions() {
let args = ["--deny", "all"];
let free = findings("check_free", &args);
let methods = findings("check_impl", &args);
let expected = in_file(&free, "src/handlers.rs");
assert_eq!(
expected.iter().map(|f| f.0).collect::<Vec<_>>(),
["ACC001-P", "ZC002-P", "ACC002-P", "ACC003-P", "CPI001-P"]
);
assert_eq!(in_file(&methods, "src/handlers.rs"), expected);
assert_eq!(codes(&methods), codes(&free));
}
#[test]
fn zero_handlers_is_reported_not_passed_as_clean() {
let dir = fixture("check_no_handlers");
let out = pinoc(&dir, &["check"]);
let text = stdout(&out);
assert!(text.contains("NO-HANDLERS"), "{text}");
assert!(text.contains("not a clean result"), "{text}");
assert!(!text.contains("No issues found"), "{text}");
assert!(text.contains("ZC001-P"), "{text}");
assert_eq!(out.status.code(), Some(0));
assert_eq!(
codes(&findings("check_no_handlers", &[])),
["NO-HANDLERS", "ZC001-P"]
);
for deny in ["all", "NO-HANDLERS"] {
let out = pinoc(&dir, &["check", "--deny", deny]);
assert_eq!(out.status.code(), Some(1), "--deny {deny}");
}
let out = pinoc(&dir, &["check", "--allow", "NO-HANDLERS", "ZC001-P"]);
assert!(stdout(&out).contains("No issues found"));
}
#[test]
fn missing_src_is_reported() {
let dir = fixture("check_no_handlers").join("src");
let text = stdout(&pinoc(&dir, &["check"]));
assert!(text.contains("NO-HANDLERS"), "{text}");
assert!(text.contains("no `src/` directory"), "{text}");
}
#[test]
fn typed_context_and_pinocchio_0_11_names() {
let found = findings("check_typed_context", &[]);
let got: Vec<(&str, u64)> = found
.iter()
.map(|f| {
(
f["code"].as_str().unwrap(),
f["span"]["line"].as_u64().unwrap(),
)
})
.collect();
assert_eq!(
got,
[("UNTRACKED-ACCOUNTS", 0), ("ZC002-P", 53), ("ACC001-P", 64),]
);
assert_eq!(found[0]["span"]["file"], "src");
let untracked = found[0]["evidence"].as_str().unwrap();
assert!(
untracked.contains("`DepositAccounts::try_from`"),
"{untracked}"
);
}