mod common;
use common::finding;
use secreport::Format;
use secfinding::{Evidence, Finding, FindingKind, Severity};
#[test]
fn text_empty_findings_message() {
let out = secreport::render(&[] as &[Finding], Format::Text, "tool").unwrap();
assert!(out.contains("No findings"));
}
#[test]
fn text_contains_colored_severity_labels() {
for sev in [
Severity::Critical,
Severity::High,
Severity::Medium,
Severity::Low,
Severity::Info,
] {
let f = finding("T", sev);
let out = secreport::render(&[f], Format::Text, "tool").unwrap();
let label = sev.label();
assert!(
out.contains(label),
"text output for {:?} should contain label {}",
sev,
label
);
assert!(out.contains('\x1b'), "text output should contain ANSI escapes");
}
}
#[test]
fn text_strips_ansi_in_fields() {
let f = Finding::new(
"s\x1b[31mred\x1b[0m",
"t\x1b[32mgreen\x1b[0m",
Severity::High,
"\x1b[1mTitle\x1b[0m",
"\x1b[90mDetail\x1b[0m",
)
.unwrap();
let out = secreport::render(&[f], Format::Text, "tool").unwrap();
assert!(!out.contains("\x1b[31m"));
assert!(!out.contains("\x1b[32m"));
assert!(out.contains("sred"));
assert!(out.contains("tgreen"));
assert!(out.contains("Title"));
assert!(out.contains("Detail"));
}
#[test]
fn text_summary_counts_and_totals() {
let findings = vec![
finding("C", Severity::Critical),
finding("H", Severity::High),
finding("M", Severity::Medium),
finding("L", Severity::Low),
finding("I", Severity::Info),
];
let out = secreport::render(&findings, Format::Text, "tool").unwrap();
assert!(out.contains(" 1 critical"));
assert!(out.contains(" 1 high"));
assert!(out.contains(" 1 medium"));
assert!(out.contains(" 1 low"));
assert!(out.contains(" 1 info"));
assert!(out.contains("Total: \x1b[1m5\x1b[0m findings"));
}
#[test]
fn text_summary_by_scanner_sorted() {
let findings = vec![
Finding::new("z", "t", Severity::Info, "Z", "").unwrap(),
Finding::new("a", "t", Severity::Info, "A", "").unwrap(),
Finding::new("m", "t", Severity::Info, "M", "").unwrap(),
];
let out = secreport::render(&findings, Format::Text, "tool").unwrap();
let scanner_line_pos = out.find("By scanner:").unwrap();
let scanner_line = &out[scanner_line_pos..];
assert!(scanner_line.contains("a\x1b[0m:1"));
assert!(scanner_line.contains("m\x1b[0m:1"));
assert!(scanner_line.contains("z\x1b[0m:1"));
}
#[test]
fn text_summary_by_kind_sorted() {
let findings = vec![
Finding::builder("s", "t", Severity::Info)
.title("Z")
.kind(FindingKind::Vulnerability)
.build()
.unwrap(),
Finding::builder("s", "t", Severity::Info)
.title("A")
.kind(FindingKind::Exposure)
.build()
.unwrap(),
];
let out = secreport::render(&findings, Format::Text, "tool").unwrap();
let kind_line_pos = out.find("By category:").unwrap();
let kind_line = &out[kind_line_pos..];
assert!(kind_line.contains("Exposure\x1b[0m:1"));
assert!(kind_line.contains("Vulnerability\x1b[0m:1"));
}
#[test]
fn text_evidence_http_response() {
let f = Finding::builder("s", "t", Severity::Medium)
.title("T")
.evidence(Evidence::http_status(200).unwrap())
.build()
.unwrap();
let out = secreport::render(&[f], Format::Text, "tool").unwrap();
assert!(out.contains("HTTP 200"));
}
#[test]
fn text_evidence_dns_record() {
let f = Finding::builder("s", "t", Severity::Medium)
.title("T")
.evidence(Evidence::DnsRecord {
record_type: "A".into(),
value: "1.2.3.4".into(),
})
.build()
.unwrap();
let out = secreport::render(&[f], Format::Text, "tool").unwrap();
assert!(out.contains("A"));
assert!(out.contains("1.2.3.4"));
}
#[test]
fn text_evidence_banner() {
let f = Finding::builder("s", "t", Severity::Medium)
.title("T")
.evidence(Evidence::Banner {
raw: "banner text".into(),
})
.build()
.unwrap();
let out = secreport::render(&[f], Format::Text, "tool").unwrap();
assert!(out.contains("Banner:"));
assert!(out.contains("banner text"));
}
#[test]
fn text_evidence_js_snippet() {
let f = Finding::builder("s", "t", Severity::Medium)
.title("T")
.evidence(Evidence::JsSnippet {
url: "https://example.com/app.js".into(),
line: 42,
snippet: "eval(...)".into(),
})
.build()
.unwrap();
let out = secreport::render(&[f], Format::Text, "tool").unwrap();
assert!(out.contains("app.js:42"));
assert!(out.contains("eval(...)"));
}
#[test]
fn text_evidence_code_snippet() {
let f = Finding::builder("s", "t", Severity::Medium)
.title("T")
.evidence(
Evidence::code("src/main.rs", 10, "unsafe { ... }", Some(5), Some("rust".into()))
.unwrap(),
)
.build()
.unwrap();
let out = secreport::render(&[f], Format::Text, "tool").unwrap();
assert!(out.contains("main.rs:10"));
assert!(out.contains("unsafe { ... }"));
}
#[test]
fn text_evidence_banner_truncated_to_80_chars() {
let raw = "x".repeat(200);
let f = Finding::builder("s", "t", Severity::High)
.title("T")
.evidence(Evidence::Banner { raw: raw.clone().into() })
.build()
.unwrap();
let out = secreport::render(&[f], Format::Text, "tool").unwrap();
let line_start = out.find("Banner:").unwrap();
let line = &out[line_start..];
let line_end = line.find('\n').unwrap();
let banner_line = &line[..line_end];
assert!(
banner_line.len() < 120,
"banner line should be truncated in display"
);
}
#[test]
fn text_exploit_hint_preview() {
let f = Finding::builder("s", "t", Severity::High)
.title("T")
.exploit_hint("first line\nsecond line")
.build()
.unwrap();
let out = secreport::render(&[f], Format::Text, "tool").unwrap();
assert!(out.contains("first line"));
assert!(!out.contains("second line"));
}
#[test]
fn text_tags_prefixed_with_hash() {
let f = Finding::builder("s", "t", Severity::High)
.title("T")
.tag("xss")
.tag("web")
.build()
.unwrap();
let out = secreport::render(&[f], Format::Text, "tool").unwrap();
assert!(out.contains("#xss"));
assert!(out.contains("#web"));
}
#[test]
fn text_unicode_preserved() {
let f = Finding::new("s", "t", Severity::Medium, "日本語 🚨", "詳細").unwrap();
let out = secreport::render(&[f], Format::Text, "tool").unwrap();
assert!(out.contains("日本語 🚨"));
assert!(out.contains("詳細"));
}
#[test]
fn text_large_number_of_findings() {
let findings: Vec<Finding> = (0..10_000)
.map(|i| finding(&format!("F{}", i), Severity::Low))
.collect();
let out = secreport::render(&findings, Format::Text, "tool").unwrap();
assert!(out.contains("Total: \x1b[1m10000\x1b[0m findings"));
}