spec_driven_docs/gates/
ki_report_body.rs1use std::sync::LazyLock;
10
11use regex::Regex;
12
13use crate::domain::finding::Finding;
14use crate::domain::rule_id::RuleId;
15use crate::gates::paths::ki_records;
16use crate::gates::{GateCtx, GateResult, Violation, read_text};
17
18pub const CITES: &[RuleId] = &[RuleId::FiledRecordCarriesItsReport];
20
21static FILED: LazyLock<Regex> = LazyLock::new(|| {
26 Regex::new(r"^[ \t]*[Uu]pstream:.*[/#=][0-9]+").unwrap_or_else(|_| unreachable!())
27});
28
29pub fn run(ctx: &GateCtx, args: &[String]) -> GateResult {
35 let mut bad = String::new();
36 for record in ki_records(ctx, args) {
37 let text = read_text(ctx, &record)?;
38 if text.lines().any(|line| FILED.is_match(line))
39 && !text.lines().any(|line| line == "## Report")
40 {
41 bad.push(' ');
42 bad.push_str(record.as_str());
43 }
44 }
45 if bad.is_empty() {
46 Ok(vec![])
47 } else {
48 Ok(vec![Violation::Finding(Finding::global(
49 RuleId::FiledRecordCarriesItsReport,
50 bad.trim_start().to_string(),
51 ))])
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58 use crate::gates::tests_support::ki_fixture_upstream;
59
60 fn run_on(upstream: &str, body: &str) -> Vec<String> {
61 let dir = ki_fixture_upstream(upstream, body);
62 let ctx = GateCtx::new(dir.path().to_str().unwrap());
63 run(&ctx, &[])
64 .unwrap()
65 .iter()
66 .map(ToString::to_string)
67 .collect()
68 }
69
70 #[test]
71 fn a_tracker_reference_needs_no_report() {
72 assert!(run_on("https://example.invalid/issues", "# V\n").is_empty());
73 }
74
75 #[test]
76 fn a_filed_reference_demands_the_report_section() {
77 for upstream in [
78 "https://example.invalid/issues/123",
79 "https://example.invalid/issues/123 (open)",
80 "https://example.invalid/issues/123#c4",
81 "https://bugzilla.example/show_bug.cgi?id=123",
82 ] {
83 let out = run_on(upstream, "# V\n");
84 assert_eq!(out.len(), 1, "upstream {upstream:?}");
85 assert!(out[0].starts_with("FAIL known-issues:a-filed-record-carries-its-report: "));
86 }
87 }
88
89 #[test]
90 fn a_filed_record_with_its_report_passes() {
91 assert!(
92 run_on(
93 "https://example.invalid/issues/123",
94 "# V\n## Report\n```text\nbody\n```\n"
95 )
96 .is_empty()
97 );
98 }
99}