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 DEFAULT_TITLE: &str = "perf-sentinel report";
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:; \
base-uri 'none'; form-action 'none'";
const _: () = {
let bytes = STATIC_CSP.as_bytes();
let mut i = 0;
while i + 1 < bytes.len() {
assert!(
!(bytes[i] == b'{' && bytes[i + 1] == b'{'),
"STATIC_CSP must not contain `{{{{`, it would shadow placeholder substitution"
);
i += 1;
}
};
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 payload = build_payload_with_label(report, traces, options, &sanitized_label);
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")]
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,
target: &'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("</", "<\\/");
TEMPLATE
.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,
traces: &'a [Trace],
options: &'a RenderOptions,
input_label: &'a str,
) -> Payload<'a> {
let ordered = order_candidates_by_iis(report, 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)
};
let embedded_traces = kept_refs.iter().copied().map(embed_trace).collect();
Payload {
version: PAYLOAD_VERSION,
input_label,
report,
embedded_traces,
trimmed_traces: trimmed,
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>(report: &Report, traces: &'a [Trace]) -> Vec<&'a Trace> {
let finding_trace_ids: HashSet<&str> = report
.findings
.iter()
.map(|f| f.trace_id.as_str())
.collect();
let mut rank: HashMap<(&str, &str), usize> = HashMap::new();
for (i, off) in report.green_summary.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)
}
fn trim_to_size_target<'a>(
ordered: Vec<&'a Trace>,
report: &Report,
options: &'a RenderOptions,
input_label: &'a str,
) -> (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 }),
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(),
target: e.event.target.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,
},
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(),
}
}
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(), 2, "fixture must yield 2 findings");
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(r#"id="quality-gate-rules""#),
"Findings tab must carry the quality gate rules host"
);
assert!(
html.contains("renderQualityGateRules"),
"renderAllPanels must call renderQualityGateRules"
);
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 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 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*', ...)"
);
}
#[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 selected finding in Explain",
"close search",
"back from Explain",
"Open filter search for active tab",
"Go to Findings",
"Go to Explain",
"Go to pg_stat",
"Go to Diff",
"Go to Correlations",
"Go to GreenOps",
"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 explain_empty_helper_is_shared_across_call_sites() {
assert!(
TEMPLATE.contains("function renderExplainEmpty("),
"renderExplainEmpty helper missing"
);
assert!(
TEMPLATE.contains("Trace not embedded (cap reached)"),
"cap-reached message missing"
);
assert!(
TEMPLATE.contains("This finding was resolved."),
"resolved-diff empty-state message missing"
);
let use_count = TEMPLATE.matches("renderExplainEmpty(").count();
assert!(
use_count >= 3,
"expected at least 3 renderExplainEmpty uses, found {use_count}"
);
}
#[test]
fn tabs_and_panels_carry_aria_roles() {
assert!(
TEMPLATE.contains("role=\"tablist\""),
"tablist role missing from template"
);
for panel in [
"panel-findings",
"panel-explain",
"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 [
"findings",
"explain",
"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("connect-src"),
"static mode must not advertise connect-src in the CSP, got: {csp_value}"
);
}
#[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""#));
}
}