spec_driven_docs/gates/
ki_report_body.rs1use crate::domain::finding::Finding;
10use crate::domain::rule_id::RuleId;
11use crate::gates::ki_record::{FILINGS, axis};
12use crate::gates::paths::ki_records;
13use crate::gates::{GateCtx, GateResult, Violation, front_matter_values, read_text};
14
15pub const CITES: &[RuleId] = &[RuleId::FiledRecordCarriesItsReport];
17
18const RULE: RuleId = RuleId::FiledRecordCarriesItsReport;
19
20pub fn run(ctx: &GateCtx, args: &[String]) -> GateResult {
26 let mut violations = Vec::new();
27 for record in ki_records(ctx, args)? {
28 let text = read_text(ctx, &record)?;
29 if axis(&text, "filing", &FILINGS).as_deref() != Some("filed") {
30 continue;
31 }
32 let named = front_matter_values(&text, "upstream")
33 .iter()
34 .any(|value| !value.is_empty());
35 if !named {
36 violations.push(Violation::Finding(Finding::on_file(
37 RULE,
38 &record,
39 "a filed record names no upstream:",
40 )));
41 }
42 if !text.lines().any(|line| line == "## Report") {
43 violations.push(Violation::Finding(Finding::on_file(
44 RULE,
45 &record,
46 "a filed record carries no ## Report section",
47 )));
48 }
49 }
50 Ok(violations)
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56 use crate::gates::tests_support::ki_fixture_filing;
57
58 fn run_on(filing: &str, upstream: &str, body: &str) -> Vec<String> {
59 let dir = ki_fixture_filing(filing, upstream, body);
60 let ctx = GateCtx::new(dir.path().to_str().unwrap());
61 run(&ctx, &[])
62 .unwrap()
63 .iter()
64 .map(ToString::to_string)
65 .collect()
66 }
67
68 #[test]
69 fn an_unfiled_record_needs_no_report() {
70 for filing in ["gathering", "ready", "deferred"] {
71 assert!(
72 run_on(filing, "https://example.invalid/issues", "# V\n").is_empty(),
73 "filing {filing:?}"
74 );
75 }
76 }
77
78 #[test]
79 fn a_filed_record_demands_the_report_section() {
80 let out = run_on("filed", "https://example.invalid/issues/123", "# V\n");
81 assert_eq!(out.len(), 1);
82 assert!(out[0].starts_with("FAIL known-issues:a-filed-record-carries-its-report "));
83 assert!(out[0].ends_with(": a filed record carries no ## Report section"));
84 }
85
86 #[test]
87 fn a_filed_record_demands_the_issue_it_was_filed_as() {
88 let out = run_on("filed", "", "# V\n## Report\n```text\nbody\n```\n");
89 assert_eq!(out.len(), 1);
90 assert!(out[0].ends_with(": a filed record names no upstream:"));
91 }
92
93 #[test]
94 fn a_filed_record_with_its_report_passes() {
95 assert!(
96 run_on(
97 "filed",
98 "https://example.invalid/issues/123",
99 "# V\n## Report\n```text\nbody\n```\n"
100 )
101 .is_empty()
102 );
103 }
104}