use crate::domain::finding::Finding;
use crate::domain::rule_id::RuleId;
use crate::gates::paths::ki_records;
use crate::gates::{GateCtx, GateResult, Violation, read_text};
pub const CITES: &[RuleId] = &[RuleId::RecordCarriesItsRetirementCondition];
pub fn run(ctx: &GateCtx, args: &[String]) -> GateResult {
let mut bad = String::new();
for record in ki_records(ctx, args) {
let carries = read_text(ctx, &record)?.lines().any(|line| {
line.strip_prefix("retire_when:")
.is_some_and(|value| !value.trim().is_empty())
});
if !carries {
bad.push(' ');
bad.push_str(record.as_str());
}
}
if bad.is_empty() {
Ok(vec![])
} else {
Ok(vec![Violation::Finding(Finding::global(
RuleId::RecordCarriesItsRetirementCondition,
bad.trim_start().to_string(),
))])
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::gates::tests_support::ki_fixture;
#[test]
fn accepts_a_record_with_a_condition() {
let dir = ki_fixture("retire_when: release >= 2.0\n");
let ctx = GateCtx::new(dir.path().to_str().unwrap());
assert!(run(&ctx, &[]).unwrap().is_empty());
}
#[test]
fn rejects_a_missing_or_empty_condition() {
for frontmatter in ["retire_when:\n", ""] {
let dir = ki_fixture(frontmatter);
let ctx = GateCtx::new(dir.path().to_str().unwrap());
let out: Vec<String> = run(&ctx, &[])
.unwrap()
.iter()
.map(ToString::to_string)
.collect();
assert_eq!(out.len(), 1, "frontmatter {frontmatter:?}");
assert!(
out[0].starts_with("FAIL known-issues:a-record-carries-its-retirement-condition: ")
);
assert!(out[0].contains("KI-vendor.md"));
}
}
}