1use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14use std::path::Path;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
18#[serde(rename_all = "lowercase")]
19pub enum HealthSeverity {
20 Info,
21 Warning,
22 Error,
23 Critical,
24}
25
26impl HealthSeverity {
27 pub fn as_str(&self) -> &'static str {
28 match self {
29 HealthSeverity::Info => "info",
30 HealthSeverity::Warning => "warning",
31 HealthSeverity::Error => "error",
32 HealthSeverity::Critical => "critical",
33 }
34 }
35
36 pub fn try_parse(s: &str) -> Option<Self> {
37 match s.to_lowercase().as_str() {
38 "info" => Some(HealthSeverity::Info),
39 "warning" => Some(HealthSeverity::Warning),
40 "error" => Some(HealthSeverity::Error),
41 "critical" => Some(HealthSeverity::Critical),
42 _ => None,
43 }
44 }
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
49#[serde(rename_all = "lowercase")]
50pub enum Platform {
51 Linux,
52 MacOS,
53 Windows,
54 Unknown(String),
55}
56
57impl Platform {
58 pub fn current() -> Self {
59 if cfg!(target_os = "linux") {
60 Platform::Linux
61 } else if cfg!(target_os = "macos") {
62 Platform::MacOS
63 } else if cfg!(target_os = "windows") {
64 Platform::Windows
65 } else {
66 Platform::Unknown(std::env::consts::OS.to_string())
67 }
68 }
69
70 pub fn as_str(&self) -> &str {
71 match self {
72 Platform::Linux => "linux",
73 Platform::MacOS => "macos",
74 Platform::Windows => "windows",
75 Platform::Unknown(s) => s,
76 }
77 }
78}
79
80#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
82#[serde(rename_all = "lowercase")]
83pub enum Architecture {
84 X86_64,
85 Arm64,
86 X86,
87 Arm,
88 Unknown(String),
89}
90
91impl Architecture {
92 pub fn current() -> Self {
93 if cfg!(target_arch = "x86_64") {
94 Architecture::X86_64
95 } else if cfg!(target_arch = "aarch64") {
96 Architecture::Arm64
97 } else if cfg!(target_arch = "x86") {
98 Architecture::X86
99 } else if cfg!(target_arch = "arm") {
100 Architecture::Arm
101 } else {
102 Architecture::Unknown(std::env::consts::ARCH.to_string())
103 }
104 }
105
106 pub fn as_str(&self) -> &str {
107 match self {
108 Architecture::X86_64 => "x86_64",
109 Architecture::Arm64 => "arm64",
110 Architecture::X86 => "x86",
111 Architecture::Arm => "arm",
112 Architecture::Unknown(s) => s,
113 }
114 }
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct SymbolRequirement {
120 pub name: String,
121 pub required: bool,
122 pub description: String,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct BinaryCompatibility {
128 pub platform: Platform,
129 pub architecture: Architecture,
130 pub min_libc_version: Option<String>,
131 pub required_symbols: Vec<SymbolRequirement>,
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct HealthCheckResult {
137 pub check_name: String,
138 pub passed: bool,
139 pub severity: HealthSeverity,
140 pub message: String,
141 pub details: Option<String>,
142 pub suggestion: Option<String>,
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct HealthReport {
148 pub plugin_id: String,
149 pub plugin_version: String,
150 pub check_timestamp: String,
151 pub overall_health: HealthScore,
152 pub checks: Vec<HealthCheckResult>,
153 pub binary_compatibility: Option<BinaryCompatibility>,
154 pub performance_baseline: Option<PerformanceBaseline>,
155 pub recommendations: Vec<String>,
156}
157
158#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
160pub struct HealthScore {
161 pub score: u32,
162 pub status: HealthStatus,
163}
164
165#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
167#[serde(rename_all = "PascalCase")]
168pub enum HealthStatus {
169 Excellent, Good, Fair, Poor, Critical, }
175
176impl HealthStatus {
177 pub fn from_score(score: u32) -> Self {
178 match score {
179 90..=100 => HealthStatus::Excellent,
180 75..=89 => HealthStatus::Good,
181 60..=74 => HealthStatus::Fair,
182 40..=59 => HealthStatus::Poor,
183 _ => HealthStatus::Critical,
184 }
185 }
186
187 pub fn as_str(&self) -> &'static str {
188 match self {
189 HealthStatus::Excellent => "Excellent",
190 HealthStatus::Good => "Good",
191 HealthStatus::Fair => "Fair",
192 HealthStatus::Poor => "Poor",
193 HealthStatus::Critical => "Critical",
194 }
195 }
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct PerformanceBaseline {
201 pub init_time_ms: f64,
202 pub shutdown_time_ms: f64,
203 pub memory_usage_mb: f64,
204 pub max_concurrent_calls: u32,
205}
206
207pub struct PluginHealthChecker {
209 required_symbols: HashMap<String, SymbolRequirement>,
210 performance_thresholds: PerformanceThresholds,
211}
212
213#[derive(Debug, Clone)]
215pub struct PerformanceThresholds {
216 pub max_init_time_ms: f64,
217 pub max_shutdown_time_ms: f64,
218 pub max_memory_usage_mb: f64,
219 pub min_concurrent_calls: u32,
220}
221
222impl Default for PerformanceThresholds {
223 fn default() -> Self {
224 Self {
225 max_init_time_ms: 5000.0,
226 max_shutdown_time_ms: 1000.0,
227 max_memory_usage_mb: 500.0,
228 min_concurrent_calls: 10,
229 }
230 }
231}
232
233impl PluginHealthChecker {
234 pub fn new() -> Self {
236 Self {
237 required_symbols: Self::default_required_symbols(),
238 performance_thresholds: PerformanceThresholds::default(),
239 }
240 }
241
242 fn default_required_symbols() -> HashMap<String, SymbolRequirement> {
244 let mut symbols = HashMap::new();
245
246 symbols.insert(
247 "plugin_init".to_string(),
248 SymbolRequirement {
249 name: "plugin_init".to_string(),
250 required: true,
251 description: "Plugin initialization entry point".to_string(),
252 },
253 );
254
255 symbols.insert(
256 "plugin_get_info".to_string(),
257 SymbolRequirement {
258 name: "plugin_get_info".to_string(),
259 required: true,
260 description: "Plugin metadata provider".to_string(),
261 },
262 );
263
264 symbols.insert(
265 "plugin_shutdown".to_string(),
266 SymbolRequirement {
267 name: "plugin_shutdown".to_string(),
268 required: false,
269 description: "Plugin cleanup on shutdown".to_string(),
270 },
271 );
272
273 symbols
274 }
275
276 pub fn check_binary_exists(&self, binary_path: &Path) -> HealthCheckResult {
278 let exists = binary_path.exists();
279 HealthCheckResult {
280 check_name: "Binary Existence".to_string(),
281 passed: exists,
282 severity: if exists {
283 HealthSeverity::Info
284 } else {
285 HealthSeverity::Critical
286 },
287 message: if exists {
288 format!("Plugin binary found at {}", binary_path.display())
289 } else {
290 format!("Plugin binary not found at {}", binary_path.display())
291 },
292 details: None,
293 suggestion: if !exists {
294 Some("Ensure plugin.so/.dll/.dylib is built and included in package".to_string())
295 } else {
296 None
297 },
298 }
299 }
300
301 pub fn check_binary_validity(&self, binary_path: &Path) -> HealthCheckResult {
303 match std::fs::metadata(binary_path) {
304 Ok(metadata) => {
305 let size = metadata.len();
306 let passed = size > 0;
307 let readonly = metadata.permissions().readonly();
308 HealthCheckResult {
309 check_name: "Binary Validity".to_string(),
310 passed,
311 severity: if passed {
312 HealthSeverity::Info
313 } else {
314 HealthSeverity::Error
315 },
316 message: format!("Binary size: {} bytes", size),
317 details: Some(format!(
318 "Readable: {}, Size check: {}",
319 if readonly { "No" } else { "Yes" },
320 if size > 0 { "Pass" } else { "Fail" }
321 )),
322 suggestion: if size == 0 {
323 Some("Binary file is empty. Rebuild the plugin.".to_string())
324 } else {
325 None
326 },
327 }
328 }
329 Err(e) => HealthCheckResult {
330 check_name: "Binary Validity".to_string(),
331 passed: false,
332 severity: HealthSeverity::Critical,
333 message: format!("Cannot read binary metadata: {}", e),
334 details: Some(e.to_string()),
335 suggestion: Some("Check file permissions and ensure binary exists".to_string()),
336 },
337 }
338 }
339
340 pub fn check_platform_compatibility(
342 &self,
343 binary_path: &Path,
344 expected_platform: Platform,
345 ) -> HealthCheckResult {
346 if !binary_path.exists() {
348 return HealthCheckResult {
349 check_name: "Platform Compatibility".to_string(),
350 passed: false,
351 severity: HealthSeverity::Critical,
352 message: "Binary file not found".to_string(),
353 details: None,
354 suggestion: Some("Build and package the plugin correctly".to_string()),
355 };
356 }
357
358 let platform_match = match expected_platform {
359 Platform::Linux => binary_path.to_string_lossy().contains(".so"),
360 Platform::MacOS => binary_path.to_string_lossy().contains(".dylib"),
361 Platform::Windows => binary_path.to_string_lossy().contains(".dll"),
362 Platform::Unknown(_) => false,
363 };
364
365 HealthCheckResult {
366 check_name: "Platform Compatibility".to_string(),
367 passed: platform_match,
368 severity: if platform_match {
369 HealthSeverity::Info
370 } else {
371 HealthSeverity::Warning
372 },
373 message: if platform_match {
374 format!("Binary appears to be for {}", expected_platform.as_str())
375 } else {
376 format!(
377 "Binary may not be for {} platform",
378 expected_platform.as_str()
379 )
380 },
381 details: Some(format!("Expected platform: {}", expected_platform.as_str())),
382 suggestion: if !platform_match {
383 Some(format!(
384 "Rebuild plugin for {} platform",
385 expected_platform.as_str()
386 ))
387 } else {
388 None
389 },
390 }
391 }
392
393 pub fn check_required_symbols(&self) -> HealthCheckResult {
395 let required_count = self
396 .required_symbols
397 .iter()
398 .filter(|(_, req)| req.required)
399 .count();
400
401 let optional_count = self.required_symbols.len() - required_count;
402
403 HealthCheckResult {
404 check_name: "Required Symbols".to_string(),
405 passed: required_count > 0,
406 severity: if required_count > 0 {
407 HealthSeverity::Info
408 } else {
409 HealthSeverity::Warning
410 },
411 message: format!(
412 "Required symbols check: {} symbols required",
413 self.required_symbols.len()
414 ),
415 details: Some(format!(
416 "Total required: {}, Optional: {}",
417 required_count, optional_count
418 )),
419 suggestion: None,
420 }
421 }
422
423 pub fn check_performance_baseline(&self, baseline: &PerformanceBaseline) -> HealthCheckResult {
425 let init_ok = baseline.init_time_ms <= self.performance_thresholds.max_init_time_ms;
426 let shutdown_ok =
427 baseline.shutdown_time_ms <= self.performance_thresholds.max_shutdown_time_ms;
428 let memory_ok = baseline.memory_usage_mb <= self.performance_thresholds.max_memory_usage_mb;
429 let concurrency_ok =
430 baseline.max_concurrent_calls >= self.performance_thresholds.min_concurrent_calls;
431
432 let all_passed = init_ok && shutdown_ok && memory_ok && concurrency_ok;
433
434 let mut issues = Vec::new();
435 if !init_ok {
436 issues.push(format!(
437 "Init time: {} ms (threshold: {} ms)",
438 baseline.init_time_ms, self.performance_thresholds.max_init_time_ms
439 ));
440 }
441 if !shutdown_ok {
442 issues.push(format!(
443 "Shutdown time: {} ms (threshold: {} ms)",
444 baseline.shutdown_time_ms, self.performance_thresholds.max_shutdown_time_ms
445 ));
446 }
447 if !memory_ok {
448 issues.push(format!(
449 "Memory usage: {} MB (threshold: {} MB)",
450 baseline.memory_usage_mb, self.performance_thresholds.max_memory_usage_mb
451 ));
452 }
453 if !concurrency_ok {
454 issues.push(format!(
455 "Concurrency: {} calls (threshold: {})",
456 baseline.max_concurrent_calls, self.performance_thresholds.min_concurrent_calls
457 ));
458 }
459
460 HealthCheckResult {
461 check_name: "Performance Baseline".to_string(),
462 passed: all_passed,
463 severity: if all_passed {
464 HealthSeverity::Info
465 } else {
466 HealthSeverity::Warning
467 },
468 message: if all_passed {
469 "Performance baseline within acceptable thresholds".to_string()
470 } else {
471 format!("Performance issues detected: {} found", issues.len())
472 },
473 details: if issues.is_empty() {
474 None
475 } else {
476 Some(issues.join(", "))
477 },
478 suggestion: if !all_passed {
479 Some("Review performance metrics and optimize hot paths".to_string())
480 } else {
481 None
482 },
483 }
484 }
485
486 pub fn calculate_health_score(checks: &[HealthCheckResult]) -> HealthScore {
488 if checks.is_empty() {
489 return HealthScore {
490 score: 50,
491 status: HealthStatus::Poor,
492 };
493 }
494
495 let mut score = 100u32;
496 let mut critical_found = false;
497
498 for check in checks {
499 match check.severity {
500 HealthSeverity::Critical => {
501 critical_found = true;
502 score = score.saturating_sub(20);
503 }
504 HealthSeverity::Error => {
505 score = score.saturating_sub(10);
506 }
507 HealthSeverity::Warning => {
508 score = score.saturating_sub(5);
509 }
510 HealthSeverity::Info => {
511 }
513 }
514
515 if !check.passed {
516 score = score.saturating_sub(5);
517 }
518 }
519
520 if critical_found {
522 score = score.min(30);
523 }
524
525 score = score.min(100);
527
528 HealthScore {
529 score,
530 status: HealthStatus::from_score(score),
531 }
532 }
533
534 pub fn generate_recommendations(
536 checks: &[HealthCheckResult],
537 score: HealthScore,
538 ) -> Vec<String> {
539 let mut recommendations = Vec::new();
540
541 for check in checks {
543 if !check.passed {
544 if let Some(suggestion) = &check.suggestion {
545 recommendations.push(suggestion.clone());
546 }
547 }
548 }
549
550 match score.status {
552 HealthStatus::Critical => {
553 recommendations.push("URGENT: Plugin has critical health issues that must be addressed before deployment".to_string());
554 }
555 HealthStatus::Poor => {
556 recommendations.push("Plugin has multiple issues. Review all failed checks and fix high-priority items.".to_string());
557 }
558 HealthStatus::Fair => {
559 recommendations.push(
560 "Plugin has some issues. Address warnings and errors to improve health score."
561 .to_string(),
562 );
563 }
564 HealthStatus::Good => {
565 recommendations
566 .push("Plugin is healthy. Continue monitoring for regressions.".to_string());
567 }
568 HealthStatus::Excellent => {
569 recommendations.push(
570 "Excellent plugin health. Maintain current quality standards.".to_string(),
571 );
572 }
573 }
574
575 recommendations
576 }
577}
578
579impl Default for PluginHealthChecker {
580 fn default() -> Self {
581 Self::new()
582 }
583}
584
585#[cfg(test)]
586mod tests {
587 use super::*;
588
589 #[test]
590 fn test_health_severity_to_str() {
591 assert_eq!(HealthSeverity::Info.as_str(), "info");
592 assert_eq!(HealthSeverity::Warning.as_str(), "warning");
593 assert_eq!(HealthSeverity::Error.as_str(), "error");
594 assert_eq!(HealthSeverity::Critical.as_str(), "critical");
595 }
596
597 #[test]
598 fn test_health_severity_try_parse() {
599 assert_eq!(
600 HealthSeverity::try_parse("info"),
601 Some(HealthSeverity::Info)
602 );
603 assert_eq!(
604 HealthSeverity::try_parse("warning"),
605 Some(HealthSeverity::Warning)
606 );
607 assert_eq!(
608 HealthSeverity::try_parse("error"),
609 Some(HealthSeverity::Error)
610 );
611 assert_eq!(
612 HealthSeverity::try_parse("critical"),
613 Some(HealthSeverity::Critical)
614 );
615 assert_eq!(HealthSeverity::try_parse("unknown"), None);
616 }
617
618 #[test]
619 fn test_health_severity_ordering() {
620 assert!(HealthSeverity::Info < HealthSeverity::Warning);
621 assert!(HealthSeverity::Warning < HealthSeverity::Error);
622 assert!(HealthSeverity::Error < HealthSeverity::Critical);
623 }
624
625 #[test]
626 fn test_platform_current() {
627 let platform = Platform::current();
628 assert!(!matches!(platform, Platform::Unknown(_)));
629 }
630
631 #[test]
632 fn test_platform_to_str() {
633 assert_eq!(Platform::Linux.as_str(), "linux");
634 assert_eq!(Platform::MacOS.as_str(), "macos");
635 assert_eq!(Platform::Windows.as_str(), "windows");
636 }
637
638 #[test]
639 fn test_architecture_current() {
640 let arch = Architecture::current();
641 assert!(!matches!(arch, Architecture::Unknown(_)));
642 }
643
644 #[test]
645 fn test_architecture_to_str() {
646 assert_eq!(Architecture::X86_64.as_str(), "x86_64");
647 assert_eq!(Architecture::Arm64.as_str(), "arm64");
648 assert_eq!(Architecture::X86.as_str(), "x86");
649 assert_eq!(Architecture::Arm.as_str(), "arm");
650 }
651
652 #[test]
653 fn test_health_status_from_score() {
654 assert_eq!(HealthStatus::from_score(95), HealthStatus::Excellent);
655 assert_eq!(HealthStatus::from_score(80), HealthStatus::Good);
656 assert_eq!(HealthStatus::from_score(65), HealthStatus::Fair);
657 assert_eq!(HealthStatus::from_score(50), HealthStatus::Poor);
658 assert_eq!(HealthStatus::from_score(20), HealthStatus::Critical);
659 }
660
661 #[test]
662 fn test_health_status_to_str() {
663 assert_eq!(HealthStatus::Excellent.as_str(), "Excellent");
664 assert_eq!(HealthStatus::Good.as_str(), "Good");
665 assert_eq!(HealthStatus::Fair.as_str(), "Fair");
666 assert_eq!(HealthStatus::Poor.as_str(), "Poor");
667 assert_eq!(HealthStatus::Critical.as_str(), "Critical");
668 }
669
670 #[test]
671 fn test_checker_creation() {
672 let checker = PluginHealthChecker::new();
673 assert!(!checker.required_symbols.is_empty());
674 }
675
676 #[test]
677 fn test_checker_default_symbols() {
678 let checker = PluginHealthChecker::new();
679 assert!(checker.required_symbols.contains_key("plugin_init"));
680 assert!(checker.required_symbols.contains_key("plugin_get_info"));
681 assert!(checker.required_symbols.contains_key("plugin_shutdown"));
682 }
683
684 #[test]
685 fn test_required_symbol_properties() {
686 let checker = PluginHealthChecker::new();
687 let init_sym = checker.required_symbols.get("plugin_init").unwrap();
688 assert!(init_sym.required);
689
690 let shutdown_sym = checker.required_symbols.get("plugin_shutdown").unwrap();
691 assert!(!shutdown_sym.required);
692 }
693
694 #[test]
695 fn test_check_binary_exists_nonexistent() {
696 let checker = PluginHealthChecker::new();
697 let result = checker.check_binary_exists(Path::new("/nonexistent/plugin.so"));
698 assert!(!result.passed);
699 assert_eq!(result.severity, HealthSeverity::Critical);
700 }
701
702 #[test]
703 fn test_performance_baseline_check() {
704 let checker = PluginHealthChecker::new();
705 let baseline = PerformanceBaseline {
706 init_time_ms: 100.0,
707 shutdown_time_ms: 50.0,
708 memory_usage_mb: 100.0,
709 max_concurrent_calls: 50,
710 };
711 let result = checker.check_performance_baseline(&baseline);
712 assert!(result.passed);
713 }
714
715 #[test]
716 fn test_performance_baseline_check_failure() {
717 let checker = PluginHealthChecker::new();
718 let baseline = PerformanceBaseline {
719 init_time_ms: 10000.0, shutdown_time_ms: 50.0,
721 memory_usage_mb: 100.0,
722 max_concurrent_calls: 50,
723 };
724 let result = checker.check_performance_baseline(&baseline);
725 assert!(!result.passed);
726 assert!(result.details.is_some());
727 }
728
729 #[test]
730 fn test_calculate_health_score_all_passed() {
731 let checks = vec![
732 HealthCheckResult {
733 check_name: "Check 1".to_string(),
734 passed: true,
735 severity: HealthSeverity::Info,
736 message: "Passed".to_string(),
737 details: None,
738 suggestion: None,
739 },
740 HealthCheckResult {
741 check_name: "Check 2".to_string(),
742 passed: true,
743 severity: HealthSeverity::Info,
744 message: "Passed".to_string(),
745 details: None,
746 suggestion: None,
747 },
748 ];
749 let score = PluginHealthChecker::calculate_health_score(&checks);
750 assert_eq!(score.score, 100);
751 assert_eq!(score.status, HealthStatus::Excellent);
752 }
753
754 #[test]
755 fn test_calculate_health_score_with_errors() {
756 let checks = vec![
757 HealthCheckResult {
758 check_name: "Check 1".to_string(),
759 passed: true,
760 severity: HealthSeverity::Info,
761 message: "Passed".to_string(),
762 details: None,
763 suggestion: None,
764 },
765 HealthCheckResult {
766 check_name: "Check 2".to_string(),
767 passed: false,
768 severity: HealthSeverity::Error,
769 message: "Failed".to_string(),
770 details: None,
771 suggestion: None,
772 },
773 ];
774 let score = PluginHealthChecker::calculate_health_score(&checks);
775 assert!(score.score < 100);
776 }
777
778 #[test]
779 fn test_calculate_health_score_with_critical() {
780 let checks = vec![HealthCheckResult {
781 check_name: "Check 1".to_string(),
782 passed: false,
783 severity: HealthSeverity::Critical,
784 message: "Critical failure".to_string(),
785 details: None,
786 suggestion: None,
787 }];
788 let score = PluginHealthChecker::calculate_health_score(&checks);
789 assert!(score.score <= 30);
790 assert_eq!(score.status, HealthStatus::Critical);
791 }
792
793 #[test]
794 fn test_generate_recommendations_excellent() {
795 let checks = vec![];
796 let score = HealthScore {
797 score: 95,
798 status: HealthStatus::Excellent,
799 };
800 let recommendations = PluginHealthChecker::generate_recommendations(&checks, score);
801 assert!(!recommendations.is_empty());
802 assert!(recommendations[0].contains("Excellent"));
803 }
804
805 #[test]
806 fn test_generate_recommendations_critical() {
807 let checks = vec![];
808 let score = HealthScore {
809 score: 15,
810 status: HealthStatus::Critical,
811 };
812 let recommendations = PluginHealthChecker::generate_recommendations(&checks, score);
813 assert!(!recommendations.is_empty());
814 assert!(recommendations[0].to_uppercase().contains("URGENT"));
815 }
816
817 #[test]
818 fn test_required_symbols_check() {
819 let checker = PluginHealthChecker::new();
820 let result = checker.check_required_symbols();
821 assert!(result.passed);
822 }
823
824 #[test]
825 fn test_platform_compatibility_check() {
826 let checker = PluginHealthChecker::new();
827 let result =
828 checker.check_platform_compatibility(Path::new("/tmp/test.so"), Platform::Linux);
829 assert!(!result.passed);
831 }
832
833 #[test]
834 fn test_health_check_result_serialization() {
835 let result = HealthCheckResult {
836 check_name: "Test Check".to_string(),
837 passed: true,
838 severity: HealthSeverity::Info,
839 message: "Test message".to_string(),
840 details: Some("Details".to_string()),
841 suggestion: Some("Suggestion".to_string()),
842 };
843
844 let json = serde_json::to_string(&result).unwrap();
845 let deserialized: HealthCheckResult = serde_json::from_str(&json).unwrap();
846
847 assert_eq!(deserialized.check_name, result.check_name);
848 assert_eq!(deserialized.passed, result.passed);
849 assert_eq!(deserialized.severity, result.severity);
850 }
851
852 #[test]
853 fn test_health_report_serialization() {
854 let report = HealthReport {
855 plugin_id: "test-plugin".to_string(),
856 plugin_version: "1.0.0".to_string(),
857 check_timestamp: "2024-01-01T00:00:00Z".to_string(),
858 overall_health: HealthScore {
859 score: 85,
860 status: HealthStatus::Good,
861 },
862 checks: vec![],
863 binary_compatibility: None,
864 performance_baseline: None,
865 recommendations: vec![],
866 };
867
868 let json = serde_json::to_string(&report).unwrap();
869 let deserialized: HealthReport = serde_json::from_str(&json).unwrap();
870
871 assert_eq!(deserialized.plugin_id, report.plugin_id);
872 assert_eq!(deserialized.plugin_version, report.plugin_version);
873 }
874
875 #[test]
876 fn test_performance_baseline_serialization() {
877 let baseline = PerformanceBaseline {
878 init_time_ms: 100.0,
879 shutdown_time_ms: 50.0,
880 memory_usage_mb: 250.0,
881 max_concurrent_calls: 100,
882 };
883
884 let json = serde_json::to_string(&baseline).unwrap();
885 let deserialized: PerformanceBaseline = serde_json::from_str(&json).unwrap();
886
887 assert_eq!(deserialized.init_time_ms, baseline.init_time_ms);
888 assert_eq!(
889 deserialized.max_concurrent_calls,
890 baseline.max_concurrent_calls
891 );
892 }
893}