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),
link_trace_id: None,
service: service.into(),
grouping: Vec::new(),
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(),
grouping: Vec::new(),
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,
ingest: None,
},
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![],
embedded_traces: vec![],
warnings: vec![],
warning_details: vec![],
acknowledged_findings: vec![],
binary_version: String::new(),
detection_config: None,
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, None);
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 no_js_notice_is_removed_by_a_script_that_follows_it() {
let report = minimal_report(vec![]);
let (html, _) = render(&report, &[], &opts("traces.json", None));
let notice = html.find(r#"id="ps-no-js""#).expect("no-JS notice");
let remover = html
.find(r#"getElementById("ps-no-js")"#)
.expect("notice remover");
let shell = html.find(r#"id="ps-shell""#).expect("app shell");
assert!(
notice < remover && remover < shell,
"notice, then remover, then shell"
);
}
#[test]
fn green_panel_carries_database_waste_card() {
let report = minimal_report(vec![]);
let (html, _) = render(&report, &[], &opts("traces.json", None));
assert!(
html.contains("greenSummary.database_waste"),
"green panel must read database_waste"
);
assert!(
html.contains("excluded from totals"),
"the card must state the exclusion from totals"
);
assert!(
html.contains("avoidable / total ops"),
"waste ratio subtitle missing"
);
}
#[test]
fn green_panel_greys_out_absent_figures_instead_of_hiding_them() {
let report = minimal_report(vec![]);
let (html, _) = render(&report, &[], &opts("traces.json", None));
assert!(
html.contains("a measured reading needs `[green.alumet.database]`"),
"absent database figure must say what produces one"
);
assert!(
html.contains("no broker publish spans, or no broker energy backend"),
"absent broker figure must name its causes"
);
assert!(
html.contains("GreenOps scoring is off ([green] enabled = false)"),
"the disabled Carbon tab must explain itself"
);
assert!(
html.contains(r#"data-grad="muted"#) || html.contains("[data-grad=\"muted\"]"),
"the muted card style must exist"
);
assert!(
html.contains("registerDisabledTab"),
"the disabled-tab mechanism must exist"
);
assert!(
html.contains("daemon-only output (`perf-sentinel watch`)"),
"the batch-run cause must be named"
);
assert!(
html.contains("`[daemon.correlation]` may be disabled"),
"the daemon-run cause must be named"
);
}
#[test]
fn finding_types_carry_a_help_tooltip_with_the_run_thresholds() {
let report = minimal_report(vec![]);
let (html, _) = render(&report, &[], &opts("traces.json", None));
for key in [
"n_plus_one_sql:",
"redundant_sql:",
"slow_sql:",
"excessive_fanout:",
"chatty_service:",
"pool_saturation:",
"serialized_calls:",
] {
assert!(
html.contains(&format!("\n {key}")),
"FINDING_HELP entry missing: {key}"
);
}
assert!(
html.contains("function findingHelpText"),
"the value-substitution helper must exist"
);
assert!(
html.contains("report.detection_config"),
"the tooltip must read the embedded detection config"
);
for needle in [
"var TAB_HELP",
"share of the source finding's occurrences followed by the target",
"`pg_stat_statements` extension",
"events_statements_summary_by_digest",
"SCI v1.0 methodology",
"The only term a code fix can reduce",
"I/O Intensity Score of the worst endpoint",
] {
assert!(html.contains(needle), "tab/card help missing: {needle}");
}
}
#[test]
fn diff_columns_sort_by_severity_and_tint_by_the_worst() {
let report = minimal_report(vec![]);
let (html, _) = render(&report, &[], &opts("traces.json", None));
assert!(
html.contains("bySeverityDesc"),
"diff lists must sort by decreasing severity"
);
assert!(
html.contains(r#".ps-diff-section-new[data-sev="critical"]"#),
"the New zone must tint by its worst severity"
);
assert!(
html.contains(".ps-diff-section-resolved { background-color: var(--ok-bg); }"),
"the Resolved zone stays green"
);
}
#[test]
fn correlation_cards_land_on_and_announce_the_real_findings() {
let report = minimal_report(vec![]);
let (html, _) = render(&report, &[], &opts("traces.json", None));
for needle in [
"findRealFindingForCorrelation",
"endpointSeverity",
"correlationSeverity",
] {
assert!(
html.contains(needle),
"correlation mechanism missing: {needle}"
);
}
assert!(
html.contains("ps-corr-side-link"),
"per-side click zones must exist"
);
}
#[test]
fn template_pins_the_whole_trace_highlight_and_escape_decisions() {
let report = minimal_report(vec![]);
let (html, _) = render(&report, &[], &opts("traces.json", None));
let table_start = html
.find("var WHOLE_TRACE_HILITE")
.expect("the whole-trace highlight table must exist");
let table = &html[table_start..];
let table = &table[..table.find("};").expect("unterminated table")];
assert!(
!table.contains("serialized_calls"),
"serialized_calls must not carry an event-type highlight rule"
);
assert!(
html.contains(r#"querySelector(".ps-span.hilite")"#),
"the stand-down must read the rendered rows"
);
assert!(
html.contains(r#"querySelector("dialog[open]")"#),
"the Escape ladder must cover every modal, not just one"
);
assert!(
html.contains(r##"querySelectorAll("#tabs [role=\"tab\"]")"##),
"switchTab must not stamp tab semantics on disabled entries"
);
}
#[test]
fn help_icons_open_a_plain_language_method_sheet() {
let report = minimal_report(vec![]);
let (html, _) = render(&report, &[], &opts("traces.json", None));
for needle in [
"var HELP_DEEP",
r#"id="topic-help""#,
"What it measures",
"How it is computed",
"the classic lazy-loading",
"weighted interval scheduling",
"log-symmetric bracket",
"https://perf-sentinel.dev/docs/METHODOLOGY.html",
"https://perf-sentinel.dev/docs/ENERGY.html",
"Full methodology",
"Click the ? for the full method sheet",
] {
assert!(html.contains(needle), "method sheet missing: {needle}");
}
}
#[test]
fn tooltip_drops_below_its_anchor_when_centering_lacks_room() {
let report = minimal_report(vec![]);
let (html, _) = render(&report, &[], &opts("traces.json", None));
assert!(
html.contains("ps-tipbox-below"),
"the below-anchor tooltip mode must exist"
);
assert!(
html.contains("--tip-caret-x"),
"the caret must aim at the anchor in below mode"
);
}
#[test]
fn green_panel_carries_broker_waste_card() {
let report = minimal_report(vec![]);
let (html, _) = render(&report, &[], &opts("traces.json", None));
assert!(
html.contains("greenSummary.messaging_waste"),
"green panel must read messaging_waste"
);
assert!(
html.contains("Broker waste"),
"the broker card needs its own label, distinct from the database one"
);
assert!(
html.contains("% messaging ·"),
"the subtitle must state the messaging ratio"
);
}
#[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(),
4,
"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,
..ScoringConfig::default()
},
),
];
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 (trimmed to fit the size budget)"),
"trimmed-traces 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(),
grouping_key: None,
grouping_value: None,
},
target: CorrelationEndpoint {
finding_type: FindingType::SlowHttp,
service: "payment-svc".to_string(),
template: "POST /api/charge".to_string(),
grouping_key: None,
grouping_value: None,
},
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""#));
}
fn serialized_chain(
trace_id: &str,
parent: &str,
prefix: &str,
base_ms: u32,
) -> Vec<NormalizedEvent> {
let mut out = vec![span(
trace_id,
parent,
Some("root"),
"svc",
"/ep",
"GET /ep",
)];
for (n, offset) in [0_u32, 20, 40, 60].into_iter().enumerate() {
let id = format!("{prefix}{n}");
let mut s = span(
trace_id,
&id,
Some(parent),
"svc",
"/ep",
&format!("SELECT {id}"),
);
s.event.timestamp = format!("2026-04-21T00:00:{:02}.{:03}Z", base_ms / 1000, offset);
s.event.duration_us = 10_000;
out.push(s);
}
let mut straddle = span(
trace_id,
&format!("{prefix}x"),
Some(parent),
"svc",
"/ep",
"SELECT straddle",
);
straddle.event.timestamp = format!("2026-04-21T00:00:{:02}.005Z", base_ms / 1000);
straddle.event.duration_us = 60_000;
out.push(straddle);
out
}
fn serialized_report(trace: &Trace, min_sequential: u32) -> Report {
let indices = crate::detect::TraceIndices::build(trace);
let mut findings =
crate::detect::serialized::detect_serialized(trace, &indices, min_sequential);
crate::acknowledgments::enrich_with_signatures(&mut findings);
let mut report = minimal_report(findings);
report.detection_config = Some(crate::detect::DetectConfig {
serialized_min_sequential: min_sequential,
..crate::detect::DetectConfig::default()
});
report
}
fn culprit_map(html: &str) -> serde_json::Value {
let value: serde_json::Value =
serde_json::from_str(&extract_payload_json(html)).expect("payload parses");
value
.get("culprit_spans")
.cloned()
.unwrap_or(serde_json::Value::Null)
}
fn culprit_key_of(f: &Finding) -> String {
culprit_key(
&f.trace_id,
f.effective_grouping(),
&f.signature,
&f.first_timestamp,
&f.last_timestamp,
)
}
#[test]
fn culprit_spans_name_the_serialized_chain_not_the_whole_trace() {
let trace = Trace {
trace_id: "t1".into(),
spans: serialized_chain("t1", "p1", "c", 0),
};
let report = serialized_report(&trace, 3);
assert_eq!(report.findings.len(), 1, "one serialized finding expected");
let key = culprit_key_of(&report.findings[0]);
let html = render(&report, std::slice::from_ref(&trace), &opts("-", None)).0;
let map = culprit_map(&html);
let ids: Vec<&str> = map[&key]
.as_array()
.expect("the chain is published under its finding key")
.iter()
.map(|v| v.as_str().unwrap())
.collect();
assert_eq!(ids, vec!["c0", "c1", "c2", "c3"]);
}
#[test]
fn two_chains_sharing_a_signature_keep_their_own_spans() {
let mut spans = serialized_chain("t1", "p1", "a", 0);
spans.extend(serialized_chain("t1", "p2", "b", 10_000));
let trace = Trace {
trace_id: "t1".into(),
spans,
};
let report = serialized_report(&trace, 3);
assert_eq!(report.findings.len(), 2, "one finding per parent");
let signatures: HashSet<&str> = report
.findings
.iter()
.map(|f| f.signature.as_str())
.collect();
assert_eq!(signatures.len(), 1, "the two findings share a signature");
let html = render(&report, std::slice::from_ref(&trace), &opts("-", None)).0;
let map = culprit_map(&html);
for f in &report.findings {
let ids: Vec<&str> = map[&culprit_key_of(f)]
.as_array()
.expect("each finding keeps its own entry")
.iter()
.map(|v| v.as_str().unwrap())
.collect();
assert_eq!(ids.len(), 4, "four spans, not the eight of both chains");
let prefix = ids[0].chars().next().unwrap();
assert!(
ids.iter().all(|id| id.starts_with(prefix)),
"spans of one parent only, got {ids:?}"
);
}
}
#[test]
fn culprit_spans_name_every_child_of_a_fanout_parent() {
let mut spans = vec![span("t1", "root", None, "svc", "/ep", "GET /ep")];
for i in 0..6 {
spans.push(span(
"t1",
&format!("kid{i}"),
Some("root"),
"svc",
"/ep",
&format!("SELECT {i}"),
));
}
let trace = Trace {
trace_id: "t1".into(),
spans,
};
let indices = crate::detect::TraceIndices::build(&trace);
let mut findings = crate::detect::fanout::detect_fanout(&trace, &indices, 3);
assert_eq!(findings.len(), 1, "one fanout finding expected");
crate::acknowledgments::enrich_with_signatures(&mut findings);
let mut report = minimal_report(findings);
report.detection_config = Some(crate::detect::DetectConfig {
max_fanout: 3,
..crate::detect::DetectConfig::default()
});
let html = render(&report, std::slice::from_ref(&trace), &opts("-", None)).0;
let map = culprit_map(&html);
let ids: Vec<&str> = map[&culprit_key_of(&report.findings[0])]
.as_array()
.expect("the children are published under the finding key")
.iter()
.map(|v| v.as_str().unwrap())
.collect();
assert_eq!(ids, vec!["kid0", "kid1", "kid2", "kid3", "kid4", "kid5"]);
assert!(
!ids.contains(&"root"),
"the parent is context, not a culprit"
);
}
#[test]
fn culprit_spans_name_only_the_exact_redundant_parameter_group() {
let mut spans = Vec::new();
for i in 0..4 {
let mut duplicate = span("t1", &format!("dup{i}"), None, "svc", "/ep", "SELECT ?");
duplicate.event.timestamp = format!("2026-04-21T00:00:00.0{i}0Z");
duplicate.params = vec!["42".into()];
spans.push(duplicate);
}
let mut context = span("t1", "context", None, "svc", "/ep", "SELECT ?");
context.event.timestamp = "2026-04-21T00:00:00.040Z".into();
context.params = vec!["7".into()];
spans.push(context);
let trace = Trace {
trace_id: "t1".into(),
spans,
};
let mut findings = crate::detect::redundant::detect_redundant(&trace, &[]);
crate::acknowledgments::enrich_with_signatures(&mut findings);
let mut report = minimal_report(findings);
report.detection_config = Some(crate::detect::DetectConfig::default());
let html = render(&report, std::slice::from_ref(&trace), &opts("-", None)).0;
let map = culprit_map(&html);
let ids: Vec<&str> = map[&culprit_key_of(&report.findings[0])]
.as_array()
.expect("the exact redundant group is published")
.iter()
.map(|value| value.as_str().unwrap())
.collect();
assert_eq!(ids, vec!["dup0", "dup1", "dup2", "dup3"]);
}
#[test]
fn culprit_spans_name_only_the_pool_peak() {
let mut spans = Vec::new();
for (id, timestamp) in [
("s0", "2026-04-21T00:00:00.000Z"),
("s1", "2026-04-21T00:00:00.050Z"),
("s2", "2026-04-21T00:00:00.120Z"),
] {
let mut query = span("t1", id, None, "svc", "/ep", "SELECT ?");
query.event.timestamp = timestamp.into();
query.event.duration_us = 100_000;
spans.push(query);
}
let trace = Trace {
trace_id: "t1".into(),
spans,
};
let mut findings = crate::detect::pool_saturation::detect_pool_saturation(&trace, 2);
crate::acknowledgments::enrich_with_signatures(&mut findings);
let mut report = minimal_report(findings);
report.detection_config = Some(crate::detect::DetectConfig {
pool_saturation_concurrent_threshold: 2,
..crate::detect::DetectConfig::default()
});
let html = render(&report, std::slice::from_ref(&trace), &opts("-", None)).0;
let map = culprit_map(&html);
let ids: Vec<&str> = map[&culprit_key_of(&report.findings[0])]
.as_array()
.expect("the peak concurrency set is published")
.iter()
.map(|value| value.as_str().unwrap())
.collect();
assert_eq!(ids, vec!["s0", "s1"]);
}
#[test]
fn culprit_spans_name_every_n_plus_one_occurrence() {
let mut spans = Vec::new();
for i in 0..5 {
let mut query = span("t1", &format!("q{i}"), None, "svc", "/ep", "SELECT ?");
query.event.timestamp = format!("2026-04-21T00:00:00.0{i}0Z");
query.params = vec![i.to_string()];
spans.push(query);
}
let trace = Trace {
trace_id: "t1".into(),
spans,
};
let mut findings = crate::detect::n_plus_one::detect_n_plus_one(
&trace,
5,
500,
crate::detect::sanitizer_aware::SanitizerAwareMode::Auto,
);
crate::acknowledgments::enrich_with_signatures(&mut findings);
let mut report = minimal_report(findings);
report.detection_config = Some(crate::detect::DetectConfig::default());
let html = render(&report, std::slice::from_ref(&trace), &opts("-", None)).0;
let map = culprit_map(&html);
let ids: Vec<&str> = map[&culprit_key_of(&report.findings[0])]
.as_array()
.expect("all N+1 occurrences are published")
.iter()
.map(|value| value.as_str().unwrap())
.collect();
assert_eq!(ids, vec!["q0", "q1", "q2", "q3", "q4"]);
}
#[test]
fn n_plus_one_evidence_stays_inside_its_grouping_identity() {
let mut spans = Vec::new();
for (key, prefix) in [("tenant.id", "tenant"), ("k8s.namespace.name", "k8s")] {
for i in 0..5 {
let mut query = span(
"t1",
&format!("{prefix}-{i}"),
None,
"svc",
"/ep",
"SELECT ?",
);
query.event.timestamp = format!("2026-04-21T00:00:00.0{i}0Z");
query.event.grouping = crate::test_helpers::grouping(key, "prod");
query.params = vec![i.to_string()];
spans.push(query);
}
}
let trace = Trace {
trace_id: "t1".into(),
spans,
};
let mut findings = crate::detect::n_plus_one::detect_n_plus_one(
&trace,
5,
500,
crate::detect::sanitizer_aware::SanitizerAwareMode::Auto,
);
crate::acknowledgments::enrich_with_signatures(&mut findings);
assert_eq!(findings.len(), 2, "{findings:#?}");
let mut report = minimal_report(findings);
report.detection_config = Some(crate::detect::DetectConfig::default());
let html = render(&report, std::slice::from_ref(&trace), &opts("-", None)).0;
let map = culprit_map(&html);
for finding in &report.findings {
let ids = map[&culprit_key_of(finding)].as_array().unwrap();
assert_eq!(ids.len(), 5, "{finding:#?}: {ids:#?}");
let expected_prefix = match finding
.effective_grouping()
.map(|g| g.key.as_ref())
.unwrap()
{
"tenant.id" => "tenant-",
"k8s.namespace.name" => "k8s-",
key => panic!("unexpected grouping key {key}"),
};
assert!(
ids.iter()
.all(|id| id.as_str().unwrap().starts_with(expected_prefix))
);
}
}
#[test]
fn cross_trace_slow_evidence_stays_inside_its_namespace() {
let slow_span =
|trace_id: &str, span_id: &str, namespace: &str, timestamp: &str, duration_us: u64| {
let mut event = span(trace_id, span_id, None, "svc", "/ep", "SELECT ?");
event.event.grouping = crate::test_helpers::k8s_grouping(namespace);
event.event.timestamp = timestamp.into();
event.event.duration_us = duration_us;
event
};
let traces = vec![
Trace {
trace_id: "t1".into(),
spans: vec![
slow_span("t1", "prod-1", "prod", "2026-04-21T00:00:00.000Z", 600_000),
slow_span(
"t1",
"stage-1",
"staging",
"2026-04-21T00:00:00.000Z",
610_000,
),
],
},
Trace {
trace_id: "t2".into(),
spans: vec![
slow_span("t2", "prod-2", "prod", "2026-04-21T00:00:00.100Z", 700_000),
slow_span("t2", "prod-3", "prod", "2026-04-21T00:00:00.200Z", 800_000),
slow_span(
"t2",
"stage-2",
"staging",
"2026-04-21T00:00:00.100Z",
710_000,
),
slow_span(
"t2",
"stage-3",
"staging",
"2026-04-21T00:00:00.200Z",
810_000,
),
],
},
];
let mut findings = crate::detect::slow::detect_slow_cross_trace(&traces, 500, 3);
crate::acknowledgments::enrich_with_signatures(&mut findings);
assert_eq!(findings.len(), 2);
assert_eq!(findings[0].signature, findings[1].signature);
assert_eq!(findings[0].first_timestamp, findings[1].first_timestamp);
assert_eq!(findings[0].last_timestamp, findings[1].last_timestamp);
let mut report = minimal_report(findings);
report.detection_config = Some(crate::detect::DetectConfig {
slow_threshold_ms: 500,
slow_min_occurrences: 3,
..crate::detect::DetectConfig::default()
});
let html = render(&report, &traces, &opts("-", None)).0;
let map = culprit_map(&html);
assert_eq!(map.as_object().unwrap().len(), 2);
for finding in &report.findings {
let ids: Vec<&str> = map[&culprit_key_of(finding)]
.as_array()
.unwrap()
.iter()
.map(|value| value.as_str().unwrap())
.collect();
match finding.grouping_value().unwrap() {
"prod" => assert_eq!(ids, vec!["prod-2", "prod-3"]),
"staging" => assert_eq!(ids, vec!["stage-2", "stage-3"]),
namespace => panic!("unexpected namespace {namespace}"),
}
}
}
#[test]
fn culprit_spans_are_withheld_without_a_detection_config() {
let trace = Trace {
trace_id: "t1".into(),
spans: serialized_chain("t1", "p1", "c", 0),
};
let mut report = serialized_report(&trace, 3);
assert_eq!(report.findings.len(), 1, "the chain is there to be found");
report.detection_config = None;
let html = render(&report, std::slice::from_ref(&trace), &opts("-", None)).0;
assert!(culprit_map(&html).is_null());
}
#[test]
fn culprit_spans_skip_a_finding_the_report_no_longer_carries() {
let trace = Trace {
trace_id: "t1".into(),
spans: serialized_chain("t1", "p1", "c", 0),
};
let mut report = serialized_report(&trace, 3);
report.findings.clear();
let html = render(&report, std::slice::from_ref(&trace), &opts("-", None)).0;
assert!(culprit_map(&html).is_null());
}
#[test]
fn report_carried_traces_render_when_no_trace_is_handed_over() {
let f = finding("t1", "svc", "/ep", "select 1");
let mut report = minimal_report(vec![f]);
report.embedded_traces = vec![EmbeddedTrace::from_trace(&Trace {
trace_id: "t1".into(),
spans: vec![span("t1", "s1", None, "svc", "/ep", "select 1")],
})];
let (html, stats) = render(&report, &[], &opts("daemon.json", None));
let payload: serde_json::Value =
serde_json::from_str(&extract_payload_json(&html)).expect("payload parses");
let embedded = payload["embedded_traces"]
.as_array()
.expect("embedded_traces array");
assert_eq!(embedded.len(), 1, "the report's own trace must be embedded");
assert_eq!(embedded[0]["trace_id"], "t1");
assert_eq!(stats.kept, 1);
assert!(
payload["report"]["embedded_traces"].is_null(),
"the embedded report must not repeat the spans"
);
}
#[test]
fn report_carried_traces_of_hidden_findings_do_not_ship() {
let f = finding("t-visible", "svc", "/ep", "select 1");
let mut report = minimal_report(vec![f]);
report.embedded_traces = vec![
EmbeddedTrace::from_trace(&Trace {
trace_id: "t-visible".into(),
spans: vec![span("t-visible", "s1", None, "svc", "/ep", "select 1")],
}),
EmbeddedTrace::from_trace(&Trace {
trace_id: "t-acked".into(),
spans: vec![span(
"t-acked",
"s2",
None,
"svc",
"/secret",
"select secret",
)],
}),
];
let (html, _) = render(&report, &[], &opts("snapshot.json", None));
let payload: serde_json::Value =
serde_json::from_str(&extract_payload_json(&html)).expect("payload parses");
let ids: Vec<&str> = payload["embedded_traces"]
.as_array()
.expect("array")
.iter()
.map(|t| t["trace_id"].as_str().unwrap())
.collect();
assert_eq!(ids, vec!["t-visible"]);
assert!(
!html.contains("select secret"),
"a hidden finding's spans must not be greppable in the HTML"
);
}
#[test]
fn report_carried_traces_honor_the_explicit_cap() {
let findings: Vec<Finding> = (0..3)
.map(|i| finding(&format!("t{i}"), "svc", "/ep", "select 1"))
.collect();
let mut report = minimal_report(findings);
report.embedded_traces = (0..3)
.map(|i| {
EmbeddedTrace::from_trace(&Trace {
trace_id: format!("t{i}"),
spans: vec![span(&format!("t{i}"), "s1", None, "svc", "/ep", "select 1")],
})
})
.collect();
let (html, stats) = render(&report, &[], &opts("snapshot.json", Some(1)));
let payload: serde_json::Value =
serde_json::from_str(&extract_payload_json(&html)).expect("payload parses");
assert_eq!(payload["embedded_traces"].as_array().unwrap().len(), 1);
assert_eq!(stats.kept, 1);
assert_eq!(stats.total, 3);
assert_eq!(payload["trimmed_traces"]["kept"], 1);
assert_eq!(payload["trimmed_traces"]["total"], 3);
}
#[test]
fn borrowed_size_probe_matches_owned_serialization() {
let trace = Trace {
trace_id: "t1".into(),
spans: vec![span("t1", "s1", Some("s0"), "svc", "/ep", "select 1")],
};
let owned = serde_json::to_string(&EmbeddedTrace::from_trace(&trace)).unwrap();
let borrowed = serde_json::to_string(&embed_trace_ref(&trace)).unwrap();
assert_eq!(owned, borrowed);
}
#[test]
fn handed_over_traces_win_over_the_report_copy() {
let f = finding("t1", "svc", "/ep", "select 1");
let mut report = minimal_report(vec![f]);
report.embedded_traces = vec![EmbeddedTrace::from_trace(&Trace {
trace_id: "stale".into(),
spans: vec![span("stale", "s9", None, "svc", "/ep", "select 1")],
})];
let live = Trace {
trace_id: "t1".into(),
spans: vec![span("t1", "s1", None, "svc", "/ep", "select 1")],
};
let (html, _) = render(&report, &[live], &opts("-", None));
let payload: serde_json::Value =
serde_json::from_str(&extract_payload_json(&html)).expect("payload parses");
let embedded = payload["embedded_traces"]
.as_array()
.expect("embedded_traces array");
assert_eq!(embedded.len(), 1);
assert_eq!(
embedded[0]["trace_id"], "t1",
"the traces the caller handed over take precedence"
);
}
#[test]
fn culprit_key_length_prefix_keeps_namespaces_unambiguous() {
let prod = crate::event::GroupingAttribute {
key: "k8s.namespace.name".into(),
value: "prod".into(),
};
let separated = crate::event::GroupingAttribute {
key: "k8s.namespace.name".into(),
value: "a|b".into(),
};
let shorter = crate::event::GroupingAttribute {
key: "k8s.namespace.name".into(),
value: "a".into(),
};
let unicode = crate::event::GroupingAttribute {
key: "k8s.namespace.name".into(),
value: "prod-eu-café".into(),
};
let base = culprit_key("t1", None, "sig", "first", "last");
let with_ns = culprit_key("t1", Some(&prod), "sig", "first", "last");
assert_eq!(base, "t1|sig|first|last");
assert_eq!(with_ns, "t1|sig|first|last|18:k8s.namespace.name|4:prod");
assert_ne!(
culprit_key("t1", Some(&separated), "sig", "first", "last"),
culprit_key("t1", Some(&shorter), "sig", "first", "last|b"),
);
assert_eq!(
culprit_key("t1", Some(&unicode), "sig", "first", "last"),
"t1|sig|first|last|18:k8s.namespace.name|13:prod-eu-café",
"the prefix counts UTF-8 bytes, not characters"
);
}
#[test]
fn culprit_key_separates_equal_values_from_different_grouping_keys() {
let mut tenant = finding("t1", "svc", "/ep", "SELECT 1");
tenant.grouping = crate::test_helpers::grouping("tenant.id", "prod");
let mut namespace = tenant.clone();
namespace.grouping = crate::test_helpers::grouping("k8s.namespace.name", "prod");
assert_ne!(culprit_key_of(&tenant), culprit_key_of(&namespace));
}
#[test]
fn report_warnings_reach_the_embedded_payload() {
let mut report = minimal_report(vec![]);
report.warning_details = vec![crate::report::Warning::new(
crate::report::warnings::TUNING,
"[green.alumet] configured but this is a batch run",
)];
let (html, _) = render(&report, &[], &opts("-", 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("payload parses");
assert_eq!(value["report"]["warning_details"][0]["kind"], "tuning");
assert!(
value["report"]["warning_details"][0]["message"]
.as_str()
.is_some_and(|m| m.contains("[green.alumet]"))
);
}
#[test]
fn template_carries_the_report_warnings_banner() {
assert!(TEMPLATE.contains(r#"id="report-warnings""#));
assert!(TEMPLATE.contains("renderReportWarnings()"));
}