use std::{fs, path::PathBuf, str::FromStr};
use nessus_parser::{MacAddress, NessusClientDataV2, error::FormatError};
fn fixture_path(name: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("fixtures")
.join(name)
}
#[test]
fn parses_anonymized_fixtures() {
let fixtures = [
"anonymized_minimal_alive.nessus",
"anonymized_minimal_dead.nessus",
];
for fixture in fixtures {
let xml = fs::read_to_string(fixture_path(fixture)).expect("fixture should be readable");
let parsed = NessusClientDataV2::parse(&xml)
.unwrap_or_else(|err| panic!("failed parsing fixture {fixture}: {err}"));
assert!(!parsed.policy.policy_name.trim().is_empty());
let report = parsed
.report
.as_ref()
.expect("fixtures should include a report");
assert!(
!report.hosts.is_empty(),
"fixture {fixture} should have hosts"
);
for host in &report.hosts {
assert!(
!host.items.is_empty(),
"fixture {fixture} has host without items"
);
assert!(!host.properties.host_start.trim().is_empty());
assert!(host.properties.host_start_timestamp.as_second() > 0);
assert!(
host.scanner_ip.is_some(),
"fixture {fixture} should include scanner_ip"
);
assert!(
host.ping_outcome.is_some(),
"fixture {fixture} should include ping outcome"
);
}
}
}
#[test]
fn parses_structural_edge_case_fixtures() {
let fixtures = [
"anonymized_report_namespace_with_host.nessus",
"anonymized_empty_report.nessus",
"anonymized_attachment_and_rich_fields.nessus",
];
for fixture in fixtures {
let xml = fs::read_to_string(fixture_path(fixture)).expect("fixture should be readable");
let parsed = NessusClientDataV2::parse(&xml)
.unwrap_or_else(|err| panic!("failed parsing fixture {fixture}: {err}"));
assert!(!parsed.policy.policy_name.trim().is_empty());
assert!(
parsed.report.is_some(),
"fixture {fixture} should include a report"
);
}
let empty_xml = fs::read_to_string(fixture_path("anonymized_empty_report.nessus"))
.expect("fixture should be readable");
let empty_parsed = NessusClientDataV2::parse(&empty_xml).expect("fixture should parse");
let empty_report = empty_parsed.report.as_ref().expect("report should exist");
assert!(
empty_report.hosts.is_empty(),
"empty report fixture should have no hosts"
);
let rich_xml = fs::read_to_string(fixture_path("anonymized_attachment_and_rich_fields.nessus"))
.expect("fixture should be readable");
let rich_parsed = NessusClientDataV2::parse(&rich_xml).expect("fixture should parse");
let rich_report = rich_parsed.report.as_ref().expect("report should exist");
let rich_host = rich_report
.hosts
.first()
.expect("rich fixture should have one host");
let rich_item = rich_host
.items
.first()
.expect("rich fixture should have one report item");
assert!(
rich_item.others.contains_key("attachment"),
"rich fixture should include attachment field"
);
assert!(
rich_item.others.contains_key("synopsis"),
"rich fixture should include synopsis field"
);
assert!(
rich_item.others.contains_key("see_also"),
"rich fixture should include see_also field"
);
assert!(
rich_item.others.contains_key("cve"),
"rich fixture should include cve field"
);
}
#[test]
fn parse_rejects_non_nessus_root() {
let xml = r#"<?xml version="1.0"?><not_nessus/>"#;
let err = NessusClientDataV2::parse(xml).expect_err("must reject unsupported root");
assert!(matches!(err, FormatError::UnsupportedVersion));
}
#[test]
fn parse_rejects_missing_policy_tag() {
let xml = r#"<NessusClientData_v2>
<Report name="Anonymized"></Report>
</NessusClientData_v2>"#;
let err = NessusClientDataV2::parse(xml).expect_err("missing Policy should fail");
assert!(matches!(err, FormatError::MissingTag("Policy")));
}
#[test]
fn mac_address_parsing_and_display_is_stable() {
let mac = MacAddress::from_str("0a:1B:2c:3D:4e:5f").expect("valid MAC should parse");
assert_eq!(mac.bytes(), [10, 27, 44, 61, 78, 95]);
assert_eq!(mac.to_string(), "0A:1B:2C:3D:4E:5F");
}