1use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use std::sync::Arc;
9
10use crate::lifecycle::{HealthStatus, ServiceStatus};
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct HealthReport {
15 pub status: HealthStatus,
16 pub services: Vec<ServiceHealth>,
17 pub timestamp: DateTime<Utc>,
18}
19
20impl Default for HealthReport {
21 fn default() -> Self {
22 Self {
23 status: HealthStatus::Healthy,
24 services: Vec::new(),
25 timestamp: Utc::now(),
26 }
27 }
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct ServiceHealth {
33 pub name: String,
34 pub status: ServiceStatus,
35}
36
37pub type HealthChecker = Arc<dyn Fn() -> HealthReport + Send + Sync>;
38
39pub trait HealthSource: Send + Sync {
43 fn liveness(&self) -> HealthStatus;
44 fn readiness(&self) -> HealthStatus;
45
46 fn startup(&self) -> HealthStatus {
48 HealthStatus::Healthy
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55
56 #[test]
57 fn test_health_source_default_startup() {
58 struct MinimalSource;
59 impl HealthSource for MinimalSource {
60 fn liveness(&self) -> HealthStatus {
61 HealthStatus::Healthy
62 }
63
64 fn readiness(&self) -> HealthStatus {
65 HealthStatus::Healthy
66 }
67 }
68 let s = MinimalSource;
69 assert_eq!(s.startup(), HealthStatus::Healthy);
70 }
71
72 #[test]
73 fn test_health_source_custom_startup() {
74 struct BootingSource;
75 impl HealthSource for BootingSource {
76 fn liveness(&self) -> HealthStatus {
77 HealthStatus::Healthy
78 }
79
80 fn readiness(&self) -> HealthStatus {
81 HealthStatus::Healthy
82 }
83
84 fn startup(&self) -> HealthStatus {
85 HealthStatus::Unhealthy
86 }
87 }
88 let s = BootingSource;
89 assert_eq!(s.startup(), HealthStatus::Unhealthy);
90 }
91
92 #[test]
93 fn test_health_report_serialization() {
94 let report = HealthReport {
95 status: HealthStatus::Healthy,
96 services: vec![ServiceHealth {
97 name: "prometheus".to_string(),
98 status: ServiceStatus::Started,
99 }],
100 timestamp: chrono::Utc::now(),
101 };
102
103 let json = serde_json::to_string(&report).unwrap();
104 assert!(json.contains("Healthy"));
105 assert!(json.contains("prometheus"));
106 assert!(json.contains("Started"));
107 assert!(json.contains("timestamp"));
108 }
109
110 #[test]
111 fn test_health_report_default() {
112 let report = HealthReport::default();
113 assert_eq!(report.status, HealthStatus::Healthy);
114 assert!(report.services.is_empty());
115 assert!(report.timestamp <= chrono::Utc::now());
116 }
117
118 #[test]
119 fn test_service_health_serialization_round_trip() {
120 let svc = ServiceHealth {
121 name: "kafka".to_string(),
122 status: ServiceStatus::Stopped,
123 };
124
125 let json = serde_json::to_string(&svc).unwrap();
126 assert!(json.contains("kafka"));
127 assert!(json.contains("Stopped"));
128
129 let decoded: ServiceHealth = serde_json::from_str(&json).unwrap();
130 assert_eq!(decoded.name, "kafka");
131 assert_eq!(decoded.status, ServiceStatus::Stopped);
132 }
133
134 #[test]
135 fn test_health_checker_type_alias_invocation() {
136 let checker: HealthChecker = Arc::new(|| HealthReport {
137 status: HealthStatus::Unhealthy,
138 services: vec![ServiceHealth {
139 name: "db".to_string(),
140 status: ServiceStatus::Started,
141 }],
142 timestamp: chrono::Utc::now(),
143 });
144
145 let report = checker();
146 assert_eq!(report.status, HealthStatus::Unhealthy);
147 assert_eq!(report.services.len(), 1);
148 assert_eq!(report.services[0].name, "db");
149 }
150
151 #[test]
152 fn test_health_source_liveness_and_readiness() {
153 struct MixedSource;
154 impl HealthSource for MixedSource {
155 fn liveness(&self) -> HealthStatus {
156 HealthStatus::Healthy
157 }
158
159 fn readiness(&self) -> HealthStatus {
160 HealthStatus::Unhealthy
161 }
162 }
163
164 let s = MixedSource;
165 assert_eq!(s.liveness(), HealthStatus::Healthy);
166 assert_eq!(s.readiness(), HealthStatus::Unhealthy);
167 }
168}