use secreport::Format;
use secfinding::{Evidence, Finding, FindingKind, Severity};
use std::collections::HashSet;
pub fn finding(title: &str, severity: Severity) -> Finding {
Finding::new("test-scanner", "https://example.com", severity, title, "detail text").unwrap()
}
pub fn finding_full(title: &str, severity: Severity) -> Finding {
Finding::builder("test-scanner", "https://example.com", severity)
.title(title)
.detail("Detailed description of the issue.")
.kind(FindingKind::Vulnerability)
.tag("web")
.tag("xss")
.cwe("CWE-79")
.cve("CVE-2024-12345")
.reference("https://example.com/ref")
.confidence(0.95)
.cvss_score(7.5)
.exploit_hint("curl -X GET https://example.com/poc")
.remediation("Sanitize user input.")
.matched_value("<script>alert(1)</script>")
.evidence(Evidence::http_status(200).unwrap())
.evidence(Evidence::Banner {
raw: "Server: nginx".into(),
})
.build()
.unwrap()
}
pub fn assert_all_formats_produce_output(findings: &[Finding]) {
for format in [
Format::Text,
Format::Json,
Format::Jsonl,
Format::Sarif,
Format::Markdown,
] {
let out = secreport::render(findings, format, "test-tool").unwrap();
if findings.is_empty() && format == Format::Jsonl {
assert_eq!(out, "", "jsonl with 0 findings should be empty");
} else {
assert!(
!out.is_empty(),
"format {:?} produced empty output for {} findings",
format,
findings.len()
);
}
}
}
pub fn assert_sarif_schema(value: &serde_json::Value) {
assert_eq!(
value["$schema"],
"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json"
);
assert_eq!(value["version"], "2.1.0");
let runs = value["runs"].as_array().expect("runs must be array");
assert_eq!(runs.len(), 1, "SARIF must contain exactly one run");
}
pub fn assert_sarif_run(run: &serde_json::Value, expected_tool_name: &str) {
let tool = &run["tool"]["driver"];
assert_eq!(tool["name"], expected_tool_name);
assert!(tool["rules"].is_array(), "tool.driver.rules must be array");
assert!(run["results"].is_array(), "run.results must be array");
}
pub fn assert_sarif_rule(rule: &serde_json::Value) {
assert!(rule["id"].is_string(), "rule.id must be string");
assert!(rule["name"].is_string(), "rule.name must be string");
assert!(
rule["shortDescription"]["text"].is_string(),
"rule.shortDescription.text must be string"
);
assert!(
rule["fullDescription"]["text"].is_string(),
"rule.fullDescription.text must be string"
);
assert!(rule["help"]["text"].is_string(), "rule.help.text must be string");
assert!(
rule["help"]["markdown"].is_string(),
"rule.help.markdown must be string"
);
assert!(rule["properties"].is_object(), "rule.properties must be object");
}
pub fn assert_sarif_result(result: &serde_json::Value) {
assert!(result["ruleId"].is_string(), "result.ruleId must be string");
assert!(result["level"].is_string(), "result.level must be string");
assert!(
result["message"]["text"].is_string(),
"result.message.text must be string"
);
assert!(result["locations"].is_array(), "result.locations must be array");
assert!(
result["partialFingerprints"].is_object(),
"result.partialFingerprints must be object"
);
assert!(
result["partialFingerprints"]["primaryLocationLineHash"]
.as_str()
.is_some(),
"result.partialFingerprints.primaryLocationLineHash must be string"
);
assert!(result["codeFlows"].is_array(), "result.codeFlows must be array");
assert!(result["properties"].is_object(), "result.properties must be object");
}
pub fn assert_sarif_results_match_rules(value: &serde_json::Value) {
let rules = value["runs"][0]["tool"]["driver"]["rules"]
.as_array()
.unwrap();
let results = value["runs"][0]["results"].as_array().unwrap();
let rule_ids: HashSet<String> = rules
.iter()
.filter_map(|r| r["id"].as_str().map(String::from))
.collect();
for result in results {
let rid = result["ruleId"].as_str().unwrap_or("");
if !rid.is_empty() {
assert!(
rule_ids.contains(rid),
"result ruleId {:?} not found in rules array",
rid
);
}
}
}
pub fn render_sarif(findings: &[Finding]) -> serde_json::Value {
let out = secreport::render(findings, Format::Sarif, "test-tool").unwrap();
serde_json::from_str(&out).expect("SARIF output must be valid JSON")
}
pub fn render_json(findings: &[Finding]) -> Vec<serde_json::Value> {
let out = secreport::render(findings, Format::Json, "test-tool").unwrap();
serde_json::from_str(&out).expect("JSON output must be valid array")
}
pub fn render_jsonl(findings: &[Finding]) -> Vec<serde_json::Value> {
let out = secreport::render(findings, Format::Jsonl, "test-tool").unwrap();
out.lines()
.filter(|l| !l.is_empty())
.map(|line| serde_json::from_str(line).expect("JSONL line must be valid JSON"))
.collect()
}