Skip to main content

litellm_rs/server/
types.rs

1//! Server types for monitoring and health checks
2//!
3//! This module provides types used for server monitoring and metrics.
4
5/// Server health status
6#[derive(Debug, Clone, serde::Serialize)]
7pub struct ServerHealth {
8    /// Server status
9    pub status: String,
10    /// Server uptime in seconds
11    pub uptime: u64,
12    /// Number of active connections
13    pub active_connections: u32,
14    /// Memory usage in bytes
15    pub memory_usage: u64,
16    /// CPU usage percentage
17    pub cpu_usage: f64,
18    /// Storage health
19    pub storage_health: crate::storage::StorageHealthStatus,
20}
21
22/// Request metrics for monitoring
23#[derive(Debug, Clone)]
24pub struct ServerRequestMetrics {
25    /// Request ID
26    pub request_id: String,
27    /// HTTP method
28    pub method: String,
29    /// Request path
30    pub path: String,
31    /// Response status code
32    pub status_code: u16,
33    /// Response time in milliseconds
34    pub response_time_ms: u64,
35    /// Request size in bytes
36    pub request_size: u64,
37    /// Response size in bytes
38    pub response_size: u64,
39    /// User agent
40    pub user_agent: Option<String>,
41    /// Client IP address
42    pub client_ip: Option<String>,
43    /// User ID (if authenticated)
44    pub user_id: Option<uuid::Uuid>,
45    /// API key ID (if used)
46    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    // ==================== ServerHealth Tests ====================
56
57    #[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, // 512MB
64            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, // 1GB
86            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, // 8GB
174            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    // ==================== RequestMetrics Tests ====================
210
211    #[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, // 30 seconds
366            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, // 10MB
386            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    // ==================== Integration Tests ====================
397
398    #[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, // Redis is down
409                files: true,
410                vector: true,
411                overall: false, // Overall is unhealthy due to Redis
412            },
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}