use serde_json::Value;
use serde_json::from_str;
use stern4rust::reporting::json_printer::JsonPrinter;
use stern4rust::reporting::offence::Offence;
use stern4rust::reporting::offence_threshold::OffenceThreshold;
use stern4rust::reporting::package_roster::PackageRoster;
fn parsed(files_scanned: usize, offences: &[Offence]) -> Value {
from_str(&JsonPrinter::new(files_scanned).render(offences)).expect("valid json")
}
fn plain() -> Offence {
Offence::new(
"src/a.rs",
12,
"header",
"expected something".to_string(),
"fix it".to_string(),
)
}
#[test]
fn render_carries_a_roster_for_each_package() {
let printer = JsonPrinter::new(9).with_package_rosters(vec![
PackageRoster::new("node", vec!["header".to_string()], Vec::new(), Vec::new()),
PackageRoster::new(
"validation",
Vec::new(),
vec!["header".to_string()],
Vec::new(),
),
]);
let document: Value = from_str(&printer.render(&[])).expect("valid json");
let packages = document["packages"].as_array().expect("a packages array");
assert_eq!(packages.len(), 2);
assert_eq!(packages[0]["package"], "node");
assert_eq!(packages[0]["rules_applied"][0], "header");
assert_eq!(packages[1]["rules_skipped"][0], "header");
}
#[test]
fn render_carries_every_exclusion_with_its_count() {
let printer = JsonPrinter::new(4).with_exclusions(vec![
("fixture/**".to_string(), 27),
("gone/**".to_string(), 0),
]);
let document: Value = from_str(&printer.render(&[])).expect("valid json");
assert_eq!(document["files_excluded"], 27);
assert_eq!(document["exclusions"][0]["pattern"], "fixture/**");
assert_eq!(document["exclusions"][0]["files_excluded"], 27);
assert_eq!(document["exclusions"][1]["pattern"], "gone/**");
assert_eq!(document["exclusions"][1]["files_excluded"], 0);
}
#[test]
fn render_counts_one_broken_rule_for_two_offences_of_the_same_rule() {
let found = parsed(1, &[plain(), plain()]);
assert_eq!(found["offences_found"], 2);
assert_eq!(found["rules_broken"], 1);
}
#[test]
fn render_escapes_a_description_containing_quotes() {
let offence = Offence::new(
"src/a.rs",
1,
"header",
r#"expected "// Copyright" but found "// nope""#.to_string(),
r#"make line 1 read "// Copyright""#.to_string(),
);
let found = parsed(1, &[offence]);
assert_eq!(
found["offences"][0]["description"],
Value::String(r#"expected "// Copyright" but found "// nope""#.to_string())
);
}
#[test]
fn render_includes_the_summary_counts() {
let found = parsed(43, &[plain()]);
assert_eq!(found["files_scanned"], 43);
assert_eq!(found["offences_found"], 1);
assert_eq!(found["rules_broken"], 1);
}
#[test]
fn render_lists_the_rules_applied_skipped_and_unconfigured() {
let found = from_str::<Value>(
&JsonPrinter::new(1)
.with_rules(
vec!["header".to_string()],
vec!["tests-layout".to_string()],
vec!["readable-source".to_string()],
)
.render(&[]),
)
.expect("valid json");
assert_eq!(found["rules_applied"][0], "header");
assert_eq!(found["rules_skipped"][0], "tests-layout");
assert_eq!(found["rules_unconfigured"][0], "readable-source");
}
#[test]
fn render_names_every_field_of_an_offence() {
let offence = plain()
.with_subject("the constant `LIMIT`")
.with_expected("// Copyright");
let found = parsed(1, &[offence]);
assert_eq!(found["offences"][0]["file"], "src/a.rs");
assert_eq!(found["offences"][0]["line"], 12);
assert_eq!(found["offences"][0]["rule"], "header");
assert_eq!(found["offences"][0]["correction"], "fix it");
assert_eq!(found["offences"][0]["subject"], "the constant `LIMIT`");
assert_eq!(found["offences"][0]["expected"], "// Copyright");
}
#[test]
fn render_names_why_a_package_rule_is_unconfigured() {
let printer = JsonPrinter::new(9).with_package_rosters(vec![PackageRoster::new(
"node",
Vec::new(),
Vec::new(),
vec![(
"spdx-matches-manifest".to_string(),
"needs a `license` field in Cargo.toml".to_string(),
)],
)]);
let document: Value = from_str(&printer.render(&[])).expect("valid json");
let unconfigured = &document["packages"][0]["rules_unconfigured"][0];
assert_eq!(unconfigured["rule"], "spdx-matches-manifest");
assert_eq!(
unconfigured["requirement"],
"needs a `license` field in Cargo.toml"
);
}
#[test]
fn render_of_an_offence_without_extras_still_carries_both_keys() {
let found = parsed(1, &[plain()]);
assert_eq!(found["offences"][0]["subject"], Value::Null);
assert_eq!(found["offences"][0]["expected"], Value::Null);
}
#[test]
fn render_of_more_offences_than_the_threshold_reports_both_counts() {
let offences: Vec<Offence> = (1..=10).map(|_| plain()).collect();
let found = from_str::<Value>(
&JsonPrinter::new(1)
.with_threshold(OffenceThreshold::new(3))
.render(&offences),
)
.expect("valid json");
assert_eq!(found["offences_found"], 10);
assert_eq!(found["offences_reported"], 3);
assert_eq!(found["offences_omitted"], 7);
assert_eq!(found["offence_threshold"], 3);
assert_eq!(found["offences"].as_array().expect("array").len(), 3);
}
#[test]
fn render_of_no_offences_reports_an_empty_list() {
let found = parsed(43, &[]);
assert_eq!(found["offences"], Value::Array(vec![]));
assert_eq!(found["rules_broken"], 0);
}
#[test]
fn render_of_one_package_carries_one_roster() {
let printer = JsonPrinter::new(9).with_package_rosters(vec![PackageRoster::new(
"cargo-stern4rust",
vec!["header".to_string()],
Vec::new(),
Vec::new(),
)]);
let document: Value = from_str(&printer.render(&[])).expect("valid json");
assert_eq!(document["packages"].as_array().expect("array").len(), 1);
assert_eq!(document["packages"][0]["package"], "cargo-stern4rust");
}
#[test]
fn render_of_several_offences_keeps_the_order_it_was_given() {
let first = Offence::new(
"src/a.rs",
1,
"header",
"first".to_string(),
"fix it".to_string(),
);
let second = Offence::new(
"src/b.rs",
2,
"header",
"second".to_string(),
"fix it".to_string(),
);
let found = parsed(2, &[first, second]);
assert_eq!(found["offences"][0]["description"], "first");
assert_eq!(found["offences"][1]["description"], "second");
}
#[test]
fn render_with_rosters_keeps_the_run_level_keys() {
let printer = JsonPrinter::new(9)
.with_rules(vec!["header".to_string()], Vec::new(), Vec::new())
.with_package_rosters(vec![PackageRoster::new(
"node",
vec!["header".to_string()],
Vec::new(),
Vec::new(),
)]);
let document: Value = from_str(&printer.render(&[])).expect("valid json");
assert_eq!(document["rules_applied"][0], "header");
assert_eq!(document["files_scanned"], 9);
}
#[test]
fn render_without_rosters_carries_an_empty_packages_array() {
let printer = JsonPrinter::new(0);
let document: Value = from_str(&printer.render(&[])).expect("valid json");
assert_eq!(document["packages"].as_array().expect("array").len(), 0);
}
#[test]
fn with_baseline_carries_the_suppressed_and_stale_counts() {
let printer = JsonPrinter::new(1).with_baseline(Some("bl.json".to_string()), 7, 2);
let document: Value = from_str(&printer.render(&[])).expect("valid json");
assert_eq!(document["baseline"], "bl.json");
assert_eq!(document["baselined"], 7);
assert_eq!(document["baseline_stale_entries"], 2);
}
#[test]
fn with_config_file_carries_the_config_the_run_used() {
let printer = JsonPrinter::new(1).with_config_file(Some("stern4rust.toml".to_string()));
let document: Value = from_str(&printer.render(&[])).expect("valid json");
assert_eq!(document["config_file"], "stern4rust.toml");
}
#[test]
fn with_fixed_carries_the_number_of_files_rewritten() {
let printer = JsonPrinter::new(1).with_fixed(12);
let document: Value = from_str(&printer.render(&[])).expect("valid json");
assert_eq!(document["files_fixed"], 12);
}