1fn kind_label(kind: crate::check::FindingKind) -> &'static str {
4 use crate::check::FindingKind::*;
5 match kind {
6 Drift => "drift",
7 New => "new",
8 Removed => "removed",
9 Broken => "broken",
10 Orphan => "orphan",
11 NoTest => "no-test",
12 Invalid => "invalid",
13 Index => "index",
14 }
15}
16
17pub fn render_text(findings: &[crate::check::Finding]) -> String {
19 if findings.is_empty() {
20 return "no findings\n".to_string();
21 }
22 let mut out = String::new();
23 for f in findings {
24 out.push_str(&format!(
25 "{} {:?} {} {:?} {}\n",
26 kind_label(f.kind),
27 f.target_kind,
28 f.target,
29 f.reason,
30 f.detail
31 ));
32 }
33 out
34}
35
36pub fn render_json(findings: &[crate::check::Finding]) -> String {
38 #[derive(serde::Serialize)]
39 struct Wrapper<'a> {
40 findings: &'a [crate::check::Finding],
41 }
42 serde_json::to_string_pretty(&Wrapper { findings }).expect("findings serialize to json")
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48 use crate::check::{Finding, FindingKind};
49 use crate::model::{ReasonCode, TargetKind};
50
51 fn sample_findings() -> Vec<Finding> {
52 vec![
53 Finding {
54 kind: FindingKind::Drift,
55 target_kind: TargetKind::Edge,
56 target: "doc-req:DOC-A:FR-001".to_string(),
57 reason: ReasonCode::DocChanged,
58 detail: "doc changed".to_string(),
59 },
60 Finding {
61 kind: FindingKind::NoTest,
62 target_kind: TargetKind::Req,
63 target: "FR-002".to_string(),
64 reason: ReasonCode::ReqWithoutTest,
65 detail: "no test found".to_string(),
66 },
67 ]
68 }
69
70 #[test]
72 fn render_textはfindingなしなら固定文言になる() {
73 assert_eq!(render_text(&[]), "no findings\n");
74 }
75
76 #[test]
78 fn render_textは1行1findingでkindとtargetを含む() {
79 let findings = sample_findings();
80 let text = render_text(&findings);
81 let lines: Vec<&str> = text.lines().collect();
82 assert_eq!(lines.len(), findings.len());
83 assert!(lines[0].contains("doc-req:DOC-A:FR-001"));
84 assert!(lines[0].to_lowercase().contains("drift"));
85 assert!(lines[1].contains("FR-002"));
86 assert!(lines[1].to_lowercase().contains("no")); }
88
89 #[test]
91 fn render_jsonはfindings配列にkind_target_kind_target_reason_detailを持つ() {
92 let findings = sample_findings();
93 let json = render_json(&findings);
94 let value: serde_json::Value = serde_json::from_str(&json).expect("valid json");
95 let arr = value
96 .get("findings")
97 .and_then(|v| v.as_array())
98 .expect("findings array");
99 assert_eq!(arr.len(), 2);
100 for (i, item) in arr.iter().enumerate() {
101 assert!(item.get("kind").is_some(), "missing kind at {i}");
102 assert!(
103 item.get("target_kind").is_some(),
104 "missing target_kind at {i}"
105 );
106 assert!(item.get("target").is_some(), "missing target at {i}");
107 assert!(item.get("reason").is_some(), "missing reason at {i}");
108 assert!(item.get("detail").is_some(), "missing detail at {i}");
109 }
110 assert_eq!(arr[0]["kind"], serde_json::json!("Drift"));
111 assert_eq!(arr[0]["target"], serde_json::json!("doc-req:DOC-A:FR-001"));
112 assert_eq!(arr[0]["reason"], serde_json::json!("DocChanged"));
113 assert_eq!(arr[1]["kind"], serde_json::json!("NoTest"));
114 }
115}