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(),
2,
"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",
"Open search for the active tab",
"Go to Overview",
"Go to Findings",
"Go to pg_stat",
"Go to Diff",
"Go to Correlations",
"Go to Carbon",
"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\", \"dark\", \"light\""),
"THEME_MODES tri-state ordering must be auto -> dark -> light"
);
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 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, 6,
"expected 6 @font-face rules, found {face_count}"
);
assert!(FONT_FACES.contains("font-family:'Geist'"));
assert!(FONT_FACES.contains("font-family:'Geist 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 template_does_not_leak_session_storage_to_local_storage() {
assert!(
!TEMPLATE.contains("localStorage.setItem"),
"do not persist the X-API-Key beyond the tab session"
);
assert!(
!TEMPLATE.contains("localStorage.set"),
"do not persist the X-API-Key beyond the tab session"
);
}
#[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""#));
}