mod common;
use common::{finding, finding_full};
use secreport::Format;
use secfinding::{Evidence, Finding, FindingKind, Severity};
#[test]
fn format_serde_roundtrip() {
for format in [
Format::Text,
Format::Json,
Format::Jsonl,
Format::Sarif,
Format::Markdown,
] {
let json = serde_json::to_string(&format).unwrap();
let back: Format = serde_json::from_str(&json).unwrap();
assert_eq!(back, format, "serde roundtrip failed for {:?}", format);
}
}
#[test]
fn format_serde_deserialize_from_strings() {
let pairs = [
("\"Text\"", Format::Text),
("\"Json\"", Format::Json),
("\"Jsonl\"", Format::Jsonl),
("\"Sarif\"", Format::Sarif),
("\"Markdown\"", Format::Markdown),
];
for (json, expected) in pairs {
let back: Format = serde_json::from_str(json).unwrap();
assert_eq!(back, expected);
}
}
#[test]
fn json_roundtrip_parsed_values() {
let f = finding_full("RT", Severity::Critical);
let out = secreport::render(&[f], Format::Json, "tool").unwrap();
let parsed: Vec<serde_json::Value> = serde_json::from_str(&out).unwrap();
let json = serde_json::to_string(&parsed).unwrap();
let reparsed: Vec<serde_json::Value> = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, reparsed);
}
#[test]
fn jsonl_roundtrip_parsed_values() {
let findings = vec![
finding("A", Severity::High),
finding("B", Severity::Medium),
finding("C", Severity::Low),
];
let out = secreport::render(&findings, Format::Jsonl, "tool").unwrap();
let parsed: Vec<serde_json::Value> = out
.lines()
.map(|line| serde_json::from_str(line).unwrap())
.collect();
let jsonl = parsed
.iter()
.map(|v| serde_json::to_string(v).unwrap())
.collect::<Vec<_>>()
.join("\n");
let reparsed: Vec<serde_json::Value> = jsonl
.lines()
.map(|line| serde_json::from_str(line).unwrap())
.collect();
assert_eq!(parsed, reparsed);
}
#[test]
fn sarif_roundtrip_parsed_value() {
let f = finding_full("SARIF-RT", Severity::High);
let out = secreport::render(&[f], Format::Sarif, "tool").unwrap();
let parsed: serde_json::Value = serde_json::from_str(&out).unwrap();
let json = serde_json::to_string_pretty(&parsed).unwrap();
let reparsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, reparsed);
}
#[test]
fn markdown_output_is_deterministic_for_same_input() {
let f = finding_full("MD", Severity::Medium);
let out1 = secreport::render(&[f.clone()], Format::Markdown, "tool").unwrap();
let out2 = secreport::render(&[f], Format::Markdown, "tool").unwrap();
assert_eq!(out1, out2);
}
#[test]
fn text_output_is_deterministic_for_same_input() {
let f = finding_full("TXT", Severity::Medium);
let out1 = secreport::render(&[f.clone()], Format::Text, "tool").unwrap();
let out2 = secreport::render(&[f], Format::Text, "tool").unwrap();
assert_eq!(out1, out2);
}
#[test]
fn json_output_is_deterministic_for_same_input() {
let f = finding_full("JSON", Severity::Medium);
let out1 = secreport::render(&[f.clone()], Format::Json, "tool").unwrap();
let out2 = secreport::render(&[f], Format::Json, "tool").unwrap();
assert_eq!(out1, out2);
}
#[test]
fn jsonl_output_is_deterministic_for_same_input() {
let f = finding_full("JSONL", Severity::Medium);
let out1 = secreport::render(&[f.clone()], Format::Jsonl, "tool").unwrap();
let out2 = secreport::render(&[f], Format::Jsonl, "tool").unwrap();
assert_eq!(out1, out2);
}
#[test]
fn sarif_output_is_deterministic_for_same_input() {
let f = finding_full("SARIF", Severity::Medium);
let out1 = secreport::render(&[f.clone()], Format::Sarif, "tool").unwrap();
let out2 = secreport::render(&[f], Format::Sarif, "tool").unwrap();
assert_eq!(out1, out2);
}
#[test]
fn evidence_serde_roundtrip() {
let samples = vec![
Evidence::http_status(200).unwrap(),
Evidence::DnsRecord {
record_type: "A".into(),
value: "1.2.3.4".into(),
},
Evidence::Banner {
raw: "banner".into(),
},
Evidence::JsSnippet {
url: "https://example.com/app.js".into(),
line: 42,
snippet: "eval(...)".into(),
},
Evidence::Certificate {
subject: "CN=example".into(),
san: vec!["DNS:example.com".into()],
issuer: "Let's Encrypt".into(),
expires: "2028-01-01".into(),
},
Evidence::code("src/main.rs", 10, "unsafe {}", Some(5), Some("rust".into())).unwrap(),
Evidence::HttpRequest {
method: "GET".into(),
url: "https://example.com".into(),
headers: vec![("Host".into(), "example.com".into())],
body: Some("body".into()),
},
Evidence::PatternMatch {
pattern: "p".into(),
matched: "m".into(),
},
];
for sample in samples {
let json = serde_json::to_string(&sample).unwrap();
let back: Evidence = serde_json::from_str(&json).unwrap();
assert_eq!(sample, back);
}
}
#[test]
fn finding_serde_roundtrip() {
let f = finding_full("Roundtrip", Severity::High);
let json = serde_json::to_string(&f).unwrap();
let back: Finding = serde_json::from_str(&json).unwrap();
assert_eq!(f.scanner, back.scanner);
assert_eq!(f.target, back.target);
assert_eq!(f.severity, back.severity);
assert_eq!(f.title, back.title);
assert_eq!(f.detail, back.detail);
assert_eq!(f.kind, back.kind);
assert_eq!(f.tags, back.tags);
assert_eq!(f.cve_ids, back.cve_ids);
assert_eq!(f.cwe_ids, back.cwe_ids);
assert_eq!(f.confidence, back.confidence);
assert_eq!(f.cvss_score, back.cvss_score);
assert_eq!(f.exploit_hint, back.exploit_hint);
assert_eq!(f.remediation, back.remediation);
assert_eq!(f.matched_values, back.matched_values);
assert_eq!(f.evidence.len(), back.evidence.len());
}
#[test]
fn generic_finding_json_value_roundtrip() {
let evidence = vec![Evidence::http_status(403).unwrap()];
let cwe = vec![std::sync::Arc::<str>::from("CWE-79")];
let cve = vec![std::sync::Arc::<str>::from("CVE-2024-1")];
let tags = vec![std::sync::Arc::<str>::from("tag1")];
let gf = secreport::models::GenericFinding::builder("s", "t", Severity::Critical)
.title("T")
.detail("D")
.cwe_ids(&cwe)
.cve_ids(&cve)
.tags(&tags)
.confidence(Some(0.99))
.rule_id("R")
.sarif_level("error")
.exploit_hint(Some("hint"))
.evidence(&evidence)
.kind(FindingKind::Vulnerability)
.build();
let v = gf.json_value();
let json = serde_json::to_string(&v).unwrap();
let back: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(back["scanner"], "s");
assert_eq!(back["severity"], "critical");
assert_eq!(back["confidence"], 0.99);
assert_eq!(back["kind"], "Vulnerability");
assert_eq!(back["exploit_hint"], "hint");
assert_eq!(back["rule_id"], "R");
}