use crate::correlate::Trace;
use crate::diff::DiffReport;
use crate::event::EventType;
use crate::ingest::pg_stat::PgStatReport;
use crate::normalize::NormalizedEvent;
use crate::report::Report;
use serde::Serialize;
use std::collections::{HashMap, HashSet};
use std::path::Path;
const TEMPLATE: &str = include_str!("html_template.html");
const JSON_PLACEHOLDER: &str = "{{REPORT_JSON}}";
const TITLE_PLACEHOLDER: &str = "{{PAGE_TITLE}}";
const CSP_PLACEHOLDER: &str = "{{CONTENT_SECURITY_POLICY}}";
const BRAND_LOGO_PLACEHOLDER: &str = "{{BRAND_LOGO}}";
const FONT_FACES_PLACEHOLDER: &str = "{{FONT_FACES}}";
const DEFAULT_TITLE: &str = "perf-sentinel report";
const FONT_FACES: &str = include_str!("fonts.css");
const BRAND_LOGO_LIGHT_SVG: &str = include_str!("logo-horiz-light.svg");
const BRAND_LOGO_DARK_SVG: &str = include_str!("logo-horiz-dark.svg");
const DEFAULT_SIZE_TARGET_BYTES: usize = 5 * 1024 * 1024;
const STATIC_CSP: &str = "default-src 'none'; script-src 'unsafe-inline'; \
style-src 'unsafe-inline'; img-src data:; \
font-src data:; base-uri 'none'; form-action 'none'";
const fn assert_no_double_brace(s: &str) {
let bytes = s.as_bytes();
let mut i = 0;
while i + 1 < bytes.len() {
assert!(
!(bytes[i] == b'{' && bytes[i + 1] == b'{'),
"embedded asset must not contain `{{{{`, it would shadow placeholder substitution"
);
i += 1;
}
}
const _: () = {
assert_no_double_brace(STATIC_CSP);
assert_no_double_brace(BRAND_LOGO_LIGHT_SVG);
assert_no_double_brace(BRAND_LOGO_DARK_SVG);
assert_no_double_brace(FONT_FACES);
};
const PAYLOAD_VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(Debug, Clone)]
pub struct RenderOptions {
pub input_label: String,
pub max_traces_embedded: Option<usize>,
pub pg_stat: Option<PgStatReport>,
pub diff: Option<DiffReport>,
pub daemon_url: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RenderStats {
pub kept: usize,
pub total: usize,
}
#[must_use]
pub fn render(report: &Report, traces: &[Trace], options: &RenderOptions) -> (String, RenderStats) {
if let Some(url) = options.daemon_url.as_deref()
&& let Some(rest) = url.strip_prefix("http://")
{
let host_only = rest.split(['/', ':']).next().unwrap_or("");
let is_loopback =
host_only == "localhost" || host_only == "127.0.0.1" || host_only == "[::1]";
if !is_loopback {
tracing::warn!(
daemon_url = url,
"http:// daemon URL on a non-loopback host: ack/revoke fetches will be blocked when the report is served over https://"
);
}
}
let sanitized_label = sanitize_input_label(&options.input_label);
let (report_embed, trimmed_findings) = slim_report_for_embed(report, options);
let payload = build_payload_with_label(
&report_embed,
&report.green_summary.top_offenders,
traces,
options,
&sanitized_label,
trimmed_findings,
);
let kept = payload.embedded_traces.len();
let total = payload.trimmed_traces.as_ref().map_or(kept, |s| s.total);
let json = serde_json::to_string(&payload).expect("payload always serializes");
let title = derive_page_title(&sanitized_label);
let csp = build_csp(options.daemon_url.as_deref());
let html = inject(&json, &title, &csp);
(html, RenderStats { kept, total })
}
pub fn write(
report: &Report,
traces: &[Trace],
options: &RenderOptions,
output: &Path,
) -> std::io::Result<()> {
let (html, _stats) = render(report, traces, options);
std::fs::write(output, html)
}
#[derive(Debug, Serialize)]
struct Payload<'a> {
version: &'static str,
input_label: &'a str,
report: &'a Report,
embedded_traces: Vec<EmbeddedTrace<'a>>,
#[serde(skip_serializing_if = "Option::is_none")]
trimmed_traces: Option<TrimSummary>,
#[serde(skip_serializing_if = "Option::is_none")]
trimmed_findings: Option<TrimSummary>,
#[serde(skip_serializing_if = "Option::is_none")]
pg_stat: Option<&'a PgStatReport>,
#[serde(skip_serializing_if = "Option::is_none")]
diff: Option<&'a DiffReport>,
#[serde(skip_serializing_if = "Option::is_none")]
daemon: Option<DaemonHandle<'a>>,
}
#[derive(Debug, Serialize)]
struct DaemonHandle<'a> {
url: &'a str,
}
#[derive(Debug, Serialize)]
struct EmbeddedTrace<'a> {
trace_id: &'a str,
spans: Vec<EmbeddedSpan<'a>>,
}
#[derive(Debug, Serialize)]
struct EmbeddedSpan<'a> {
span_id: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
parent_span_id: Option<&'a str>,
service: &'a str,
endpoint: &'a str,
event_type: &'static str,
operation: &'a str,
template: &'a str,
duration_us: u64,
#[serde(skip_serializing_if = "Option::is_none")]
status_code: Option<u16>,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
struct TrimSummary {
kept: usize,
total: usize,
}
fn inject(json: &str, title: &str, csp: &str) -> String {
assert!(
!csp.contains("{{"),
"CSP must not contain `{{{{` placeholder bytes, got: {csp}"
);
let safe = json.replace("</", "<\\/");
let brand_logo = format!(
"<span class=\"ps-logo ps-logo-light\">{BRAND_LOGO_LIGHT_SVG}</span>\
<span class=\"ps-logo ps-logo-dark\">{BRAND_LOGO_DARK_SVG}</span>"
);
TEMPLATE
.replacen(FONT_FACES_PLACEHOLDER, FONT_FACES, 1)
.replacen(BRAND_LOGO_PLACEHOLDER, &brand_logo, 1)
.replacen(JSON_PLACEHOLDER, &safe, 1)
.replacen(CSP_PLACEHOLDER, csp, 1)
.replacen(TITLE_PLACEHOLDER, title, 1)
}
#[must_use]
fn build_csp(daemon_url: Option<&str>) -> String {
match daemon_url {
Some(url) => format!("{STATIC_CSP}; connect-src 'self' {url}"),
None => STATIC_CSP.to_string(),
}
}
fn derive_page_title(input_label: &str) -> String {
let trimmed = input_label.trim();
if trimmed.is_empty() || trimmed == "-" {
return DEFAULT_TITLE.to_string();
}
let filename = Path::new(trimmed)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(trimmed);
format!("perf-sentinel: {}", html_escape_text(filename))
}
fn sanitize_input_label(input_label: &str) -> String {
input_label
.chars()
.filter(|c| !c.is_control() && !is_unsafe_format_char(*c))
.collect()
}
fn html_escape_text(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for c in s.chars() {
match c {
'&' => out.push_str("&"),
'<' => out.push_str("<"),
'>' => out.push_str(">"),
'"' => out.push_str("""),
'\'' => out.push_str("'"),
c if c.is_control() || is_unsafe_format_char(c) => {}
_ => out.push(c),
}
}
out
}
fn is_unsafe_format_char(c: char) -> bool {
matches!(
c,
'\u{200E}' | '\u{200F}' | '\u{2028}' | '\u{2029}' | '\u{202A}'..='\u{202E}' | '\u{2066}'..='\u{2069}' | '\u{FEFF}' )
}
fn build_payload_with_label<'a>(
report: &'a Report,
full_top_offenders: &[crate::report::TopOffender],
traces: &'a [Trace],
options: &'a RenderOptions,
input_label: &'a str,
trimmed_findings: Option<TrimSummary>,
) -> Payload<'a> {
let ordered = order_candidates_by_iis(&report.findings, full_top_offenders, traces);
let total = ordered.len();
let (kept_refs, trimmed) = if let Some(cap) = options.max_traces_embedded {
let take = cap.min(total);
let summary = if take < total {
Some(TrimSummary { kept: take, total })
} else {
None
};
(ordered.into_iter().take(take).collect::<Vec<_>>(), summary)
} else {
trim_to_size_target(
ordered,
report,
options,
input_label,
trimmed_findings.clone(),
)
};
let embedded_traces = kept_refs.iter().copied().map(embed_trace).collect();
Payload {
version: PAYLOAD_VERSION,
input_label,
report,
embedded_traces,
trimmed_traces: trimmed,
trimmed_findings,
pg_stat: options.pg_stat.as_ref(),
diff: options.diff.as_ref(),
daemon: options
.daemon_url
.as_deref()
.map(|url| DaemonHandle { url }),
}
}
fn order_candidates_by_iis<'a>(
findings: &[crate::detect::Finding],
top_offenders: &[crate::report::TopOffender],
traces: &'a [Trace],
) -> Vec<&'a Trace> {
let finding_trace_ids: HashSet<&str> = findings.iter().map(|f| f.trace_id.as_str()).collect();
let mut rank: HashMap<(&str, &str), usize> = HashMap::new();
for (i, off) in top_offenders.iter().enumerate() {
rank.insert((off.service.as_str(), off.endpoint.as_str()), i);
}
let mut scored: Vec<(usize, &'a Trace)> = traces
.iter()
.filter(|t| finding_trace_ids.contains(t.trace_id.as_str()))
.map(|t| (trace_rank(t, &rank), t))
.collect();
scored.sort_by_key(|(score, _)| *score);
scored.into_iter().map(|(_, t)| t).collect()
}
fn trace_rank(trace: &Trace, rank: &HashMap<(&str, &str), usize>) -> usize {
trace
.spans
.iter()
.map(|s| {
rank.get(&(s.event.service.as_ref(), s.event.source.endpoint.as_str()))
.copied()
.unwrap_or(usize::MAX)
})
.min()
.unwrap_or(usize::MAX)
}
const FINDINGS_BUDGET_SHARE_PCT: usize = 70;
const TOP_OFFENDERS_EMBED_CAP: usize = 25;
fn slim_report_for_embed(
report: &Report,
options: &RenderOptions,
) -> (Report, Option<TrimSummary>) {
let (findings, trimmed_findings) = select_embedded_findings(report, options);
let mut green_summary = report.green_summary.clone();
green_summary
.top_offenders
.truncate(TOP_OFFENDERS_EMBED_CAP);
let embed = Report {
analysis: report.analysis.clone(),
findings,
green_summary,
quality_gate: report.quality_gate.clone(),
per_endpoint_io_ops: Vec::new(),
correlations: report.correlations.clone(),
warnings: report.warnings.clone(),
warning_details: report.warning_details.clone(),
acknowledged_findings: report.acknowledged_findings.clone(),
binary_version: report.binary_version.clone(),
disclosure_waste: report.disclosure_waste.clone(),
};
(embed, trimmed_findings)
}
fn select_embedded_findings(
report: &Report,
options: &RenderOptions,
) -> (Vec<crate::detect::Finding>, Option<TrimSummary>) {
if options.max_traces_embedded.is_some() {
return (report.findings.clone(), None);
}
let json_budget = DEFAULT_SIZE_TARGET_BYTES.saturating_sub(TEMPLATE.len());
let findings_budget = json_budget * FINDINGS_BUDGET_SHARE_PCT / 100;
let sizes: Vec<usize> = report
.findings
.iter()
.map(|f| serde_json::to_string(f).map_or(usize::MAX, |s| s.len()))
.collect();
let total_len = sizes
.iter()
.fold(2usize, |acc, len| acc.saturating_add(len.saturating_add(1)));
if total_len <= findings_budget {
return (report.findings.clone(), None);
}
let mut order: Vec<usize> = (0..report.findings.len()).collect();
order.sort_by_key(|&i| &report.findings[i].severity);
let mut running = 2usize; let mut keep: Vec<usize> = Vec::new();
for &i in &order {
let next = running.saturating_add(sizes[i].saturating_add(1));
if next > findings_budget {
break;
}
running = next;
keep.push(i);
}
keep.sort_unstable();
let summary = TrimSummary {
kept: keep.len(),
total: report.findings.len(),
};
let kept = keep
.into_iter()
.map(|i| report.findings[i].clone())
.collect();
(kept, Some(summary))
}
fn trim_to_size_target<'a>(
ordered: Vec<&'a Trace>,
report: &Report,
options: &'a RenderOptions,
input_label: &'a str,
trimmed_findings: Option<TrimSummary>,
) -> (Vec<&'a Trace>, Option<TrimSummary>) {
let total = ordered.len();
let per_trace_lens: Vec<usize> = ordered
.iter()
.copied()
.map(|t| serde_json::to_string(&embed_trace(t)).map_or(usize::MAX, |s| s.len()))
.collect();
let envelope = Payload {
version: PAYLOAD_VERSION,
input_label,
report,
embedded_traces: Vec::new(),
trimmed_traces: Some(TrimSummary { kept: 0, total }),
trimmed_findings,
pg_stat: options.pg_stat.as_ref(),
diff: options.diff.as_ref(),
daemon: options
.daemon_url
.as_deref()
.map(|url| DaemonHandle { url }),
};
let envelope_len = serde_json::to_string(&envelope).map_or(usize::MAX, |s| s.len());
let json_budget = DEFAULT_SIZE_TARGET_BYTES.saturating_sub(TEMPLATE.len());
let mut running = envelope_len;
let mut keep_count: usize = 0;
for &len in &per_trace_lens {
let delta = len.saturating_add(1);
let next = running.saturating_add(delta);
if next > json_budget {
break;
}
running = next;
keep_count += 1;
}
let kept: Vec<&'a Trace> = ordered.into_iter().take(keep_count).collect();
let trimmed = if kept.len() < total {
Some(TrimSummary {
kept: kept.len(),
total,
})
} else {
None
};
(kept, trimmed)
}
fn embed_trace(t: &Trace) -> EmbeddedTrace<'_> {
EmbeddedTrace {
trace_id: t.trace_id.as_str(),
spans: t.spans.iter().map(embed_span).collect(),
}
}
fn embed_span(e: &NormalizedEvent) -> EmbeddedSpan<'_> {
EmbeddedSpan {
span_id: e.event.span_id.as_str(),
parent_span_id: e.event.parent_span_id.as_deref(),
service: e.event.service.as_ref(),
endpoint: e.event.source.endpoint.as_str(),
event_type: match e.event.event_type {
EventType::Sql => "sql",
EventType::HttpOut => "http_out",
},
operation: e.event.operation.as_str(),
template: e.template.as_ref(),
duration_us: e.event.duration_us,
status_code: e.event.status_code,
}
}
#[cfg(test)]
mod tests {
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,
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", "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, 4,
"expected exactly 4 export buttons (findings, pgstat, 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,
}],
}
}
#[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-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, 7,
"expected 7 tabpanels, found {tabpanel_count}"
);
for tab in [
"overview",
"findings",
"pgstat",
"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", "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, 4,
"expected exactly 4 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""#));
}
}