1#[derive(Debug, Clone, serde::Serialize)]
7pub struct ServerHealth {
8 pub status: String,
10 pub uptime: u64,
12 pub active_connections: u32,
14 pub memory_usage: u64,
16 pub cpu_usage: f64,
18 pub storage_health: crate::storage::StorageHealthStatus,
20}
21
22#[derive(Debug, Clone)]
24pub struct ServerRequestMetrics {
25 pub request_id: String,
27 pub method: String,
29 pub path: String,
31 pub status_code: u16,
33 pub response_time_ms: u64,
35 pub request_size: u64,
37 pub response_size: u64,
39 pub user_agent: Option<String>,
41 pub client_ip: Option<String>,
43 pub user_id: Option<uuid::Uuid>,
45 pub api_key_id: Option<uuid::Uuid>,
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52 use crate::storage::StorageHealthStatus;
53 use uuid::Uuid;
54
55 #[test]
58 fn test_server_health_creation() {
59 let health = ServerHealth {
60 status: "healthy".to_string(),
61 uptime: 3600,
62 active_connections: 100,
63 memory_usage: 1024 * 1024 * 512, cpu_usage: 25.5,
65 storage_health: StorageHealthStatus {
66 database: true,
67 redis: true,
68 files: true,
69 vector: true,
70 overall: true,
71 },
72 };
73
74 assert_eq!(health.status, "healthy");
75 assert_eq!(health.uptime, 3600);
76 assert_eq!(health.active_connections, 100);
77 }
78
79 #[test]
80 fn test_server_health_unhealthy() {
81 let health = ServerHealth {
82 status: "unhealthy".to_string(),
83 uptime: 100,
84 active_connections: 0,
85 memory_usage: 1024 * 1024 * 1024, cpu_usage: 95.0,
87 storage_health: StorageHealthStatus {
88 database: false,
89 redis: false,
90 files: true,
91 vector: false,
92 overall: false,
93 },
94 };
95
96 assert_eq!(health.status, "unhealthy");
97 assert!(!health.storage_health.overall);
98 }
99
100 #[test]
101 fn test_server_health_clone() {
102 let health = ServerHealth {
103 status: "healthy".to_string(),
104 uptime: 1000,
105 active_connections: 50,
106 memory_usage: 500000,
107 cpu_usage: 10.0,
108 storage_health: StorageHealthStatus {
109 database: true,
110 redis: true,
111 files: true,
112 vector: true,
113 overall: true,
114 },
115 };
116
117 let cloned = health.clone();
118 assert_eq!(cloned.status, health.status);
119 assert_eq!(cloned.uptime, health.uptime);
120 }
121
122 #[test]
123 fn test_server_health_debug() {
124 let health = ServerHealth {
125 status: "healthy".to_string(),
126 uptime: 100,
127 active_connections: 10,
128 memory_usage: 100000,
129 cpu_usage: 5.0,
130 storage_health: StorageHealthStatus {
131 database: true,
132 redis: true,
133 files: true,
134 vector: true,
135 overall: true,
136 },
137 };
138
139 let debug_str = format!("{:?}", health);
140 assert!(debug_str.contains("ServerHealth"));
141 assert!(debug_str.contains("healthy"));
142 }
143
144 #[test]
145 fn test_server_health_serialization() {
146 let health = ServerHealth {
147 status: "healthy".to_string(),
148 uptime: 7200,
149 active_connections: 200,
150 memory_usage: 1024 * 1024 * 256,
151 cpu_usage: 30.0,
152 storage_health: StorageHealthStatus {
153 database: true,
154 redis: true,
155 files: true,
156 vector: true,
157 overall: true,
158 },
159 };
160
161 let json = serde_json::to_value(&health).unwrap();
162 assert_eq!(json["status"], "healthy");
163 assert_eq!(json["uptime"], 7200);
164 assert_eq!(json["active_connections"], 200);
165 }
166
167 #[test]
168 fn test_server_health_high_load() {
169 let health = ServerHealth {
170 status: "degraded".to_string(),
171 uptime: 86400,
172 active_connections: 10000,
173 memory_usage: 8 * 1024 * 1024 * 1024, cpu_usage: 85.0,
175 storage_health: StorageHealthStatus {
176 database: true,
177 redis: true,
178 files: true,
179 vector: true,
180 overall: true,
181 },
182 };
183
184 assert_eq!(health.status, "degraded");
185 assert!(health.cpu_usage > 80.0);
186 }
187
188 #[test]
189 fn test_server_health_zero_uptime() {
190 let health = ServerHealth {
191 status: "starting".to_string(),
192 uptime: 0,
193 active_connections: 0,
194 memory_usage: 50 * 1024 * 1024,
195 cpu_usage: 0.1,
196 storage_health: StorageHealthStatus {
197 database: true,
198 redis: true,
199 files: true,
200 vector: true,
201 overall: true,
202 },
203 };
204
205 assert_eq!(health.uptime, 0);
206 assert_eq!(health.active_connections, 0);
207 }
208
209 #[test]
212 fn test_request_metrics_creation() {
213 let metrics = ServerRequestMetrics {
214 request_id: "req-123".to_string(),
215 method: "GET".to_string(),
216 path: "/api/v1/users".to_string(),
217 status_code: 200,
218 response_time_ms: 150,
219 request_size: 256,
220 response_size: 1024,
221 user_agent: Some("Mozilla/5.0".to_string()),
222 client_ip: Some("192.168.1.1".to_string()),
223 user_id: Some(Uuid::new_v4()),
224 api_key_id: Some(Uuid::new_v4()),
225 };
226
227 assert_eq!(metrics.request_id, "req-123");
228 assert_eq!(metrics.method, "GET");
229 assert_eq!(metrics.status_code, 200);
230 }
231
232 #[test]
233 fn test_request_metrics_minimal() {
234 let metrics = ServerRequestMetrics {
235 request_id: "req-456".to_string(),
236 method: "POST".to_string(),
237 path: "/".to_string(),
238 status_code: 201,
239 response_time_ms: 50,
240 request_size: 100,
241 response_size: 50,
242 user_agent: None,
243 client_ip: None,
244 user_id: None,
245 api_key_id: None,
246 };
247
248 assert!(metrics.user_agent.is_none());
249 assert!(metrics.client_ip.is_none());
250 assert!(metrics.user_id.is_none());
251 assert!(metrics.api_key_id.is_none());
252 }
253
254 #[test]
255 fn test_request_metrics_clone() {
256 let user_id = Uuid::new_v4();
257 let metrics = ServerRequestMetrics {
258 request_id: "req-789".to_string(),
259 method: "PUT".to_string(),
260 path: "/api/resource".to_string(),
261 status_code: 200,
262 response_time_ms: 100,
263 request_size: 500,
264 response_size: 200,
265 user_agent: Some("Custom Agent".to_string()),
266 client_ip: None,
267 user_id: Some(user_id),
268 api_key_id: None,
269 };
270
271 let cloned = metrics.clone();
272 assert_eq!(cloned.request_id, metrics.request_id);
273 assert_eq!(cloned.user_id, metrics.user_id);
274 }
275
276 #[test]
277 fn test_request_metrics_debug() {
278 let metrics = ServerRequestMetrics {
279 request_id: "req-abc".to_string(),
280 method: "DELETE".to_string(),
281 path: "/api/item/1".to_string(),
282 status_code: 204,
283 response_time_ms: 30,
284 request_size: 0,
285 response_size: 0,
286 user_agent: None,
287 client_ip: None,
288 user_id: None,
289 api_key_id: None,
290 };
291
292 let debug_str = format!("{:?}", metrics);
293 assert!(debug_str.contains("RequestMetrics"));
294 assert!(debug_str.contains("DELETE"));
295 }
296
297 #[test]
298 fn test_request_metrics_different_methods() {
299 let methods = vec!["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
300
301 for method in methods {
302 let metrics = ServerRequestMetrics {
303 request_id: format!("req-{}", method.to_lowercase()),
304 method: method.to_string(),
305 path: "/test".to_string(),
306 status_code: 200,
307 response_time_ms: 10,
308 request_size: 0,
309 response_size: 0,
310 user_agent: None,
311 client_ip: None,
312 user_id: None,
313 api_key_id: None,
314 };
315
316 assert_eq!(metrics.method, method);
317 }
318 }
319
320 #[test]
321 fn test_request_metrics_error_status() {
322 let metrics = ServerRequestMetrics {
323 request_id: "req-error".to_string(),
324 method: "GET".to_string(),
325 path: "/api/not-found".to_string(),
326 status_code: 404,
327 response_time_ms: 5,
328 request_size: 0,
329 response_size: 100,
330 user_agent: None,
331 client_ip: None,
332 user_id: None,
333 api_key_id: None,
334 };
335
336 assert!(metrics.status_code >= 400);
337 }
338
339 #[test]
340 fn test_request_metrics_server_error() {
341 let metrics = ServerRequestMetrics {
342 request_id: "req-server-error".to_string(),
343 method: "POST".to_string(),
344 path: "/api/process".to_string(),
345 status_code: 500,
346 response_time_ms: 100,
347 request_size: 1000,
348 response_size: 50,
349 user_agent: None,
350 client_ip: None,
351 user_id: None,
352 api_key_id: None,
353 };
354
355 assert!(metrics.status_code >= 500);
356 }
357
358 #[test]
359 fn test_request_metrics_slow_request() {
360 let metrics = ServerRequestMetrics {
361 request_id: "req-slow".to_string(),
362 method: "GET".to_string(),
363 path: "/api/heavy-computation".to_string(),
364 status_code: 200,
365 response_time_ms: 30000, request_size: 100,
367 response_size: 10000,
368 user_agent: None,
369 client_ip: None,
370 user_id: None,
371 api_key_id: None,
372 };
373
374 assert!(metrics.response_time_ms > 10000);
375 }
376
377 #[test]
378 fn test_request_metrics_large_payload() {
379 let metrics = ServerRequestMetrics {
380 request_id: "req-large".to_string(),
381 method: "POST".to_string(),
382 path: "/api/upload".to_string(),
383 status_code: 200,
384 response_time_ms: 5000,
385 request_size: 10 * 1024 * 1024, response_size: 100,
387 user_agent: None,
388 client_ip: None,
389 user_id: None,
390 api_key_id: None,
391 };
392
393 assert!(metrics.request_size > 1024 * 1024);
394 }
395
396 #[test]
399 fn test_server_health_with_partial_storage() {
400 let health = ServerHealth {
401 status: "degraded".to_string(),
402 uptime: 1000,
403 active_connections: 50,
404 memory_usage: 500 * 1024 * 1024,
405 cpu_usage: 40.0,
406 storage_health: StorageHealthStatus {
407 database: true,
408 redis: false, files: true,
410 vector: true,
411 overall: false, },
413 };
414
415 assert!(!health.storage_health.redis);
416 assert!(!health.storage_health.overall);
417 }
418
419 #[test]
420 fn test_request_metrics_authenticated() {
421 let user_id = Uuid::new_v4();
422 let api_key_id = Uuid::new_v4();
423
424 let metrics = ServerRequestMetrics {
425 request_id: "req-auth".to_string(),
426 method: "GET".to_string(),
427 path: "/api/protected".to_string(),
428 status_code: 200,
429 response_time_ms: 50,
430 request_size: 100,
431 response_size: 500,
432 user_agent: Some("API Client/1.0".to_string()),
433 client_ip: Some("10.0.0.1".to_string()),
434 user_id: Some(user_id),
435 api_key_id: Some(api_key_id),
436 };
437
438 assert!(metrics.user_id.is_some());
439 assert!(metrics.api_key_id.is_some());
440 }
441
442 #[test]
443 fn test_request_metrics_status_classification() {
444 let status_codes = vec![
445 (200, "success"),
446 (201, "success"),
447 (301, "redirect"),
448 (400, "client_error"),
449 (401, "client_error"),
450 (403, "client_error"),
451 (404, "client_error"),
452 (500, "server_error"),
453 (502, "server_error"),
454 (503, "server_error"),
455 ];
456
457 for (code, expected_type) in status_codes {
458 let actual_type = if code >= 500 {
459 "server_error"
460 } else if code >= 400 {
461 "client_error"
462 } else if code >= 300 {
463 "redirect"
464 } else {
465 "success"
466 };
467
468 assert_eq!(
469 actual_type, expected_type,
470 "Status code {} classification",
471 code
472 );
473 }
474 }
475}