1use super::checker::{CheckStatus, DiagnosticCheck};
6use super::report::DiagnosticReport;
7use serde::{Deserialize, Serialize};
8use std::path::Path;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12pub enum HealthStatus {
13 Healthy,
15 Degraded,
17 Unhealthy,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct HealthSummary {
24 pub status: HealthStatus,
26 pub score: u8,
28 pub critical_issues: Vec<String>,
30}
31
32impl HealthSummary {
33 pub fn from_report(report: &DiagnosticReport) -> Self {
35 let total = report.checks.len();
36 let failed = report.summary.failed;
37 let warnings = report.summary.warnings;
38
39 let score = if total > 0 {
41 let penalty = failed as f64 + warnings as f64 * 0.5;
42 let raw_score = ((total as f64 - penalty) / total as f64) * 100.0;
43 raw_score.clamp(0.0, 100.0) as u8
44 } else {
45 100
46 };
47
48 let status = if score >= 90 {
50 HealthStatus::Healthy
51 } else if score >= 70 {
52 HealthStatus::Degraded
53 } else {
54 HealthStatus::Unhealthy
55 };
56
57 let critical_issues: Vec<String> = report
59 .checks
60 .iter()
61 .filter(|c| c.status == CheckStatus::Fail)
62 .map(|c| format!("{}: {}", c.name, c.message))
63 .collect();
64
65 Self {
66 status,
67 score,
68 critical_issues,
69 }
70 }
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct AutoFixResult {
76 pub fixed: Vec<String>,
78 pub failed: Vec<String>,
80}
81
82pub struct AutoFixer;
84
85impl AutoFixer {
86 pub fn auto_fix(report: &DiagnosticReport) -> AutoFixResult {
88 let mut fixed = Vec::new();
89 let mut failed = Vec::new();
90
91 for check in &report.checks {
92 if check.status == CheckStatus::Fail || check.status == CheckStatus::Warn {
93 match Self::try_fix(check) {
94 Ok(msg) => fixed.push(msg),
95 Err(msg) => failed.push(msg),
96 }
97 }
98 }
99
100 AutoFixResult { fixed, failed }
101 }
102
103 fn try_fix(check: &DiagnosticCheck) -> Result<String, String> {
104 match check.name.as_str() {
105 "文件权限" | "会话目录" | "缓存目录" | "配置目录" => {
106 Self::fix_directory_issue(check)
107 }
108 _ => {
109 if let Some(ref fix) = check.fix {
111 Err(format!("{}: {}", check.name, fix))
112 } else {
113 Err(format!("{}: 无法自动修复", check.name))
114 }
115 }
116 }
117 }
118
119 fn fix_directory_issue(check: &DiagnosticCheck) -> Result<String, String> {
120 let path = check
122 .details
123 .as_ref()
124 .and_then(|d| {
125 d.strip_prefix("路径: ")
126 .or_else(|| d.strip_prefix("Path: "))
127 })
128 .map(|s| s.trim());
129
130 if let Some(path_str) = path {
131 let path = Path::new(path_str);
132 if !path.exists() {
133 match std::fs::create_dir_all(path) {
134 Ok(_) => Ok(format!("已创建目录: {}", path_str)),
135 Err(e) => Err(format!("无法创建目录 {}: {}", path_str, e)),
136 }
137 } else {
138 Ok(format!("目录已存在: {}", path_str))
139 }
140 } else {
141 Err(format!("{}: 无法确定目录路径", check.name))
142 }
143 }
144}
145
146pub async fn quick_health_check() -> (bool, Vec<String>) {
148 let mut issues = Vec::new();
149
150 let config_dir = dirs::config_dir()
152 .map(|p| p.join("aster"))
153 .unwrap_or_else(|| std::path::PathBuf::from("~/.config/aster"));
154
155 if !config_dir.exists() && std::fs::create_dir_all(&config_dir).is_err() {
156 issues.push("无法创建配置目录".to_string());
157 }
158
159 let has_api_key =
161 std::env::var("ANTHROPIC_API_KEY").is_ok() || std::env::var("OPENAI_API_KEY").is_ok();
162
163 if !has_api_key {
164 issues.push("未配置 API 密钥".to_string());
165 }
166
167 (issues.is_empty(), issues)
168}
169
170pub async fn get_system_health_summary() -> HealthSummary {
172 use super::report::{DiagnosticOptions, DiagnosticReport};
173
174 let options = DiagnosticOptions::default();
175 let report = DiagnosticReport::generate(&options);
176
177 HealthSummary::from_report(&report)
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use crate::diagnostics::report::{DiagnosticReport, ReportSummary};
184
185 fn create_test_report(passed: usize, warnings: usize, failed: usize) -> DiagnosticReport {
186 let mut checks = Vec::new();
187
188 for i in 0..passed {
189 checks.push(DiagnosticCheck::pass(format!("Pass{}", i), "通过"));
190 }
191 for i in 0..warnings {
192 checks.push(DiagnosticCheck::warn(format!("Warn{}", i), "警告"));
193 }
194 for i in 0..failed {
195 checks.push(DiagnosticCheck::fail(format!("Fail{}", i), "失败"));
196 }
197
198 DiagnosticReport {
199 timestamp: chrono::Utc::now().timestamp(),
200 version: "test".to_string(),
201 platform: "test".to_string(),
202 checks,
203 summary: ReportSummary {
204 passed,
205 warnings,
206 failed,
207 },
208 system_info: None,
209 }
210 }
211
212 #[test]
213 fn test_health_summary_healthy() {
214 let report = create_test_report(10, 0, 0);
215 let summary = HealthSummary::from_report(&report);
216
217 assert_eq!(summary.status, HealthStatus::Healthy);
218 assert_eq!(summary.score, 100);
219 assert!(summary.critical_issues.is_empty());
220 }
221
222 #[test]
223 fn test_health_summary_degraded() {
224 let report = create_test_report(7, 3, 0);
225 let summary = HealthSummary::from_report(&report);
226
227 assert_eq!(summary.status, HealthStatus::Degraded);
228 assert!(summary.score >= 70 && summary.score < 90);
229 }
230
231 #[test]
232 fn test_health_summary_unhealthy() {
233 let report = create_test_report(3, 2, 5);
234 let summary = HealthSummary::from_report(&report);
235
236 assert_eq!(summary.status, HealthStatus::Unhealthy);
237 assert!(summary.score < 70);
238 assert_eq!(summary.critical_issues.len(), 5);
239 }
240
241 #[test]
242 fn test_health_summary_empty_report() {
243 let report = create_test_report(0, 0, 0);
244 let summary = HealthSummary::from_report(&report);
245
246 assert_eq!(summary.status, HealthStatus::Healthy);
247 assert_eq!(summary.score, 100);
248 }
249
250 #[test]
251 fn test_auto_fixer_no_issues() {
252 let report = create_test_report(5, 0, 0);
253 let result = AutoFixer::auto_fix(&report);
254
255 assert!(result.fixed.is_empty());
256 assert!(result.failed.is_empty());
257 }
258
259 #[test]
260 fn test_auto_fixer_with_directory_issue() {
261 let temp_path = std::env::temp_dir().join("aster_autofix_test");
262 let _ = std::fs::remove_dir_all(&temp_path);
263
264 let check = DiagnosticCheck::fail("会话目录", "目录不存在")
265 .with_details(format!("路径: {}", temp_path.display()));
266
267 let report = DiagnosticReport {
268 timestamp: chrono::Utc::now().timestamp(),
269 version: "test".to_string(),
270 platform: "test".to_string(),
271 checks: vec![check],
272 summary: ReportSummary {
273 passed: 0,
274 warnings: 0,
275 failed: 1,
276 },
277 system_info: None,
278 };
279
280 let result = AutoFixer::auto_fix(&report);
281
282 assert!(!result.fixed.is_empty() || !result.failed.is_empty());
284
285 let _ = std::fs::remove_dir_all(&temp_path);
286 }
287
288 #[tokio::test]
289 async fn test_quick_health_check() {
290 let (healthy, issues) = quick_health_check().await;
291 assert!(healthy || !issues.is_empty());
293 }
294
295 #[tokio::test]
296 async fn test_get_system_health_summary() {
297 let summary = get_system_health_summary().await;
298 assert!(summary.score <= 100);
300 }
301}