use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SuppressionKind {
ExposureGap,
TestEfficiency,
}
impl SuppressionKind {
pub fn as_str(self) -> &'static str {
match self {
SuppressionKind::ExposureGap => "exposure_gap",
SuppressionKind::TestEfficiency => "test_efficiency",
}
}
pub fn from_str(value: &str) -> Option<Self> {
match value {
"exposure_gap" => Some(Self::ExposureGap),
"test_efficiency" => Some(Self::TestEfficiency),
_ => None,
}
}
pub fn supported() -> &'static [&'static str] {
&["exposure_gap", "test_efficiency"]
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SuppressionEntry {
pub kind: SuppressionKind,
pub finding_id: Option<String>,
pub test: Option<String>,
pub path: Option<String>,
pub reason: String,
pub owner: String,
pub expires: Option<String>,
pub scope: Option<String>,
pub created_at: Option<String>,
pub last_seen: Option<String>,
pub review_by: Option<String>,
pub expected_visibility: Option<String>,
pub static_class: Option<String>,
pub language: Option<String>,
pub language_status: Option<String>,
pub block_line: usize,
}
pub const SUPPRESSIONS_PATH: &str = ".ripr/suppressions.toml";
pub fn parse_suppressions_manifest(text: &str) -> (Vec<SuppressionEntry>, Vec<String>) {
let mut entries: Vec<SuppressionEntry> = Vec::new();
let mut violations = Vec::new();
let mut schema_seen = false;
let mut current: Option<PendingSuppression> = None;
for (index, line) in text.lines().enumerate() {
let line_number = index + 1;
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with('#') {
continue;
}
if trimmed == "[[suppressions]]" {
if let Some(pending) = current.take() {
finalize_suppression(pending, &mut entries, &mut violations);
}
current = Some(PendingSuppression::new(line_number));
continue;
}
let Some((key, raw)) = trimmed.split_once('=') else {
violations.push(format!(
"{SUPPRESSIONS_PATH}:{line_number} expected `key = value`"
));
continue;
};
let key = key.trim();
let raw = raw.trim();
if let Some(pending) = current.as_mut() {
match key {
"kind" => assign_field(raw, line_number, &mut violations, |parsed| {
pending.kind = Some((parsed, line_number));
}),
"finding_id" => assign_field(raw, line_number, &mut violations, |parsed| {
pending.finding_id = Some((parsed, line_number));
}),
"test" => assign_field(raw, line_number, &mut violations, |parsed| {
pending.test = Some((parsed, line_number));
}),
"path" => assign_field(raw, line_number, &mut violations, |parsed| {
pending.path = Some((parsed, line_number));
}),
"reason" => assign_field(raw, line_number, &mut violations, |parsed| {
pending.reason = Some((parsed, line_number));
}),
"owner" => assign_field(raw, line_number, &mut violations, |parsed| {
pending.owner = Some((parsed, line_number));
}),
"expires" => assign_field(raw, line_number, &mut violations, |parsed| {
pending.expires = Some((parsed, line_number));
}),
"scope" => assign_field(raw, line_number, &mut violations, |parsed| {
pending.scope = Some((parsed, line_number));
}),
"created_at" => assign_field(raw, line_number, &mut violations, |parsed| {
pending.created_at = Some((parsed, line_number));
}),
"last_seen" => assign_field(raw, line_number, &mut violations, |parsed| {
pending.last_seen = Some((parsed, line_number));
}),
"review_by" => assign_field(raw, line_number, &mut violations, |parsed| {
pending.review_by = Some((parsed, line_number));
}),
"expected_visibility" => {
assign_field(raw, line_number, &mut violations, |parsed| {
pending.expected_visibility = Some((parsed, line_number));
});
}
"static_class" => assign_field(raw, line_number, &mut violations, |parsed| {
pending.static_class = Some((parsed, line_number));
}),
"language" => assign_field(raw, line_number, &mut violations, |parsed| {
pending.language = Some((parsed, line_number));
}),
"language_status" => assign_field(raw, line_number, &mut violations, |parsed| {
pending.language_status = Some((parsed, line_number));
}),
_ => violations.push(format!(
"{SUPPRESSIONS_PATH}:{line_number} unsupported `[[suppressions]]` field `{key}`"
)),
}
} else if key == "schema_version" {
schema_seen = true;
match raw.parse::<u32>() {
Ok(1) => {}
Ok(other) => violations.push(format!(
"{SUPPRESSIONS_PATH}:{line_number} schema_version = {other} is not supported (expected 1)"
)),
Err(_) => violations.push(format!(
"{SUPPRESSIONS_PATH}:{line_number} schema_version must be an integer literal"
)),
}
} else {
violations.push(format!(
"{SUPPRESSIONS_PATH}:{line_number} unsupported top-level field `{key}`"
));
}
}
if let Some(pending) = current.take() {
finalize_suppression(pending, &mut entries, &mut violations);
}
if !schema_seen {
violations.push(format!(
"{SUPPRESSIONS_PATH} is missing required `schema_version = 1` header"
));
}
let mut seen_exposure: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
let mut seen_te: std::collections::BTreeSet<(String, Option<String>)> =
std::collections::BTreeSet::new();
for entry in &entries {
match entry.kind {
SuppressionKind::ExposureGap => {
if let Some(id) = &entry.finding_id
&& !seen_exposure.insert(id.clone())
{
violations.push(format!(
"{SUPPRESSIONS_PATH} duplicate selector finding_id `{id}` (declared near line {})",
entry.block_line
));
}
}
SuppressionKind::TestEfficiency => {
if let Some(test) = &entry.test {
let key = (test.clone(), entry.path.clone());
if !seen_te.insert(key) {
let location = match &entry.path {
Some(p) => format!("`{}` at `{}`", test, p),
None => format!("`{}`", test),
};
violations.push(format!(
"{SUPPRESSIONS_PATH} duplicate selector {location} (declared near line {})",
entry.block_line
));
}
}
}
}
}
(entries, violations)
}
struct PendingSuppression {
block_line: usize,
kind: Option<(String, usize)>,
finding_id: Option<(String, usize)>,
test: Option<(String, usize)>,
path: Option<(String, usize)>,
reason: Option<(String, usize)>,
owner: Option<(String, usize)>,
expires: Option<(String, usize)>,
scope: Option<(String, usize)>,
created_at: Option<(String, usize)>,
last_seen: Option<(String, usize)>,
review_by: Option<(String, usize)>,
expected_visibility: Option<(String, usize)>,
static_class: Option<(String, usize)>,
language: Option<(String, usize)>,
language_status: Option<(String, usize)>,
}
impl PendingSuppression {
fn new(block_line: usize) -> Self {
Self {
block_line,
kind: None,
finding_id: None,
test: None,
path: None,
reason: None,
owner: None,
expires: None,
scope: None,
created_at: None,
last_seen: None,
review_by: None,
expected_visibility: None,
static_class: None,
language: None,
language_status: None,
}
}
}
fn assign_field<F>(raw: &str, line_number: usize, violations: &mut Vec<String>, mut assign: F)
where
F: FnMut(String),
{
match parse_quoted_value(raw) {
Ok(parsed) => assign(parsed),
Err(message) => violations.push(format!("{SUPPRESSIONS_PATH}:{line_number} {message}")),
}
}
fn parse_quoted_value(raw: &str) -> Result<String, String> {
let trimmed = raw.trim();
if trimmed.len() < 2 || !trimmed.starts_with('"') || !trimmed.ends_with('"') {
return Err(format!("expected quoted string, got `{trimmed}`"));
}
Ok(trimmed[1..trimmed.len() - 1].to_string())
}
fn finalize_suppression(
pending: PendingSuppression,
entries: &mut Vec<SuppressionEntry>,
violations: &mut Vec<String>,
) {
let block_line = pending.block_line;
let kind = match pending.kind {
Some((value, line)) => match SuppressionKind::from_str(&value) {
Some(kind) => Some(kind),
None => {
violations.push(format!(
"{SUPPRESSIONS_PATH}:{line} unsupported kind `{value}`; supported: {}",
SuppressionKind::supported().join(", ")
));
None
}
},
None => {
violations.push(format!(
"{SUPPRESSIONS_PATH}:{block_line} `[[suppressions]]` entry is missing required `kind`"
));
None
}
};
let owner = match pending.owner {
Some((value, line)) => {
if value.trim().is_empty() {
violations.push(format!("{SUPPRESSIONS_PATH}:{line} `owner` is blank"));
None
} else {
Some(value)
}
}
None => {
violations.push(format!(
"{SUPPRESSIONS_PATH}:{block_line} `[[suppressions]]` entry is missing required `owner`"
));
None
}
};
let reason = match pending.reason {
Some((value, line)) => {
if value.trim().is_empty() {
violations.push(format!("{SUPPRESSIONS_PATH}:{line} `reason` is blank"));
None
} else {
Some(value)
}
}
None => {
violations.push(format!(
"{SUPPRESSIONS_PATH}:{block_line} `[[suppressions]]` entry is missing required `reason`"
));
None
}
};
let path = match pending.path {
Some((value, line)) => {
if value.trim().is_empty() {
violations.push(format!("{SUPPRESSIONS_PATH}:{line} `path` is empty"));
None
} else if value.contains('\\') {
violations.push(format!(
"{SUPPRESSIONS_PATH}:{line} `path` `{value}` uses backslashes; use `/` separators"
));
None
} else if is_absolute_path(&value) {
violations.push(format!(
"{SUPPRESSIONS_PATH}:{line} `path` `{value}` is absolute; entries must be repository-relative"
));
None
} else {
Some(value)
}
}
None => None,
};
let expires = validate_optional_date("expires", pending.expires, violations);
let created_at = validate_optional_date("created_at", pending.created_at, violations);
let last_seen = validate_optional_date("last_seen", pending.last_seen, violations);
let review_by = validate_optional_date("review_by", pending.review_by, violations);
let finding_id = non_blank_selector("finding_id", pending.finding_id, violations);
let test = non_blank_selector("test", pending.test, violations);
if let Some(kind) = kind {
match kind {
SuppressionKind::ExposureGap => {
if finding_id.is_none() {
violations.push(format!(
"{SUPPRESSIONS_PATH}:{block_line} `kind = \"exposure_gap\"` requires `finding_id`"
));
return;
}
if test.is_some() {
violations.push(format!(
"{SUPPRESSIONS_PATH}:{block_line} `kind = \"exposure_gap\"` does not accept `test`"
));
return;
}
}
SuppressionKind::TestEfficiency => {
if test.is_none() {
violations.push(format!(
"{SUPPRESSIONS_PATH}:{block_line} `kind = \"test_efficiency\"` requires `test`"
));
return;
}
if finding_id.is_some() {
violations.push(format!(
"{SUPPRESSIONS_PATH}:{block_line} `kind = \"test_efficiency\"` does not accept `finding_id`"
));
return;
}
}
}
if let (Some(owner), Some(reason)) = (owner, reason) {
entries.push(SuppressionEntry {
kind,
finding_id,
test,
path,
reason,
owner,
expires,
scope: pending.scope.map(|(value, _)| value),
created_at,
last_seen,
review_by,
expected_visibility: pending.expected_visibility.map(|(value, _)| value),
static_class: pending.static_class.map(|(value, _)| value),
language: pending.language.map(|(value, _)| value),
language_status: pending.language_status.map(|(value, _)| value),
block_line,
});
}
}
}
fn validate_optional_date(
field: &str,
raw: Option<(String, usize)>,
violations: &mut Vec<String>,
) -> Option<String> {
match raw {
Some((value, line)) => {
if !is_iso_date(&value) {
violations.push(format!(
"{SUPPRESSIONS_PATH}:{line} `{field}` `{value}` is not in YYYY-MM-DD format"
));
None
} else {
Some(value)
}
}
None => None,
}
}
fn non_blank_selector(
field: &str,
raw: Option<(String, usize)>,
violations: &mut Vec<String>,
) -> Option<String> {
match raw {
Some((value, line)) => {
if value.trim().is_empty() {
violations.push(format!("{SUPPRESSIONS_PATH}:{line} `{field}` is blank"));
None
} else {
Some(value)
}
}
None => None,
}
}
fn is_absolute_path(value: &str) -> bool {
if value.starts_with('/') {
return true;
}
let bytes = value.as_bytes();
bytes.len() >= 2 && bytes[1] == b':' && bytes[0].is_ascii_alphabetic()
}
pub fn is_iso_date(value: &str) -> bool {
let bytes = value.as_bytes();
if bytes.len() != 10 || bytes[4] != b'-' || bytes[7] != b'-' {
return false;
}
if !bytes[..4].iter().all(|b| b.is_ascii_digit())
|| !bytes[5..7].iter().all(|b| b.is_ascii_digit())
|| !bytes[8..10].iter().all(|b| b.is_ascii_digit())
{
return false;
}
let m: u32 = (bytes[5] - b'0') as u32 * 10 + (bytes[6] - b'0') as u32;
let d: u32 = (bytes[8] - b'0') as u32 * 10 + (bytes[9] - b'0') as u32;
(1..=12).contains(&m) && (1..=31).contains(&d)
}
pub fn load_suppressions_for_root_at(
root: &Path,
relative_path: &Path,
) -> Result<Vec<SuppressionEntry>, Vec<String>> {
let path = root.join(relative_path);
if !path.exists() {
return Ok(Vec::new());
}
let text = std::fs::read_to_string(&path)
.map_err(|err| vec![format!("failed to read {}: {err}", path.display())])?;
let (entries, violations) = parse_suppressions_manifest(&text);
if violations.is_empty() {
Ok(entries)
} else {
Err(violations)
}
}
pub fn current_iso_date() -> String {
let secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let days_since_epoch = (secs / 86_400) as i64;
let (y, m, d) = days_to_civil_date(days_since_epoch);
format!("{y:04}-{m:02}-{d:02}")
}
fn days_to_civil_date(days_since_epoch: i64) -> (i32, u32, u32) {
let z = days_since_epoch + 719_468;
let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
let doe = z - era * 146_097;
let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
let y = yoe as i32 + (era * 400) as i32;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
#[expect(
clippy::cast_sign_loss,
reason = "Howard Hinnant civil-date algorithm guarantees 1..=31 for d and 1..=12 for m_raw."
)]
let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
let m_raw = if mp < 10 { mp + 3 } else { mp - 9 };
#[expect(
clippy::cast_sign_loss,
reason = "m_raw is in 1..=12 by construction (mp in 0..=11)."
)]
let m = m_raw as u32;
let y = if m <= 2 { y + 1 } else { y };
(y, m, d)
}
pub fn is_expired(expires: Option<&str>, today: &str) -> bool {
match expires {
Some(e) => today > e,
None => false,
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct SuppressionApplication {
pub suppressed_findings: std::collections::BTreeSet<String>,
pub suppressed_tests: std::collections::BTreeSet<(String, Option<String>)>,
pub warnings: Vec<String>,
}
pub fn apply_exposure_suppressions(
candidate_finding_ids: &[String],
suppressions: &[SuppressionEntry],
today: &str,
) -> SuppressionApplication {
let mut app = SuppressionApplication::default();
let candidate_set: std::collections::BTreeSet<&str> =
candidate_finding_ids.iter().map(String::as_str).collect();
for entry in suppressions
.iter()
.filter(|e| e.kind == SuppressionKind::ExposureGap)
{
let Some(id) = &entry.finding_id else {
continue;
};
if is_expired(entry.expires.as_deref(), today) {
app.warnings.push(format!(
"expired {} suppression for `{id}` (expired on {})",
entry.kind.as_str(),
entry.expires.as_deref().unwrap_or("unknown")
));
continue;
}
if !candidate_set.contains(id.as_str()) {
app.warnings.push(format!(
"{} suppression for `{id}` did not match any current exposure-gap finding",
entry.kind.as_str()
));
continue;
}
app.suppressed_findings.insert(id.clone());
}
app
}
pub fn apply_test_efficiency_suppressions(
candidate_entries: &[(String, String)],
suppressions: &[SuppressionEntry],
today: &str,
) -> SuppressionApplication {
let mut app = SuppressionApplication::default();
for entry in suppressions
.iter()
.filter(|e| e.kind == SuppressionKind::TestEfficiency)
{
let Some(test_name) = &entry.test else {
continue;
};
let key_label = match &entry.path {
Some(p) => format!("`{}` at `{}`", test_name, p),
None => format!("`{}`", test_name),
};
if is_expired(entry.expires.as_deref(), today) {
app.warnings.push(format!(
"expired {} suppression for {key_label} (expired on {})",
entry.kind.as_str(),
entry.expires.as_deref().unwrap_or("unknown")
));
continue;
}
let matches: Vec<&(String, String)> = candidate_entries
.iter()
.filter(|(name, path)| {
name == test_name && entry.path.as_ref().map(|p| p == path).unwrap_or(true)
})
.collect();
if matches.is_empty() {
app.warnings.push(format!(
"{} suppression for {key_label} did not match any test-efficiency entry",
entry.kind.as_str()
));
continue;
}
for (name, path) in matches {
app.suppressed_tests
.insert((name.clone(), Some(path.clone())));
}
}
app
}
#[cfg(test)]
mod tests {
use super::{
SUPPRESSIONS_PATH, SuppressionApplication, SuppressionEntry, SuppressionKind,
apply_exposure_suppressions, apply_test_efficiency_suppressions, current_iso_date,
days_to_civil_date, is_expired, is_iso_date, parse_suppressions_manifest,
};
#[test]
fn parse_accepts_well_formed_manifest_with_both_kinds() {
let text = r#"
schema_version = 1
[[suppressions]]
kind = "exposure_gap"
finding_id = "probe:src/pricing.rs:88:predicate"
reason = "Covered by integration test."
owner = "billing"
expires = "2026-09-01"
scope = "seam:pricing::threshold"
created_at = "2026-01-01"
last_seen = "2026-05-01"
review_by = "2026-12-01"
expected_visibility = "suppressed_visible"
static_class = "weakly_exposed"
language = "typescript"
language_status = "preview"
[[suppressions]]
kind = "test_efficiency"
test = "cli_prints_help"
path = "tests/cli.rs"
reason = "Intentionally broad CLI smoke."
owner = "devtools"
"#;
let (entries, violations) = parse_suppressions_manifest(text);
assert!(
violations.is_empty(),
"unexpected violations: {violations:?}"
);
assert_eq!(entries.len(), 2);
assert_eq!(entries[0].kind, SuppressionKind::ExposureGap);
assert_eq!(
entries[0].finding_id.as_deref(),
Some("probe:src/pricing.rs:88:predicate")
);
assert_eq!(entries[0].expires.as_deref(), Some("2026-09-01"));
assert_eq!(entries[1].kind, SuppressionKind::TestEfficiency);
assert_eq!(entries[1].test.as_deref(), Some("cli_prints_help"));
assert_eq!(entries[1].path.as_deref(), Some("tests/cli.rs"));
assert_eq!(entries[0].scope.as_deref(), Some("seam:pricing::threshold"));
assert_eq!(entries[0].created_at.as_deref(), Some("2026-01-01"));
assert_eq!(entries[0].last_seen.as_deref(), Some("2026-05-01"));
assert_eq!(entries[0].review_by.as_deref(), Some("2026-12-01"));
assert_eq!(
entries[0].expected_visibility.as_deref(),
Some("suppressed_visible")
);
assert_eq!(entries[0].static_class.as_deref(), Some("weakly_exposed"));
assert_eq!(entries[0].language.as_deref(), Some("typescript"));
assert_eq!(entries[0].language_status.as_deref(), Some("preview"));
}
#[test]
fn parse_requires_schema_version() {
let text = r#"
[[suppressions]]
kind = "exposure_gap"
finding_id = "probe:x"
reason = "y"
owner = "z"
"#;
let (_, violations) = parse_suppressions_manifest(text);
assert!(violations.iter().any(|v| v.contains("schema_version = 1")));
}
#[test]
fn parse_requires_kind_owner_reason() {
let cases = [
(
r#"schema_version = 1
[[suppressions]]
finding_id = "probe:x"
owner = "z"
reason = "y"
"#,
"missing required `kind`",
),
(
r#"schema_version = 1
[[suppressions]]
kind = "exposure_gap"
finding_id = "probe:x"
reason = "y"
"#,
"missing required `owner`",
),
(
r#"schema_version = 1
[[suppressions]]
kind = "exposure_gap"
finding_id = "probe:x"
owner = "z"
"#,
"missing required `reason`",
),
];
for (text, fragment) in cases {
let (_, violations) = parse_suppressions_manifest(text);
assert!(
violations.iter().any(|v| v.contains(fragment)),
"expected `{fragment}` violation for: {text}\nviolations: {violations:?}"
);
}
}
#[test]
fn parse_rejects_unknown_kind() {
let text = r#"schema_version = 1
[[suppressions]]
kind = "wishful"
finding_id = "probe:x"
owner = "z"
reason = "y"
"#;
let (_, violations) = parse_suppressions_manifest(text);
assert!(
violations
.iter()
.any(|v| v.contains("unsupported kind `wishful`"))
);
}
#[test]
fn parse_rejects_unknown_top_level_or_block_field() {
let text = r#"schema_version = 1
priority = "high"
[[suppressions]]
kind = "exposure_gap"
finding_id = "probe:x"
owner = "z"
reason = "y"
priority = "high"
"#;
let (_, violations) = parse_suppressions_manifest(text);
assert!(
violations
.iter()
.any(|v| v.contains("unsupported top-level field `priority`"))
);
assert!(
violations
.iter()
.any(|v| v.contains("unsupported `[[suppressions]]` field `priority`"))
);
}
#[test]
fn parse_rejects_blank_owner_or_reason() {
let text = r#"schema_version = 1
[[suppressions]]
kind = "exposure_gap"
finding_id = "probe:x"
owner = " "
reason = " "
"#;
let (_, violations) = parse_suppressions_manifest(text);
assert!(violations.iter().any(|v| v.contains("`owner` is blank")));
assert!(violations.iter().any(|v| v.contains("`reason` is blank")));
}
#[test]
fn parse_rejects_kind_field_mismatches() {
let exposure_with_test = r#"schema_version = 1
[[suppressions]]
kind = "exposure_gap"
test = "x"
owner = "z"
reason = "y"
"#;
let (_, violations) = parse_suppressions_manifest(exposure_with_test);
assert!(
violations
.iter()
.any(|v| v.contains("`kind = \"exposure_gap\"` requires `finding_id`"))
|| violations
.iter()
.any(|v| v.contains("does not accept `test`"))
);
let test_with_finding_id = r#"schema_version = 1
[[suppressions]]
kind = "test_efficiency"
finding_id = "probe:x"
test = "x"
owner = "z"
reason = "y"
"#;
let (_, violations) = parse_suppressions_manifest(test_with_finding_id);
assert!(
violations
.iter()
.any(|v| v.contains("does not accept `finding_id`"))
);
}
#[test]
fn parse_rejects_absolute_or_backslash_path() {
let abs = r#"schema_version = 1
[[suppressions]]
kind = "test_efficiency"
test = "x"
path = "/abs/path.rs"
owner = "z"
reason = "y"
"#;
let (_, violations) = parse_suppressions_manifest(abs);
assert!(violations.iter().any(|v| v.contains("is absolute")));
let drive = "Z";
let sep = ":/";
let win = format!(
r#"schema_version = 1
[[suppressions]]
kind = "test_efficiency"
test = "x"
path = "{drive}{sep}abs/path.rs"
owner = "z"
reason = "y"
"#
);
let (_, violations) = parse_suppressions_manifest(&win);
assert!(violations.iter().any(|v| v.contains("is absolute")));
let backslash = r#"schema_version = 1
[[suppressions]]
kind = "test_efficiency"
test = "x"
path = "tests\\cli.rs"
owner = "z"
reason = "y"
"#;
let (_, violations) = parse_suppressions_manifest(backslash);
assert!(violations.iter().any(|v| v.contains("backslashes")));
}
#[test]
fn parse_rejects_invalid_expires_format() {
let text = r#"schema_version = 1
[[suppressions]]
kind = "exposure_gap"
finding_id = "probe:x"
owner = "z"
reason = "y"
expires = "Sept 1, 2026"
"#;
let (_, violations) = parse_suppressions_manifest(text);
assert!(
violations
.iter()
.any(|v| v.contains("not in YYYY-MM-DD format"))
);
}
#[test]
fn parse_rejects_invalid_policy_date_formats() {
let text = r#"schema_version = 1
[[suppressions]]
kind = "exposure_gap"
finding_id = "probe:x"
owner = "z"
reason = "y"
created_at = "2026/01/01"
last_seen = "2026/02/01"
review_by = "2026/03/01"
"#;
let (_, violations) = parse_suppressions_manifest(text);
assert!(violations.iter().any(|v| v.contains("`created_at`")));
assert!(violations.iter().any(|v| v.contains("`last_seen`")));
assert!(violations.iter().any(|v| v.contains("`review_by`")));
}
#[test]
fn parse_rejects_duplicate_selectors() {
let text = r#"schema_version = 1
[[suppressions]]
kind = "exposure_gap"
finding_id = "probe:x"
owner = "z"
reason = "first"
[[suppressions]]
kind = "exposure_gap"
finding_id = "probe:x"
owner = "z"
reason = "second"
[[suppressions]]
kind = "test_efficiency"
test = "alpha"
path = "tests/a.rs"
owner = "z"
reason = "first"
[[suppressions]]
kind = "test_efficiency"
test = "alpha"
path = "tests/a.rs"
owner = "z"
reason = "second"
"#;
let (_, violations) = parse_suppressions_manifest(text);
assert!(
violations
.iter()
.any(|v| v.contains("duplicate selector finding_id `probe:x`"))
);
assert!(
violations
.iter()
.any(|v| v.contains("duplicate selector `alpha` at `tests/a.rs`"))
);
}
#[test]
fn is_iso_date_validates_format_and_ranges() {
assert!(is_iso_date("2026-09-01"));
assert!(is_iso_date("2025-12-31"));
assert!(!is_iso_date("2026/09/01"));
assert!(!is_iso_date("26-09-01"));
assert!(!is_iso_date("2026-13-01"));
assert!(!is_iso_date("2026-09-32"));
assert!(!is_iso_date(""));
}
#[test]
fn is_expired_compares_lexicographically_and_treats_none_as_active() {
assert!(!is_expired(None, "2026-05-03"));
assert!(!is_expired(Some("2026-09-01"), "2026-05-03"));
assert!(!is_expired(Some("2026-05-03"), "2026-05-03"));
assert!(is_expired(Some("2025-12-31"), "2026-05-03"));
assert!(is_expired(Some("2026-04-30"), "2026-05-03"));
}
#[test]
fn days_to_civil_date_round_trips_known_anchors() {
assert_eq!(days_to_civil_date(0), (1970, 1, 1));
assert_eq!(days_to_civil_date(10957), (2000, 1, 1));
assert_eq!(days_to_civil_date(20576), (2026, 5, 3));
assert_eq!(days_to_civil_date(20577), (2026, 5, 4));
}
#[test]
fn current_iso_date_returns_yyyy_mm_dd_format() {
let today = current_iso_date();
assert!(is_iso_date(&today), "current_iso_date returned `{today}`");
}
fn exposure_entry(
finding_id: &str,
expires: Option<&str>,
block_line: usize,
) -> SuppressionEntry {
SuppressionEntry {
kind: SuppressionKind::ExposureGap,
finding_id: Some(finding_id.to_string()),
test: None,
path: None,
reason: "stated".to_string(),
owner: "team".to_string(),
expires: expires.map(str::to_string),
scope: None,
created_at: None,
last_seen: None,
review_by: None,
expected_visibility: None,
static_class: None,
language: None,
language_status: None,
block_line,
}
}
fn test_efficiency_entry(
test: &str,
path: Option<&str>,
expires: Option<&str>,
block_line: usize,
) -> SuppressionEntry {
SuppressionEntry {
kind: SuppressionKind::TestEfficiency,
finding_id: None,
test: Some(test.to_string()),
path: path.map(str::to_string),
reason: "stated".to_string(),
owner: "team".to_string(),
expires: expires.map(str::to_string),
scope: None,
created_at: None,
last_seen: None,
review_by: None,
expected_visibility: None,
static_class: None,
language: None,
language_status: None,
block_line,
}
}
#[test]
fn apply_exposure_suppressions_moves_matched_findings_into_suppressed_set() {
let candidates = vec![
"probe:a".to_string(),
"probe:b".to_string(),
"probe:c".to_string(),
];
let suppressions = vec![
exposure_entry("probe:a", None, 10),
exposure_entry("probe:c", Some("2099-01-01"), 20),
];
let app = apply_exposure_suppressions(&candidates, &suppressions, "2026-05-03");
assert!(app.suppressed_findings.contains("probe:a"));
assert!(app.suppressed_findings.contains("probe:c"));
assert!(!app.suppressed_findings.contains("probe:b"));
assert!(app.warnings.is_empty());
}
#[test]
fn apply_exposure_suppressions_warns_on_expired_and_unmatched_selectors() {
let candidates = vec!["probe:a".to_string()];
let suppressions = vec![
exposure_entry("probe:a", Some("2025-01-01"), 10),
exposure_entry("probe:does_not_exist", Some("2099-01-01"), 20),
];
let app = apply_exposure_suppressions(&candidates, &suppressions, "2026-05-03");
assert!(
app.suppressed_findings.is_empty(),
"expired and unmatched suppressions must not apply"
);
assert_eq!(app.warnings.len(), 2);
assert!(
app.warnings
.iter()
.any(|w| w.contains("expired") && w.contains("probe:a"))
);
assert!(
app.warnings
.iter()
.any(|w| w.contains("did not match") && w.contains("probe:does_not_exist"))
);
}
#[test]
fn apply_test_efficiency_suppressions_matches_by_test_or_test_and_path() {
let candidates = vec![
("test_alpha".to_string(), "tests/a.rs".to_string()),
("test_alpha".to_string(), "tests/b.rs".to_string()),
("test_beta".to_string(), "tests/c.rs".to_string()),
];
let suppressions = vec![
test_efficiency_entry("test_alpha", None, None, 10),
];
let app = apply_test_efficiency_suppressions(&candidates, &suppressions, "2026-05-03");
assert_eq!(app.suppressed_tests.len(), 2);
assert!(app.warnings.is_empty());
let suppressions_with_path = vec![test_efficiency_entry(
"test_alpha",
Some("tests/a.rs"),
None,
10,
)];
let app =
apply_test_efficiency_suppressions(&candidates, &suppressions_with_path, "2026-05-03");
assert_eq!(app.suppressed_tests.len(), 1);
}
#[test]
fn apply_test_efficiency_suppressions_surfaces_unmatched_and_expired_warnings() {
let candidates = vec![("test_alpha".to_string(), "tests/a.rs".to_string())];
let suppressions = vec![
test_efficiency_entry("test_does_not_exist", None, Some("2099-01-01"), 10),
test_efficiency_entry("test_alpha", None, Some("2025-01-01"), 20),
];
let app = apply_test_efficiency_suppressions(&candidates, &suppressions, "2026-05-03");
assert!(app.suppressed_tests.is_empty());
assert_eq!(app.warnings.len(), 2);
assert!(app.warnings.iter().any(|w| w.contains("did not match")));
assert!(app.warnings.iter().any(|w| w.contains("expired")));
}
#[test]
fn suppressions_path_constant_matches_expected_layout() {
assert_eq!(SUPPRESSIONS_PATH, ".ripr/suppressions.toml");
}
#[test]
fn application_has_default_constructor() {
let app = SuppressionApplication::default();
assert!(app.suppressed_findings.is_empty());
assert!(app.suppressed_tests.is_empty());
assert!(app.warnings.is_empty());
}
}