Skip to main content

entrenar/storage/preflight/
results.rs

1//! Preflight validation results.
2
3use std::borrow::Cow;
4
5use super::{CheckMetadata, CheckResult};
6
7/// Results from running preflight checks
8#[derive(Debug, Clone)]
9pub struct PreflightResults {
10    /// Individual check results
11    results: Vec<(CheckMetadata, CheckResult)>,
12    /// Overall pass/fail
13    passed: bool,
14    /// Number of checks that passed
15    passed_count: usize,
16    /// Number of checks that failed
17    failed_count: usize,
18    /// Number of warnings
19    warning_count: usize,
20    /// Number of skipped checks
21    skipped_count: usize,
22}
23
24impl PreflightResults {
25    /// Create a new PreflightResults instance
26    pub(crate) fn new(
27        results: Vec<(CheckMetadata, CheckResult)>,
28        passed: bool,
29        passed_count: usize,
30        failed_count: usize,
31        warning_count: usize,
32        skipped_count: usize,
33    ) -> Self {
34        Self { results, passed, passed_count, failed_count, warning_count, skipped_count }
35    }
36
37    /// Check if all required checks passed
38    pub fn all_passed(&self) -> bool {
39        self.passed
40    }
41
42    /// Get number of passed checks
43    pub fn passed_count(&self) -> usize {
44        self.passed_count
45    }
46
47    /// Get number of failed checks
48    pub fn failed_count(&self) -> usize {
49        self.failed_count
50    }
51
52    /// Get number of warnings
53    pub fn warning_count(&self) -> usize {
54        self.warning_count
55    }
56
57    /// Get number of skipped checks
58    pub fn skipped_count(&self) -> usize {
59        self.skipped_count
60    }
61
62    /// Get all results
63    pub fn results(&self) -> &[(CheckMetadata, CheckResult)] {
64        &self.results
65    }
66
67    /// Get failed checks only
68    pub fn failed_checks(&self) -> Vec<(&CheckMetadata, &CheckResult)> {
69        self.results
70            .iter()
71            .filter(|(check, result)| check.required && result.is_failed())
72            .map(|(c, r)| (c, r))
73            .collect()
74    }
75
76    /// Get warnings only
77    pub fn warnings(&self) -> Vec<(&CheckMetadata, &CheckResult)> {
78        self.results.iter().filter(|(_, result)| result.is_warning()).map(|(c, r)| (c, r)).collect()
79    }
80
81    /// Format results as a report
82    pub fn report(&self) -> String {
83        let mut lines = Vec::new();
84        lines.push("=== Preflight Check Results ===".to_string());
85        lines.push(format!("Status: {}", if self.passed { "PASSED" } else { "FAILED" }));
86        lines.push(format!(
87            "Passed: {}, Failed: {}, Warnings: {}, Skipped: {}",
88            self.passed_count, self.failed_count, self.warning_count, self.skipped_count
89        ));
90        lines.push(String::new());
91
92        for (check, result) in &self.results {
93            let status = match result {
94                CheckResult::Passed { .. } => "✓",
95                CheckResult::Failed { .. } => "✗",
96                CheckResult::Warning { .. } => "⚠",
97                CheckResult::Skipped { .. } => "○",
98            };
99
100            let message: Cow<'_, str> = match result {
101                CheckResult::Passed { message } => Cow::Borrowed(message),
102                CheckResult::Failed { message, details } => {
103                    if let Some(d) = details {
104                        Cow::Owned(format!("{message} ({d})"))
105                    } else {
106                        Cow::Borrowed(message)
107                    }
108                }
109                CheckResult::Warning { message } => Cow::Borrowed(message),
110                CheckResult::Skipped { reason } => Cow::Borrowed(reason),
111            };
112
113            lines.push(format!("{status} {}: {message}", check.name));
114        }
115
116        lines.join("\n")
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123    use crate::storage::preflight::types::CheckType;
124
125    fn make_check(name: &str, required: bool) -> CheckMetadata {
126        CheckMetadata {
127            name: name.to_string(),
128            check_type: CheckType::Configuration,
129            description: format!("{name} description"),
130            required,
131        }
132    }
133
134    #[test]
135    fn test_preflight_results_new() {
136        let results = PreflightResults::new(vec![], true, 5, 0, 1, 2);
137        assert!(results.all_passed());
138        assert_eq!(results.passed_count(), 5);
139        assert_eq!(results.failed_count(), 0);
140        assert_eq!(results.warning_count(), 1);
141        assert_eq!(results.skipped_count(), 2);
142    }
143
144    #[test]
145    fn test_preflight_results_all_passed_true() {
146        let results = PreflightResults::new(vec![], true, 3, 0, 0, 0);
147        assert!(results.all_passed());
148    }
149
150    #[test]
151    fn test_preflight_results_all_passed_false() {
152        let results = PreflightResults::new(vec![], false, 2, 1, 0, 0);
153        assert!(!results.all_passed());
154    }
155
156    #[test]
157    fn test_preflight_results_results_accessor() {
158        let check = make_check("test", true);
159        let result = CheckResult::Passed { message: "ok".to_string() };
160        let results = PreflightResults::new(vec![(check, result)], true, 1, 0, 0, 0);
161        assert_eq!(results.results().len(), 1);
162    }
163
164    #[test]
165    fn test_preflight_results_failed_checks() {
166        let check1 = make_check("pass", true);
167        let result1 = CheckResult::Passed { message: "ok".to_string() };
168        let check2 = make_check("fail", true);
169        let result2 = CheckResult::Failed { message: "error".to_string(), details: None };
170        let check3 = make_check("optional_fail", false);
171        let result3 = CheckResult::Failed { message: "not required".to_string(), details: None };
172
173        let results = PreflightResults::new(
174            vec![(check1, result1), (check2, result2), (check3, result3)],
175            false,
176            1,
177            2,
178            0,
179            0,
180        );
181
182        let failed = results.failed_checks();
183        // Only required failed checks
184        assert_eq!(failed.len(), 1);
185        assert_eq!(failed[0].0.name, "fail");
186    }
187
188    #[test]
189    fn test_preflight_results_warnings() {
190        let check1 = make_check("pass", true);
191        let result1 = CheckResult::Passed { message: "ok".to_string() };
192        let check2 = make_check("warn", false);
193        let result2 = CheckResult::Warning { message: "heads up".to_string() };
194
195        let results =
196            PreflightResults::new(vec![(check1, result1), (check2, result2)], true, 1, 0, 1, 0);
197
198        let warnings = results.warnings();
199        assert_eq!(warnings.len(), 1);
200        assert_eq!(warnings[0].0.name, "warn");
201    }
202
203    #[test]
204    fn test_preflight_results_report_passed() {
205        let check = make_check("test_check", true);
206        let result = CheckResult::Passed { message: "All good".to_string() };
207        let results = PreflightResults::new(vec![(check, result)], true, 1, 0, 0, 0);
208
209        let report = results.report();
210        assert!(report.contains("PASSED"));
211        assert!(report.contains("test_check"));
212        assert!(report.contains("All good"));
213        assert!(report.contains("✓"));
214    }
215
216    #[test]
217    fn test_preflight_results_report_failed() {
218        let check = make_check("failing_check", true);
219        let result = CheckResult::Failed {
220            message: "Something went wrong".to_string(),
221            details: Some("extra info".to_string()),
222        };
223        let results = PreflightResults::new(vec![(check, result)], false, 0, 1, 0, 0);
224
225        let report = results.report();
226        assert!(report.contains("FAILED"));
227        assert!(report.contains("failing_check"));
228        assert!(report.contains("Something went wrong"));
229        assert!(report.contains("extra info"));
230        assert!(report.contains("✗"));
231    }
232
233    #[test]
234    fn test_preflight_results_report_warning() {
235        let check = make_check("warn_check", false);
236        let result = CheckResult::Warning { message: "Be careful".to_string() };
237        let results = PreflightResults::new(vec![(check, result)], true, 0, 0, 1, 0);
238
239        let report = results.report();
240        assert!(report.contains("warn_check"));
241        assert!(report.contains("Be careful"));
242        assert!(report.contains("⚠"));
243    }
244
245    #[test]
246    fn test_preflight_results_report_skipped() {
247        let check = make_check("skipped_check", false);
248        let result = CheckResult::Skipped { reason: "Not applicable".to_string() };
249        let results = PreflightResults::new(vec![(check, result)], true, 0, 0, 0, 1);
250
251        let report = results.report();
252        assert!(report.contains("skipped_check"));
253        assert!(report.contains("Not applicable"));
254        assert!(report.contains("○"));
255    }
256
257    #[test]
258    fn test_preflight_results_clone() {
259        let results = PreflightResults::new(vec![], true, 1, 0, 0, 0);
260        let cloned = results.clone();
261        assert_eq!(results.passed_count(), cloned.passed_count());
262    }
263
264    #[test]
265    fn test_preflight_results_debug() {
266        let results = PreflightResults::new(vec![], true, 1, 0, 0, 0);
267        let debug = format!("{results:?}");
268        assert!(debug.contains("PreflightResults"));
269    }
270}