use super::*;
use crate::correlate::Trace;
use crate::detect::{Confidence, Finding, FindingType, Pattern, Severity};
use crate::event::{EventSource, EventType, SpanEvent};
use crate::ingest::IngestSource;
use crate::normalize::NormalizedEvent;
use crate::report::interpret::InterpretationLevel;
use crate::report::{Analysis, GreenSummary, QualityGate, Report, TopOffender};
fn span(
trace_id: &str,
span_id: &str,
parent: Option<&str>,
service: &str,
endpoint: &str,
template: &str,
) -> NormalizedEvent {
NormalizedEvent {
event: SpanEvent {
timestamp: "2026-04-21T00:00:00Z".into(),
trace_id: trace_id.into(),
span_id: span_id.into(),
parent_span_id: parent.map(ToString::to_string),
service: service.into(),
cloud_region: None,
event_type: EventType::Sql,
operation: "SELECT".into(),
target: template.into(),
duration_us: 1200,
source: EventSource {
endpoint: endpoint.into(),
method: "get".into(),
},
status_code: None,
response_size_bytes: None,
code_function: None,
code_filepath: None,
code_lineno: None,
code_namespace: None,
instrumentation_scopes: Vec::new(),
},
template: template.into(),
params: vec![],
}
}
fn finding(trace_id: &str, service: &str, endpoint: &str, template: &str) -> Finding {
Finding {
finding_type: FindingType::NPlusOneSql,
severity: Severity::Critical,
trace_id: trace_id.into(),
service: service.into(),
source_endpoint: endpoint.into(),
pattern: Pattern {
template: template.into(),
occurrences: 12,
window_ms: 100,
distinct_params: 12,
..Default::default()
},
suggestion: "use JOIN FETCH".into(),
first_timestamp: "2026-04-21T00:00:00Z".into(),
last_timestamp: "2026-04-21T00:00:01Z".into(),
green_impact: None,
confidence: Confidence::CiBatch,
classification_method: None,
code_location: None,
instrumentation_scopes: Vec::new(),
suggested_fix: None,
signature: String::new(),
}
}
fn minimal_report(findings: Vec<Finding>) -> Report {
Report {
analysis: Analysis {
duration_ms: 10,
events_processed: 1,
traces_analyzed: 1,
},
findings,
green_summary: GreenSummary {
total_io_ops: 10,
avoidable_io_ops: 4,
io_waste_ratio: 0.4,
io_waste_ratio_band: InterpretationLevel::Moderate,
top_offenders: vec![TopOffender {
endpoint: "/api/orders".into(),
service: "order-svc".into(),
io_intensity_score: 6.4,
io_intensity_band: InterpretationLevel::High,
co2_grams: Some(0.000_050),
}],
..GreenSummary::disabled(0)
},
quality_gate: QualityGate {
passed: true,
rules: vec![],
},
per_endpoint_io_ops: vec![],
correlations: vec![],
warnings: vec![],
warning_details: vec![],
acknowledged_findings: vec![],
binary_version: String::new(),
disclosure_waste: None,
}
}
fn opts(label: &str, cap: Option<usize>) -> RenderOptions {
RenderOptions {
input_label: label.into(),
max_traces_embedded: cap,
pg_stat: None,
mysql_stat: None,
diff: None,
daemon_url: None,
}
}
#[test]
fn renders_minimal_report_to_valid_html() {
let path = format!(
"{}/../../tests/fixtures/report_minimal.json",
env!("CARGO_MANIFEST_DIR")
);
let raw = std::fs::read(&path).expect("fixture readable");
let cfg = crate::config::Config::default();
let events = crate::ingest::json::JsonIngest::new(cfg.daemon.max_payload_size)
.ingest(&raw)
.expect("fixture parses");
let (report, traces) = crate::pipeline::analyze_with_traces(events, &cfg);
assert_eq!(report.findings.len(), 3, "fixture must yield 3 findings");
let types: std::collections::BTreeSet<FindingType> = report
.findings
.iter()
.map(|f| f.finding_type.clone())
.collect();
let expected: std::collections::BTreeSet<FindingType> = [
FindingType::NPlusOneSql,
FindingType::RedundantSql,
FindingType::SerializedCalls,
]
.into_iter()
.collect();
assert_eq!(
types, expected,
"minimal fixture must produce one of each type"
);
let (html, _) = render(&report, &traces, &opts("report_minimal.json", None));
assert!(html.starts_with("<!DOCTYPE html>"));
assert!(html.contains(r#"<script id="report-data""#));
assert!(html.contains("trace-report-minimal"));
assert!(html.contains("order-svc"));
}
#[test]
fn quality_gate_rules_scaffold_and_csv_confidence_present() {
let report = minimal_report(vec![]);
let (html, _) = render(&report, &[], &opts("traces.json", None));
assert!(
html.contains("function renderOverviewHero"),
"Overview hero renderer (carries the gate rules) missing"
);
assert!(html.contains("ps-hero-rule"), "hero gate-rule rows missing");
assert!(
html.contains(r#""suggested_fix_recommendation","#) && html.contains(r#""confidence""#),
"Findings CSV header must include confidence after suggested_fix_recommendation"
);
}
#[test]
fn escapes_closing_script_tag_in_embedded_json() {
let hostile = "</script><img src=x onerror=alert(1)>";
let f = finding("t1", "svc", "/ep", hostile);
let report = minimal_report(vec![f]);
let trace = Trace {
trace_id: "t1".into(),
spans: vec![span("t1", "s1", None, "svc", "/ep", hostile)],
};
let (html, _) = render(&report, &[trace], &opts("-", None));
assert_eq!(
html.matches("</script>").count(),
3,
"user-controlled </script> leaked into the document"
);
assert!(html.contains("<\\/script>"));
let start = html.find("<script id=\"report-data\"").expect("script tag");
let open = html[start..]
.find('>')
.expect("script open")
.saturating_add(1);
let rest = &html[start + open..];
let end = rest.find("</script>").expect("script close");
let json_blob = rest[..end].trim().replace("<\\/", "</");
let value: serde_json::Value =
serde_json::from_str(&json_blob).expect("JSON blob parses after <\\/ reversal");
let finding_tpl = value["report"]["findings"][0]["pattern"]["template"]
.as_str()
.expect("template present");
assert_eq!(finding_tpl, hostile);
}
#[test]
fn embedded_span_does_not_leak_raw_sql_literals() {
let masked = "SELECT * FROM t WHERE tags = ARRAY[?, ?]";
let mut ev = span("t1", "s1", None, "svc", "/ep", masked);
ev.event.target =
"SELECT * FROM t WHERE tags = ARRAY['LEAK_CANARY_SECRET', 'LEAK_CANARY_PII']".into();
let report = minimal_report(vec![finding("t1", "svc", "/ep", masked)]);
let trace = Trace {
trace_id: "t1".into(),
spans: vec![ev],
};
let (html, _) = render(&report, &[trace], &opts("-", None));
assert!(
!html.contains("LEAK_CANARY_SECRET") && !html.contains("LEAK_CANARY_PII"),
"raw SQL literal leaked into the HTML payload"
);
assert!(
html.contains("ARRAY[?, ?]"),
"masked template should still be present"
);
}
#[test]
fn escapes_adversarial_control_chars() {
let weird = "a\0b\x01c\x7fd\u{1F600}";
let f = finding("t1", "svc", "/ep", weird);
let report = minimal_report(vec![f]);
let (html, _) = render(&report, &[], &opts("traces.json", None));
let start = html.find("<script id=\"report-data\"").expect("script tag");
let open = html[start..]
.find('>')
.expect("script open")
.saturating_add(1);
let rest = &html[start + open..];
let end = rest.find("</script>").expect("script close");
let json_blob = rest[..end].trim().replace("<\\/", "</");
let value: serde_json::Value = serde_json::from_str(&json_blob).expect("JSON round-trips");
assert_eq!(
value["report"]["findings"][0]["pattern"]["template"]
.as_str()
.unwrap(),
weird
);
}
#[test]
fn applies_max_traces_embedded_cap_via_top_waste_fallback() {
let mut findings = Vec::new();
let mut traces = Vec::new();
let mut offenders = Vec::new();
for i in 0..100 {
let tid = format!("t{i:03}");
let svc = format!("svc-{i}");
let ep = format!("/ep-{i}");
let tpl = format!("SELECT * FROM t{i} WHERE id = ?");
findings.push(finding(&tid, &svc, &ep, &tpl));
traces.push(Trace {
trace_id: tid.clone(),
spans: vec![span(&tid, "s", None, &svc, &ep, &tpl)],
});
offenders.push(TopOffender {
endpoint: ep.clone(),
service: svc.clone(),
io_intensity_score: 100.0 - f64::from(i),
io_intensity_band: InterpretationLevel::High,
co2_grams: None,
});
}
let mut report = minimal_report(findings);
report.green_summary.top_offenders = offenders;
let (html, stats) = render(&report, &traces, &opts("-", Some(10)));
let json_blob = extract_payload_json(&html);
let value: serde_json::Value = serde_json::from_str(&json_blob).unwrap();
let embedded = value["embedded_traces"].as_array().expect("array");
assert_eq!(embedded.len(), 10, "exactly 10 traces kept");
let summary = &value["trimmed_traces"];
assert_eq!(summary["kept"].as_u64().unwrap(), 10);
assert_eq!(summary["total"].as_u64().unwrap(), 100);
assert_eq!(stats.kept, 10);
assert_eq!(stats.total, 100);
}
#[test]
fn oversized_findings_are_trimmed_critical_first() {
let big_template = format!("SELECT * FROM t WHERE x = '{}'", "p".repeat(4096));
let mut findings = Vec::new();
for i in 0..3000 {
let mut f = finding(&format!("t{i:04}"), "svc", "/ep", &big_template);
f.severity = match i % 3 {
0 => Severity::Critical,
1 => Severity::Warning,
_ => Severity::Info,
};
findings.push(f);
}
let report = minimal_report(findings);
let (html, _) = render(&report, &[], &opts("-", None));
assert!(
html.len() <= DEFAULT_SIZE_TARGET_BYTES + 512 * 1024,
"html is {} bytes, expected near the {} target",
html.len(),
DEFAULT_SIZE_TARGET_BYTES
);
let json_blob = extract_payload_json(&html);
let value: serde_json::Value = serde_json::from_str(&json_blob).unwrap();
let summary = &value["trimmed_findings"];
let kept = summary["kept"].as_u64().expect("kept") as usize;
assert_eq!(summary["total"].as_u64().unwrap(), 3000);
assert!(kept > 0 && kept < 3000, "kept {kept} of 3000");
let embedded = value["report"]["findings"].as_array().expect("findings");
assert_eq!(embedded.len(), kept);
assert!(
embedded
.iter()
.all(|f| f["severity"].as_str() == Some("critical")),
"trim must keep critical findings first"
);
}
#[test]
fn per_endpoint_io_ops_dropped_from_embed() {
use crate::report::PerEndpointIoOps;
let mut report = minimal_report(vec![finding("t0", "svc", "/ep", "SELECT 1")]);
report.per_endpoint_io_ops = (0..5000)
.map(|i| PerEndpointIoOps {
service: format!("svc-{i}"),
endpoint: format!("/ep-{i}"),
io_ops: i,
})
.collect();
let (html, _) = render(&report, &[], &opts("-", None));
let value: serde_json::Value = serde_json::from_str(&extract_payload_json(&html)).unwrap();
let embedded = &value["report"]["per_endpoint_io_ops"];
let len = embedded.as_array().map_or(0, Vec::len);
assert_eq!(
len, 0,
"per_endpoint_io_ops must not be embedded, got {len}"
);
}
#[test]
fn top_offenders_capped_in_embed_but_full_ranking_preserved() {
let mut offenders = Vec::new();
for i in 0..40 {
offenders.push(TopOffender {
endpoint: format!("/ep-{i}"),
service: "svc".into(),
io_intensity_score: 100.0 - f64::from(i),
io_intensity_band: InterpretationLevel::High,
co2_grams: None,
});
}
let findings = vec![
finding("t-beyond", "svc", "/ep-30", "SELECT 1"),
finding("t-none", "svc", "/ep-absent", "SELECT 2"),
];
let mut report = minimal_report(findings);
report.green_summary.top_offenders = offenders;
let traces = vec![
Trace {
trace_id: "t-none".into(),
spans: vec![span("t-none", "s", None, "svc", "/ep-absent", "SELECT 2")],
},
Trace {
trace_id: "t-beyond".into(),
spans: vec![span("t-beyond", "s", None, "svc", "/ep-30", "SELECT 1")],
},
];
let (html, _) = render(&report, &traces, &opts("-", Some(1)));
let value: serde_json::Value = serde_json::from_str(&extract_payload_json(&html)).unwrap();
let embedded_offenders = value["report"]["green_summary"]["top_offenders"]
.as_array()
.expect("top_offenders array");
assert_eq!(
embedded_offenders.len(),
TOP_OFFENDERS_EMBED_CAP,
"top_offenders must be capped in the embed"
);
let embedded_traces = value["embedded_traces"].as_array().expect("traces");
assert_eq!(embedded_traces.len(), 1);
assert_eq!(
embedded_traces[0]["trace_id"].as_str(),
Some("t-beyond"),
"ranking must use the full offender list: rank-30 beats a non-offender, \
which only holds if ranking read past the embed cap"
);
}
#[test]
fn explicit_trace_cap_keeps_findings_whole() {
let big_template = format!("SELECT * FROM t WHERE x = '{}'", "p".repeat(4096));
let findings: Vec<Finding> = (0..2000)
.map(|i| finding(&format!("t{i:04}"), "svc", "/ep", &big_template))
.collect();
let report = minimal_report(findings);
let (html, _) = render(&report, &[], &opts("-", Some(5)));
let json_blob = extract_payload_json(&html);
let value: serde_json::Value = serde_json::from_str(&json_blob).unwrap();
assert!(value.get("trimmed_findings").is_none());
assert_eq!(value["report"]["findings"].as_array().unwrap().len(), 2000);
}
#[test]
fn render_stats_match_total_when_no_trim() {
let mut findings = Vec::new();
let mut traces = Vec::new();
for i in 0..3 {
let tid = format!("t{i}");
let svc = format!("svc-{i}");
let ep = format!("/ep-{i}");
let tpl = format!("SELECT * FROM t{i} WHERE id = ?");
findings.push(finding(&tid, &svc, &ep, &tpl));
traces.push(Trace {
trace_id: tid.clone(),
spans: vec![span(&tid, "s", None, &svc, &ep, &tpl)],
});
}
let report = minimal_report(findings);
let (_, stats) = render(&report, &traces, &opts("-", None));
assert_eq!(stats.kept, stats.total);
assert_eq!(stats.kept, 3);
}
#[test]
fn omits_greenops_section_when_green_disabled() {
let f = finding("t1", "svc", "/ep", "SELECT * FROM t");
let mut report = minimal_report(vec![f.clone()]);
report.green_summary = GreenSummary::disabled(1);
let trace = Trace {
trace_id: "t1".into(),
spans: vec![span("t1", "s1", None, "svc", "/ep", "SELECT * FROM t")],
};
let (html, _) = render(&report, &[trace], &opts("-", None));
let json_blob = extract_payload_json(&html);
let value: serde_json::Value = serde_json::from_str(&json_blob).unwrap();
assert!(
value["report"]["green_summary"]["co2"].is_null()
|| value["report"]["green_summary"].get("co2").is_none(),
"co2 must be absent when green disabled"
);
assert!(html.contains(r#"id="panel-green""#));
}
#[test]
fn no_forbidden_apis_in_template() {
let forbidden = [
".innerHTML",
".outerHTML",
"insertAdjacentHTML",
"document.write",
"eval(",
"new Function(",
"DOMParser(",
"createContextualFragment(",
];
for needle in forbidden {
assert!(
!TEMPLATE.contains(needle),
"template contains forbidden API: {needle}"
);
}
assert!(!TEMPLATE.contains("window.Function("));
assert!(!TEMPLATE.contains("globalThis.Function("));
let no_ws: String = TEMPLATE.chars().filter(|c| !c.is_whitespace()).collect();
assert!(
!no_ws.contains("setAttribute(\"on"),
"template contains forbidden attribute-sink: setAttribute(\"on*\", ...)"
);
assert!(
!no_ws.contains("setAttribute('on"),
"template contains forbidden attribute-sink: setAttribute('on*', ...)"
);
}
fn svg_event_handler(svg: &str) -> Option<String> {
let lower = svg.to_ascii_lowercase();
let bytes = lower.as_bytes();
for k in 0..bytes.len() {
if !bytes[k].is_ascii_whitespace() {
continue;
}
let start = k + 1;
let mut p = start;
while p < bytes.len() && bytes[p].is_ascii_alphabetic() {
p += 1;
}
let attr = &lower[start..p];
if attr.len() <= 2 || !attr.starts_with("on") {
continue;
}
let mut q = p;
while q < bytes.len() && bytes[q].is_ascii_whitespace() {
q += 1;
}
if q < bytes.len() && bytes[q] == b'=' {
return Some(attr.to_string());
}
}
None
}
#[test]
fn brand_svgs_have_no_active_content() {
for (name, svg) in [
("BRAND_LOGO_LIGHT_SVG", BRAND_LOGO_LIGHT_SVG),
("BRAND_LOGO_DARK_SVG", BRAND_LOGO_DARK_SVG),
] {
let lower = svg.to_ascii_lowercase();
for needle in ["<script", "javascript:", "<foreignobject", "<iframe"] {
assert!(
!lower.contains(needle),
"{name} contains disallowed active content: {needle}"
);
}
assert!(
svg_event_handler(svg).is_none(),
"{name} contains a forbidden event-handler attribute: {:?}",
svg_event_handler(svg)
);
}
}
#[test]
fn svg_event_handler_scan_catches_first_attribute() {
assert_eq!(
svg_event_handler(r#"<svg onload="x">"#).as_deref(),
Some("onload")
);
assert_eq!(
svg_event_handler(r#"<rect onclick="x"/>"#).as_deref(),
Some("onclick")
);
assert_eq!(
svg_event_handler(r#"<svg width="1" onerror="x">"#).as_deref(),
Some("onerror")
);
assert_eq!(
svg_event_handler("<svg onload =\"x\">").as_deref(),
Some("onload")
);
assert!(svg_event_handler(r#"<svg width="10" viewBox="0 0 1 1">"#).is_none());
assert!(svg_event_handler(r#"<a href="https://x/?online=1">"#).is_none());
}
#[test]
#[ignore = "manual visual validation artifact, not run in CI"]
fn validation_html_for_three_estimation_states() {
let fixture_path = format!(
"{}/../../tests/fixtures/report_three_estimation_states.json",
env!("CARGO_MANIFEST_DIR")
);
let raw = std::fs::read_to_string(&fixture_path).expect("fixture readable");
let report: Report = serde_json::from_str(&raw).expect("fixture parses as Report");
let traces: Vec<Trace> = vec![];
let (html, _) = render(
&report,
&traces,
&opts("report_three_estimation_states.json", None),
);
let out = "/tmp/perf-sentinel-0.5.10-validation.html";
std::fs::write(out, &html).expect("/tmp writable");
eprintln!(
"Wrote {} bytes to {out} for visual validation. Open in a browser.",
html.len()
);
}
#[test]
#[ignore = "manual visual validation artifact, not run in CI"]
fn validation_html_for_scoring_config() {
use crate::score::carbon::ScoringConfig;
use crate::score::electricity_maps::config::{
ApiVersion, EmissionFactorType, TemporalGranularity,
};
let cases = [
("v4-defaults", ScoringConfig::default()),
(
"v3-legacy",
ScoringConfig {
api_version: ApiVersion::V3,
..ScoringConfig::default()
},
),
(
"all-optins",
ScoringConfig {
api_version: ApiVersion::V4,
emission_factor_type: EmissionFactorType::Direct,
temporal_granularity: TemporalGranularity::FifteenMinutes,
},
),
];
let fixture_path = format!(
"{}/../../tests/fixtures/report_three_estimation_states.json",
env!("CARGO_MANIFEST_DIR")
);
let raw = std::fs::read_to_string(&fixture_path).expect("fixture readable");
let traces: Vec<Trace> = vec![];
for (slug, scoring) in cases {
let mut report: Report = serde_json::from_str(&raw).expect("fixture parses as Report");
report.green_summary.scoring_config = Some(scoring);
let (html, _) = render(
&report,
&traces,
&opts(&format!("scoring-config-{slug}"), None),
);
let out = format!("/tmp/perf-sentinel-0.5.12-{slug}.html");
std::fs::write(&out, &html).expect("/tmp writable");
eprintln!("Wrote {} bytes to {out}", html.len());
}
}
#[test]
fn template_carries_scoring_config_bandeau_and_helpers() {
for needle in [
"id=\"green-scoring-config\"",
"ps-scoring-bandeau",
"ps-scoring-chip-neutral",
"ps-scoring-chip-warning",
"ps-scoring-chip-accent",
"function renderScoringConfigBandeau",
"function buildApiVersionChip",
"function buildEmissionFactorChip",
"function buildTemporalGranularityChip",
] {
assert!(
TEMPLATE.contains(needle),
"template missing scoring_config plumbing: `{needle}`"
);
}
}
#[test]
fn template_carries_estimated_column_and_helper() {
for needle in [
"<th>Estimated</th>",
"function buildEstimatedCell",
"ps-badge-estimated",
"ps-badge-measured",
] {
assert!(
TEMPLATE.contains(needle),
"template missing required 0.5.10 artifact: {needle}"
);
}
}
const CHEATSHEET_DESCRIPTION_FRAGMENTS: &[&str] = &[
"Move finding selection down",
"Move finding selection up",
"Open the selected finding",
"close search",
"Focus the search box (searches every tab)",
"Focus the global search",
"Go to Overview",
"Go to Findings",
"Go to pg_stat",
"Go to Diff",
"Go to Correlations",
"Go to Carbon",
"Sort a table by that column",
"Add a column as tie-breaker",
"Show this cheatsheet",
];
#[test]
fn cheatsheet_shortcuts_listed_in_template() {
assert!(
TEMPLATE.contains("id=\"cheatsheet\""),
"cheatsheet modal scaffolding missing"
);
assert!(
TEMPLATE.contains("Keyboard shortcuts"),
"cheatsheet title missing"
);
for description in CHEATSHEET_DESCRIPTION_FRAGMENTS {
assert!(
TEMPLATE.contains(description),
"cheatsheet missing description fragment: {description:?}"
);
}
}
#[test]
fn export_button_rendered_for_listable_tabs_only() {
for tab in ["findings", "pgstat", "mysqlstat", "diff", "correlations"] {
let needle = format!("id=\"{tab}-export\"");
assert!(
TEMPLATE.contains(&needle),
"expected export button for listable tab: {tab}"
);
}
let export_count = TEMPLATE.matches("data-export-tab=\"").count();
assert_eq!(
export_count, 5,
"expected exactly 5 export buttons (findings, pgstat, mysqlstat, diff, correlations), found {export_count}. \
If you added a new listable tab, update this assertion and the positive loop above."
);
assert!(
TEMPLATE.contains(".ps-export-btn"),
".ps-export-btn CSS class missing"
);
}
#[test]
fn sessionstorage_access_is_guarded_by_try_catch() {
assert!(
TEMPLATE.contains("function sessionGet("),
"sessionGet helper missing"
);
assert!(
TEMPLATE.contains("function sessionSet("),
"sessionSet helper missing"
);
let lines: Vec<&str> = TEMPLATE.lines().collect();
let mut hits = 0;
for (idx, line) in lines.iter().enumerate() {
let touches =
line.contains("sessionStorage.getItem") || line.contains("sessionStorage.setItem");
if !touches {
continue;
}
hits += 1;
let start = idx.saturating_sub(5);
let window_has_try = lines[start..=idx].iter().any(|l| l.contains("try {"));
assert!(
window_has_try,
"sessionStorage access on line {} has no `try {{` opener within 5 lines above: {}",
idx + 1,
line.trim()
);
}
assert!(
hits >= 2,
"expected at least one sessionGet and one sessionSet access, found {hits}"
);
}
fn extract_payload_json(html: &str) -> String {
let start = html.find("<script id=\"report-data\"").expect("script tag");
let open = html[start..]
.find('>')
.expect("script open")
.saturating_add(1);
let rest = &html[start + open..];
let end = rest.find("</script>").expect("script close");
rest[..end].trim().replace("<\\/", "</")
}
fn synthetic_pg_stat() -> PgStatReport {
use crate::ingest::pg_stat::{PgStatEntry, PgStatRanking, PgStatReport};
let entries = vec![
PgStatEntry {
query: "SELECT * FROM order_item WHERE order_id = 42".into(),
normalized_template: "SELECT * FROM order_item WHERE order_id = ?".into(),
calls: 120,
total_exec_time_ms: 840.0,
mean_exec_time_ms: 7.0,
rows: 500,
shared_blks_hit: 1000,
shared_blks_read: 0,
seen_in_traces: true,
},
PgStatEntry {
query: "SELECT id FROM orders WHERE id = 7".into(),
normalized_template: "SELECT id FROM orders WHERE id = ?".into(),
calls: 30,
total_exec_time_ms: 60.0,
mean_exec_time_ms: 2.0,
rows: 30,
shared_blks_hit: 120,
shared_blks_read: 0,
seen_in_traces: false,
},
];
PgStatReport {
total_entries: 2,
top_n: 2,
rankings: vec![PgStatRanking {
label: "top by total_exec_time".into(),
entries,
}],
}
}
fn synthetic_mysql_stat() -> MySqlStatReport {
use crate::ingest::mysql_stat::{MySqlStatEntry, MySqlStatRanking, MySqlStatReport};
let entries = vec![
MySqlStatEntry {
query: "SELECT * FROM `order_item` WHERE `order_id` = ?".into(),
normalized_template: "SELECT * FROM `order_item` WHERE `order_id` = ?".into(),
schema_name: Some("shop".into()),
calls: 120,
total_exec_time_ms: 840.0,
mean_exec_time_ms: 7.0,
rows_sent: 500,
rows_examined: 45_000,
seen_in_traces: true,
},
MySqlStatEntry {
query: "SELECT `id` FROM `orders` WHERE `id` = ?".into(),
normalized_template: "SELECT `id` FROM `orders` WHERE `id` = ?".into(),
schema_name: None,
calls: 30,
total_exec_time_ms: 60.0,
mean_exec_time_ms: 2.0,
rows_sent: 30,
rows_examined: 30,
seen_in_traces: false,
},
];
MySqlStatReport {
total_entries: 2,
top_n: 2,
rankings: vec![MySqlStatRanking {
label: "top by total_exec_time".into(),
entries,
}],
}
}
#[test]
fn embeds_mysql_stat_when_provided() {
let f = finding("t1", "svc", "/ep", "SELECT * FROM t");
let report = minimal_report(vec![f]);
let mut options = opts("-", None);
options.mysql_stat = Some(synthetic_mysql_stat());
let (html, _) = render(&report, &[], &options);
let blob = extract_payload_json(&html);
let value: serde_json::Value = serde_json::from_str(&blob).unwrap();
let entries = value["mysql_stat"]["rankings"][0]["entries"]
.as_array()
.expect("entries array");
assert_eq!(entries.len(), 2);
assert_eq!(
entries[0]["normalized_template"].as_str().unwrap(),
"SELECT * FROM `order_item` WHERE `order_id` = ?"
);
assert_eq!(entries[0]["schema_name"].as_str().unwrap(), "shop");
assert!(entries[1]["schema_name"].is_null());
}
#[test]
fn omits_mysql_stat_when_absent() {
let f = finding("t1", "svc", "/ep", "SELECT * FROM t");
let report = minimal_report(vec![f]);
let (html, _) = render(&report, &[], &opts("-", None));
let blob = extract_payload_json(&html);
let value: serde_json::Value = serde_json::from_str(&blob).unwrap();
assert!(
value.get("mysql_stat").is_none(),
"mysql_stat must be absent when not provided (skip_serializing_if)"
);
assert!(html.contains(r#"id="panel-mysqlstat""#));
}
#[test]
fn mysql_stat_sub_switcher_exposes_all_ranking_labels() {
let labels = [
"\"top by total_exec_time\"",
"\"top by calls\"",
"\"top by mean_exec_time\"",
"\"top by rows_examined\"",
];
for needle in labels {
assert!(
TEMPLATE.contains(needle),
"template is missing mysql_stat sub-switcher label mapping {needle}"
);
}
assert!(
TEMPLATE.contains("\"Rows examined\""),
"template is missing the human label for the rows_examined ranking"
);
}
#[test]
fn embeds_pg_stat_when_provided() {
let f = finding("t1", "svc", "/ep", "SELECT * FROM t");
let report = minimal_report(vec![f]);
let mut options = opts("-", None);
options.pg_stat = Some(synthetic_pg_stat());
let (html, _) = render(&report, &[], &options);
let blob = extract_payload_json(&html);
let value: serde_json::Value = serde_json::from_str(&blob).unwrap();
let entries = value["pg_stat"]["rankings"][0]["entries"]
.as_array()
.expect("entries array");
assert_eq!(entries.len(), 2);
assert_eq!(
entries[0]["normalized_template"].as_str().unwrap(),
"SELECT * FROM order_item WHERE order_id = ?"
);
}
#[test]
fn omits_pg_stat_when_absent() {
let f = finding("t1", "svc", "/ep", "SELECT * FROM t");
let report = minimal_report(vec![f]);
let (html, _) = render(&report, &[], &opts("-", None));
let blob = extract_payload_json(&html);
let value: serde_json::Value = serde_json::from_str(&blob).unwrap();
assert!(
value.get("pg_stat").is_none(),
"pg_stat must be absent when not provided (skip_serializing_if)"
);
assert!(html.contains(r#"id="panel-pgstat""#));
}
#[test]
fn embeds_diff_report_when_before_provided() {
let before_finding = finding("t1", "svc", "/ep", "SELECT * FROM t");
let before = minimal_report(vec![before_finding.clone()]);
let after_extra = finding("t2", "svc", "/ep2", "SELECT * FROM u");
let after = minimal_report(vec![before_finding, after_extra]);
let diff = crate::diff::diff_runs(&before, &after);
let mut options = opts("-", None);
options.diff = Some(diff);
let (html, _) = render(&after, &[], &options);
let blob = extract_payload_json(&html);
let value: serde_json::Value = serde_json::from_str(&blob).unwrap();
let new = value["diff"]["new_findings"].as_array().expect("new array");
assert_eq!(new.len(), 1, "one new finding introduced in 'after'");
let resolved = value["diff"]["resolved_findings"]
.as_array()
.expect("resolved array");
assert_eq!(resolved.len(), 0, "nothing was removed");
}
#[test]
fn omits_diff_when_absent() {
let f = finding("t1", "svc", "/ep", "SELECT * FROM t");
let report = minimal_report(vec![f]);
let (html, _) = render(&report, &[], &opts("-", None));
let blob = extract_payload_json(&html);
let value: serde_json::Value = serde_json::from_str(&blob).unwrap();
assert!(value.get("diff").is_none());
assert!(html.contains(r#"id="panel-diff""#));
}
#[test]
fn cross_nav_pgstat_link_added_only_when_pg_stat_present() {
let tpl = "SELECT * FROM order_item WHERE order_id = ?";
let f = finding("abc", "svc", "/ep", tpl);
let report = minimal_report(vec![f]);
let trace = Trace {
trace_id: "abc".into(),
spans: vec![span("abc", "s1", None, "svc", "/ep", tpl)],
};
let mut with_pg = opts("-", None);
with_pg.pg_stat = Some(synthetic_pg_stat());
let (html_with, _) = render(&report, std::slice::from_ref(&trace), &with_pg);
let blob_with = extract_payload_json(&html_with);
let v_with: serde_json::Value = serde_json::from_str(&blob_with).unwrap();
let pg_templates: Vec<&str> = v_with["pg_stat"]["rankings"][0]["entries"]
.as_array()
.unwrap()
.iter()
.map(|e| e["normalized_template"].as_str().unwrap())
.collect();
assert!(
pg_templates.contains(&tpl),
"pg_stat carries the span template"
);
let span_templates: Vec<&str> = v_with["embedded_traces"][0]["spans"]
.as_array()
.unwrap()
.iter()
.map(|s| s["template"].as_str().unwrap())
.collect();
assert!(
span_templates.contains(&tpl),
"trace carries the same template"
);
assert!(
TEMPLATE.contains("ps-span-pgstat-link"),
"template contains the cross-nav class"
);
let without_pg = opts("-", None);
let (html_without, _) = render(&report, &[trace], &without_pg);
let blob_without = extract_payload_json(&html_without);
let v_without: serde_json::Value = serde_json::from_str(&blob_without).unwrap();
assert!(v_without.get("pg_stat").is_none());
}
#[test]
fn pg_stat_sub_switcher_exposes_all_ranking_labels() {
let labels = [
"\"Total time\"",
"\"Calls\"",
"\"Mean time\"",
"\"I/O blocks\"",
];
for needle in labels {
assert!(
TEMPLATE.contains(needle),
"template is missing sub-switcher label {needle}"
);
}
assert!(
TEMPLATE.contains("\"data-ranking-index\""),
"setAttr path must use the attribute name as a string literal"
);
assert!(
!TEMPLATE.contains("data-ranking-index=\""),
"template must not hard-code a literal data-ranking-index attribute"
);
let entries = crate::ingest::pg_stat::parse_pg_stat(
b"query,calls,total_exec_time,mean_exec_time,rows,shared_blks_hit,shared_blks_read\n\
SELECT a FROM t1,10,100.0,10.0,10,20,5\n\
SELECT b FROM t2,20,50.0,2.5,20,100,0\n\
SELECT c FROM t3,5,200.0,40.0,5,200,50\n",
1_048_576,
)
.expect("fixture parses");
let pg_stat = crate::ingest::pg_stat::rank_pg_stat(&entries, 10);
let f = finding("t1", "svc", "/ep", "SELECT * FROM t");
let report = minimal_report(vec![f]);
let mut options = opts("-", None);
options.pg_stat = Some(pg_stat);
let (html, _) = render(&report, &[], &options);
let blob = extract_payload_json(&html);
let value: serde_json::Value = serde_json::from_str(&blob).unwrap();
let rankings = value["pg_stat"]["rankings"].as_array().unwrap();
assert_eq!(rankings.len(), 4, "payload carries all four rankings");
assert_eq!(
rankings[0]["label"].as_str().unwrap(),
"top by total_exec_time"
);
assert_eq!(
rankings[3]["label"].as_str().unwrap(),
"top by shared_blks_total"
);
}
#[test]
fn theme_mode_defaults_to_auto_with_tri_state_cycle() {
assert!(
TEMPLATE.contains("\"auto\", \"light\", \"dark\""),
"THEME_MODES tri-state ordering must be auto -> light -> dark"
);
assert!(
TEMPLATE.contains("prefers-color-scheme: dark"),
"matchMedia query for prefers-color-scheme missing"
);
assert!(
TEMPLATE.contains("function applyTheme("),
"applyTheme helper missing"
);
assert!(
TEMPLATE.contains("function currentThemeMode("),
"currentThemeMode helper missing"
);
assert!(
TEMPLATE.contains("data-theme=\"\""),
"<html> data-theme must start empty so applyTheme runs before paint"
);
assert!(
!TEMPLATE.contains("data-theme=\"dark\">"),
"<html> must not force dark at boot time"
);
}
#[test]
fn density_defaults_to_comfort_with_topbar_toggle() {
assert!(
TEMPLATE.contains("localStorage.getItem('perf-sentinel:density')"),
"density bootstrap must read the persisted mode"
);
assert!(
TEMPLATE.contains("d==='compact'?'compact':'comfort'"),
"density bootstrap must default to comfort"
);
assert!(
TEMPLATE.contains("id=\"density-toggle\""),
"topbar density toggle button missing"
);
assert!(
TEMPLATE.contains(":root[data-density=\"compact\"]"),
"compact density CSS scope missing"
);
}
#[test]
fn tables_are_sortable_with_shareable_state() {
assert!(
TEMPLATE.contains("perf-sentinel:tablesort"),
"table sort persistence key missing"
);
assert!(
TEMPLATE.contains("tsort="),
"tsort hash serialization missing"
);
assert!(
TEMPLATE.contains("aria-sort"),
"aria-sort accessibility wiring missing"
);
assert!(
TEMPLATE.contains("function refreshTableSort("),
"explicit post-render sort refresh missing"
);
}
#[test]
fn overview_kpi_cards_carry_semantic_tones() {
assert!(
TEMPLATE.contains("data-kpi"),
"Findings KPI data-kpi attribute missing"
);
assert!(
TEMPLATE.contains(".ps-metric[data-kpi=\"crit\"]"),
"saturated KPI flat CSS missing"
);
assert!(
TEMPLATE.contains("[data-tone=\"info\"]")
&& TEMPLATE.contains(".ps-metric[data-grad=\"info\"]"),
"info tone CSS hooks missing"
);
assert!(
TEMPLATE.contains(".ps-metric[data-grad=\"accent\"]"),
"accent (actionable) card CSS hook missing"
);
assert!(
!TEMPLATE.contains("linear-gradient"),
"reskin must not reintroduce gradient fills"
);
}
#[test]
fn detail_pane_no_trace_states_present() {
assert!(
TEMPLATE.contains("Trace not embedded (cap reached)"),
"cap-reached message missing"
);
assert!(
TEMPLATE.contains("This finding was resolved."),
"resolved-diff message missing"
);
assert!(
TEMPLATE.contains("function showToast("),
"showToast helper (carries the resolved-diff notice) missing"
);
}
#[test]
fn tabs_and_panels_carry_aria_roles() {
assert!(
TEMPLATE.contains("role=\"tablist\""),
"tablist role missing from template"
);
for panel in [
"panel-overview",
"panel-findings",
"panel-pgstat",
"panel-mysqlstat",
"panel-diff",
"panel-correlations",
"panel-green",
"panel-acknowledgments",
] {
let needle = format!("id=\"{panel}\"");
assert!(TEMPLATE.contains(&needle), "{panel} id missing");
}
let tabpanel_count = TEMPLATE.matches("role=\"tabpanel\"").count();
assert_eq!(
tabpanel_count, 8,
"expected 8 tabpanels, found {tabpanel_count}"
);
for tab in [
"overview",
"findings",
"pgstat",
"mysqlstat",
"diff",
"correlations",
"green",
"acknowledgments",
] {
let needle = format!("aria-labelledby=\"tab-{tab}\"");
assert!(
TEMPLATE.contains(&needle),
"aria-labelledby link missing for {tab}"
);
}
assert!(TEMPLATE.contains("\"role\", \"tab\""));
assert!(TEMPLATE.contains("\"aria-selected\""));
assert!(TEMPLATE.contains("\"aria-controls\""));
}
#[test]
fn chips_carry_aria_radio_and_pressed_states() {
assert!(
TEMPLATE.contains("\"role\", \"radiogroup\""),
"radiogroup role setter missing"
);
assert!(
TEMPLATE.contains("\"aria-label\", \"pg_stat ranking\""),
"pg_stat ranking radiogroup label missing"
);
assert!(
TEMPLATE.contains("\"aria-label\", \"Finding severity\""),
"Finding severity radiogroup label missing"
);
assert!(
TEMPLATE.contains("\"aria-label\", \"Finding service\""),
"Finding service group label missing"
);
assert!(
TEMPLATE.contains("\"aria-checked\""),
"aria-checked setter missing"
);
assert!(
TEMPLATE.contains("\"aria-pressed\""),
"aria-pressed setter missing"
);
}
#[test]
fn copy_link_button_present_on_listable_tabs_only() {
for tab in ["findings", "pgstat", "mysqlstat", "diff", "correlations"] {
let needle = format!("id=\"{tab}-copy-link\"");
assert!(
TEMPLATE.contains(&needle),
"expected copy-link button for listable tab: {tab}"
);
}
let copy_link_count = TEMPLATE.matches("data-copy-link-tab=\"").count();
assert_eq!(
copy_link_count, 5,
"expected exactly 5 copy-link buttons, found {copy_link_count}"
);
let f = finding("t1", "svc", "/ep", "SELECT 1");
let report = minimal_report(vec![f]);
let (html, _) = render(&report, &[], &opts("-", None));
assert!(!html.contains("id=\"explain-copy-link\""));
assert!(!html.contains("id=\"green-copy-link\""));
assert!(
TEMPLATE.contains(".ps-copy-link-btn"),
".ps-copy-link-btn CSS class missing"
);
}
#[test]
fn template_ships_a_strict_content_security_policy() {
let f = finding("t1", "svc", "/ep", "SELECT 1");
let report = minimal_report(vec![f]);
let (html, _) = render(&report, &[], &opts("normal.json", None));
let meta_marker = r#"<meta http-equiv="Content-Security-Policy" content=""#;
let start = html
.find(meta_marker)
.expect("CSP meta tag missing in rendered HTML");
let after_marker = &html[start + meta_marker.len()..];
let close = after_marker
.find('"')
.expect("CSP meta tag content attribute is unclosed");
let csp_value = &after_marker[..close];
assert!(csp_value.contains("default-src 'none'"));
assert!(csp_value.contains("base-uri 'none'"));
assert!(csp_value.contains("form-action 'none'"));
assert!(
csp_value.contains("font-src data:"),
"embedded fonts require font-src data: in the CSP, got: {csp_value}"
);
assert!(
!csp_value.contains("connect-src"),
"static mode must not advertise connect-src in the CSP, got: {csp_value}"
);
}
#[test]
fn embeds_brand_fonts_as_base64_woff2() {
assert!(
!FONT_FACES.contains("{{"),
"embedded font CSS must not contain `{{{{` placeholder bytes"
);
let face_count = FONT_FACES.matches("@font-face").count();
assert_eq!(
face_count, 8,
"expected 8 @font-face rules, found {face_count}"
);
assert!(FONT_FACES.contains("font-family:'DM Sans'"));
assert!(FONT_FACES.contains("font-family:'JetBrains Mono'"));
assert!(FONT_FACES.contains("src:url(data:font/woff2;base64,"));
let f = finding("t1", "svc", "/ep", "SELECT 1");
let report = minimal_report(vec![f]);
let (html, _) = render(&report, &[], &opts("normal.json", None));
assert!(
html.contains("@font-face") && html.contains("data:font/woff2;base64,"),
"rendered HTML must embed the base64 woff2 font faces"
);
assert!(
!html.contains(FONT_FACES_PLACEHOLDER),
"the FONT_FACES placeholder must be substituted in the output"
);
}
#[test]
fn template_carries_csp_placeholder() {
let csp_pos = TEMPLATE
.find(CSP_PLACEHOLDER)
.expect("CSP placeholder missing");
let title_pos = TEMPLATE.find(TITLE_PLACEHOLDER).expect("title placeholder");
let json_pos = TEMPLATE.find(JSON_PLACEHOLDER).expect("JSON placeholder");
assert!(
csp_pos < title_pos,
"CSP placeholder must precede title placeholder so replacen order stays stable"
);
assert!(
csp_pos < json_pos,
"CSP placeholder must precede JSON placeholder so replacen order stays stable"
);
}
#[test]
fn build_csp_static_mode_returns_strict_policy() {
let csp = build_csp(None);
assert!(csp.contains("default-src 'none'"));
assert!(csp.contains("base-uri 'none'"));
assert!(
!csp.contains("connect-src"),
"static mode must not include connect-src"
);
}
#[test]
fn build_csp_live_mode_appends_connect_src() {
let csp = build_csp(Some("http://localhost:4318"));
assert!(csp.contains("default-src 'none'"));
assert!(
csp.contains("connect-src 'self' http://localhost:4318"),
"live mode must whitelist 'self' plus the daemon URL: {csp}"
);
}
#[test]
fn build_csp_live_mode_only_whitelists_provided_url() {
let csp = build_csp(Some("https://daemon.example.com"));
assert!(!csp.contains("connect-src *"));
assert!(csp.contains("connect-src 'self' https://daemon.example.com"));
}
#[test]
fn rendered_html_in_static_mode_does_not_carry_daemon_field() {
let f = finding("t1", "svc", "/ep", "SELECT 1");
let report = minimal_report(vec![f]);
let (html, _) = render(&report, &[], &opts("normal.json", None));
assert!(
!html.contains(r#""daemon":"#),
"static mode payload must omit the daemon field"
);
}
#[test]
fn rendered_html_in_live_mode_with_ipv6_literal_preserves_brackets() {
let f = finding("t1", "svc", "/ep", "SELECT 1");
let report = minimal_report(vec![f]);
let mut options = opts("normal.json", None);
options.daemon_url = Some("http://[::1]:4318".to_string());
let (html, _) = render(&report, &[], &options);
assert!(
html.contains(r#""daemon":{"url":"http://[::1]:4318"}"#),
"JSON payload must round-trip the IPv6 literal verbatim"
);
assert!(
html.contains("connect-src 'self' http://[::1]:4318"),
"CSP must whitelist the IPv6 literal verbatim"
);
}
#[test]
fn rendered_html_in_live_mode_carries_daemon_field_and_connect_src() {
let f = finding("t1", "svc", "/ep", "SELECT 1");
let report = minimal_report(vec![f]);
let mut options = opts("normal.json", None);
options.daemon_url = Some("http://localhost:4318".to_string());
let (html, _) = render(&report, &[], &options);
assert!(
html.contains(r#""daemon":{"url":"http://localhost:4318"}"#),
"live-mode payload must serialize the DaemonHandle"
);
assert!(
html.contains("connect-src 'self' http://localhost:4318"),
"live-mode CSP must whitelist 'self' plus the daemon URL"
);
}
#[cfg(any(feature = "daemon", feature = "tempo"))]
#[test]
fn template_propagates_api_key_header_constant() {
let header = crate::http_client::API_KEY_HEADER;
assert!(
TEMPLATE.contains(header),
"live-mode JS must propagate `{header}` on authenticated requests; \
template contains no occurrence of the constant"
);
assert!(
TEMPLATE.contains("function fetchWithAuth"),
"fetchWithAuth helper missing from the template; \
without it the header above is never attached"
);
}
#[cfg(feature = "daemon")]
#[test]
fn live_mode_acks_cap_matches_daemon_constant() {
let needle = "var DAEMON_ACKS_CAP = ";
let start = TEMPLATE
.find(needle)
.expect("template must define DAEMON_ACKS_CAP");
let after = &TEMPLATE[start + needle.len()..];
let end = after.find(';').expect("DAEMON_ACKS_CAP must end with ';'");
let parsed: usize = after[..end]
.trim()
.parse()
.expect("DAEMON_ACKS_CAP value must be a usize");
assert_eq!(
parsed,
crate::daemon::query_api::MAX_ACKS_RESPONSE,
"HTML DAEMON_ACKS_CAP drift vs daemon MAX_ACKS_RESPONSE"
);
}
#[test]
fn template_finding_action_button_default_label_is_ack() {
assert!(TEMPLATE.contains("ps-fin-action-btn"));
assert!(TEMPLATE.contains("\"Ack\""));
}
#[test]
fn ack_toast_offers_undo_without_confirm() {
assert!(
TEMPLATE.contains("function showUndoAckToast("),
"undo-ack toast helper missing"
);
assert!(
TEMPLATE.contains("ps-toast-undo"),
"undo button styling hook missing"
);
assert!(
TEMPLATE.contains("Revoke acknowledgment for "),
"revoke confirmation must stay"
);
}
#[test]
fn template_does_not_leak_session_storage_to_local_storage() {
for (idx, needle) in TEMPLATE.match_indices("localStorage.setItem(") {
let after = &TEMPLATE[idx + needle.len()..];
let call = after.lines().next().unwrap_or("");
assert!(
call.starts_with("\"perf-sentinel:") || call.starts_with("'perf-sentinel:"),
"localStorage writes must use an inline perf-sentinel: UI-pref key, found: {}",
&call[..call.len().min(40)]
);
for secret in [
"apiKey",
"API_KEY",
"DAEMON_API_KEY",
"sessionGet",
"perf-sentinel.daemon",
] {
assert!(
!call.contains(secret),
"localStorage write must not carry the daemon API key ({secret}): {call}"
);
}
}
}
#[test]
fn hostile_input_label_with_json_placeholder_does_not_double_substitute() {
let f = finding("t1", "svc", "/ep", "SELECT 1");
let report = minimal_report(vec![f]);
let (html, _) = render(&report, &[], &opts("{{REPORT_JSON}}.json", None));
assert!(
html.contains("<title>perf-sentinel: {{REPORT_JSON}}.json</title>"),
"placeholder literal must survive as data"
);
}
#[test]
fn hostile_template_containing_title_placeholder_survives_as_data() {
let f = finding("t1", "svc", "/ep", "SELECT '{{PAGE_TITLE}}'");
let report = minimal_report(vec![f]);
let (html, _) = render(&report, &[], &opts("normal.json", None));
assert!(
html.contains("SELECT '{{PAGE_TITLE}}'"),
"user-controlled placeholder literal must survive in the JSON payload"
);
}
#[test]
fn page_title_strips_control_characters() {
let f = finding("t1", "svc", "/ep", "SELECT 1");
let report = minimal_report(vec![f]);
let (html, _) = render(&report, &[], &opts("a\x1b[31mb\x00c\u{202e}d.json", None));
assert!(!html.contains('\x1b'), "ESC must not leak into the title");
assert!(
!html.contains('\x00'),
"null byte must not leak into the title"
);
assert!(
!html.contains('\u{202e}'),
"`BiDi` override must not leak into the title"
);
assert!(html.contains("<title>perf-sentinel: a[31mbcd.json</title>"));
}
#[test]
fn page_title_uses_filename_from_input_label() {
let f = finding("t1", "svc", "/ep", "SELECT 1");
let report = minimal_report(vec![f]);
let (html_with_path, _) = render(
&report,
&[],
&opts("/tmp/reports/prod-2026-04-21.json", None),
);
assert!(
html_with_path.contains("<title>perf-sentinel: prod-2026-04-21.json</title>"),
"title should show the filename without path components"
);
let (html_stdin, _) = render(&report, &[], &opts("-", None));
assert!(
html_stdin.contains("<title>perf-sentinel report</title>"),
"stdin label falls back to the default title"
);
let (html_empty, _) = render(&report, &[], &opts("", None));
assert!(
html_empty.contains("<title>perf-sentinel report</title>"),
"empty label falls back to the default title"
);
let (html_hostile, _) = render(&report, &[], &opts("/tmp/<hack>&.json", None));
assert!(
html_hostile.contains("<title>perf-sentinel: <hack>&.json</title>"),
"unsafe characters in the filename are HTML-escaped"
);
assert!(
!html_hostile.contains("<title>perf-sentinel: <hack>"),
"raw < must not leak into the title"
);
}
#[test]
fn embeds_correlations_when_report_carries_them() {
use crate::detect::FindingType;
use crate::detect::correlate_cross::{CorrelationEndpoint, CrossTraceCorrelation};
let correlation = CrossTraceCorrelation {
source: CorrelationEndpoint {
finding_type: FindingType::NPlusOneSql,
service: "order-svc".to_string(),
template: "SELECT * FROM o WHERE id = ?".to_string(),
},
target: CorrelationEndpoint {
finding_type: FindingType::SlowHttp,
service: "payment-svc".to_string(),
template: "POST /api/charge".to_string(),
},
co_occurrence_count: 8,
source_total_occurrences: 10,
confidence: 0.8,
median_lag_ms: 120.0,
first_seen: "2026-04-21T10:00:00Z".to_string(),
last_seen: "2026-04-21T10:05:00Z".to_string(),
sample_trace_id: None,
};
let f = finding("t1", "svc", "/ep", "SELECT * FROM t");
let mut report = minimal_report(vec![f]);
report.correlations = vec![correlation];
let (html, _) = render(&report, &[], &opts("-", None));
let blob = extract_payload_json(&html);
let value: serde_json::Value = serde_json::from_str(&blob).unwrap();
let corrs = value["report"]["correlations"].as_array().unwrap();
assert_eq!(corrs.len(), 1);
assert_eq!(corrs[0]["source"]["service"].as_str().unwrap(), "order-svc");
assert_eq!(
corrs[0]["target"]["service"].as_str().unwrap(),
"payment-svc"
);
assert_eq!(corrs[0]["co_occurrence_count"].as_u64().unwrap(), 8);
assert!(html.contains(r#"id="panel-correlations""#));
}