1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "lowercase")]
10pub enum Granularity {
11 Minute,
12 Hour,
13 Day,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
18pub struct MetricsAggregate {
19 pub id: Option<i64>,
20 pub timestamp: i64,
21 pub protocol: String,
22 pub method: Option<String>,
23 pub endpoint: Option<String>,
24 pub status_code: Option<i32>,
25 pub workspace_id: Option<String>,
26 pub environment: Option<String>,
27 pub request_count: i64,
28 pub error_count: i64,
29 pub latency_sum: f64,
30 pub latency_min: Option<f64>,
31 pub latency_max: Option<f64>,
32 pub latency_p50: Option<f64>,
33 pub latency_p95: Option<f64>,
34 pub latency_p99: Option<f64>,
35 pub bytes_sent: i64,
36 pub bytes_received: i64,
37 pub active_connections: Option<i64>,
38 pub created_at: Option<i64>,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
43pub struct HourMetricsAggregate {
44 pub id: Option<i64>,
45 pub timestamp: i64,
46 pub protocol: String,
47 pub method: Option<String>,
48 pub endpoint: Option<String>,
49 pub status_code: Option<i32>,
50 pub workspace_id: Option<String>,
51 pub environment: Option<String>,
52 pub request_count: i64,
53 pub error_count: i64,
54 pub latency_sum: f64,
55 pub latency_min: Option<f64>,
56 pub latency_max: Option<f64>,
57 pub latency_p50: Option<f64>,
58 pub latency_p95: Option<f64>,
59 pub latency_p99: Option<f64>,
60 pub bytes_sent: i64,
61 pub bytes_received: i64,
62 pub active_connections_avg: Option<f64>,
63 pub active_connections_max: Option<i64>,
64 pub created_at: Option<i64>,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
69pub struct DayMetricsAggregate {
70 pub id: Option<i64>,
71 pub date: String,
72 pub timestamp: i64,
73 pub protocol: String,
74 pub method: Option<String>,
75 pub endpoint: Option<String>,
76 pub status_code: Option<i32>,
77 pub workspace_id: Option<String>,
78 pub environment: Option<String>,
79 pub request_count: i64,
80 pub error_count: i64,
81 pub latency_sum: f64,
82 pub latency_min: Option<f64>,
83 pub latency_max: Option<f64>,
84 pub latency_p50: Option<f64>,
85 pub latency_p95: Option<f64>,
86 pub latency_p99: Option<f64>,
87 pub bytes_sent: i64,
88 pub bytes_received: i64,
89 pub active_connections_avg: Option<f64>,
90 pub active_connections_max: Option<i64>,
91 pub unique_clients: Option<i64>,
92 pub peak_hour: Option<i32>,
93 pub created_at: Option<i64>,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
98pub struct EndpointStats {
99 pub id: Option<i64>,
100 pub endpoint: String,
101 pub protocol: String,
102 pub method: Option<String>,
103 pub workspace_id: Option<String>,
104 pub environment: Option<String>,
105 pub total_requests: i64,
106 pub total_errors: i64,
107 pub avg_latency_ms: Option<f64>,
108 pub min_latency_ms: Option<f64>,
109 pub max_latency_ms: Option<f64>,
110 pub p95_latency_ms: Option<f64>,
111 pub status_codes: Option<String>, pub total_bytes_sent: i64,
113 pub total_bytes_received: i64,
114 pub first_seen: i64,
115 pub last_seen: i64,
116 pub updated_at: Option<i64>,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct StatusCodeBreakdown {
122 pub status_codes: HashMap<u16, i64>,
123}
124
125impl EndpointStats {
126 pub fn get_status_code_breakdown(&self) -> Result<StatusCodeBreakdown, serde_json::Error> {
128 if let Some(ref json) = self.status_codes {
129 let map: HashMap<String, i64> = serde_json::from_str(json)?;
130 let status_codes = map
131 .into_iter()
132 .filter_map(|(k, v)| k.parse::<u16>().ok().map(|code| (code, v)))
133 .collect();
134 Ok(StatusCodeBreakdown { status_codes })
135 } else {
136 Ok(StatusCodeBreakdown {
137 status_codes: HashMap::new(),
138 })
139 }
140 }
141
142 pub fn set_status_code_breakdown(
144 &mut self,
145 breakdown: &StatusCodeBreakdown,
146 ) -> Result<(), serde_json::Error> {
147 let map: HashMap<String, i64> =
148 breakdown.status_codes.iter().map(|(k, v)| (k.to_string(), *v)).collect();
149 self.status_codes = Some(serde_json::to_string(&map)?);
150 Ok(())
151 }
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
156pub struct ErrorEvent {
157 pub id: Option<i64>,
158 pub timestamp: i64,
159 pub protocol: String,
160 pub method: Option<String>,
161 pub endpoint: Option<String>,
162 pub status_code: Option<i32>,
163 pub error_type: Option<String>,
164 pub error_message: Option<String>,
165 pub error_category: Option<String>,
166 pub request_id: Option<String>,
167 pub trace_id: Option<String>,
168 pub span_id: Option<String>,
169 pub client_ip: Option<String>,
170 pub user_agent: Option<String>,
171 pub workspace_id: Option<String>,
172 pub environment: Option<String>,
173 pub metadata: Option<String>, pub created_at: Option<i64>,
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
179#[serde(rename_all = "snake_case")]
180pub enum ErrorCategory {
181 ClientError, ServerError, NetworkError,
184 TimeoutError,
185 Other,
186}
187
188impl ErrorCategory {
189 #[must_use]
191 pub const fn from_status_code(status_code: u16) -> Self {
192 match status_code {
193 400..=499 => Self::ClientError,
194 500..=599 => Self::ServerError,
195 _ => Self::Other,
196 }
197 }
198
199 #[must_use]
201 pub const fn as_str(&self) -> &'static str {
202 match self {
203 Self::ClientError => "client_error",
204 Self::ServerError => "server_error",
205 Self::NetworkError => "network_error",
206 Self::TimeoutError => "timeout_error",
207 Self::Other => "other",
208 }
209 }
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
214pub struct ClientAnalytics {
215 pub id: Option<i64>,
216 pub timestamp: i64,
217 pub client_ip: String,
218 pub user_agent: Option<String>,
219 pub user_agent_family: Option<String>,
220 pub user_agent_version: Option<String>,
221 pub protocol: String,
222 pub workspace_id: Option<String>,
223 pub environment: Option<String>,
224 pub request_count: i64,
225 pub error_count: i64,
226 pub avg_latency_ms: Option<f64>,
227 pub bytes_sent: i64,
228 pub bytes_received: i64,
229 pub top_endpoints: Option<String>, pub created_at: Option<i64>,
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
235pub struct TrafficPattern {
236 pub id: Option<i64>,
237 pub date: String,
238 pub hour: i32,
239 pub day_of_week: i32,
240 pub protocol: String,
241 pub workspace_id: Option<String>,
242 pub environment: Option<String>,
243 pub request_count: i64,
244 pub error_count: i64,
245 pub avg_latency_ms: Option<f64>,
246 pub unique_clients: Option<i64>,
247 pub created_at: Option<i64>,
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
252pub struct AnalyticsSnapshot {
253 pub id: Option<i64>,
254 pub timestamp: i64,
255 pub snapshot_type: String,
256 pub total_requests: i64,
257 pub total_errors: i64,
258 pub avg_latency_ms: Option<f64>,
259 pub active_connections: Option<i64>,
260 pub protocol_stats: Option<String>, pub top_endpoints: Option<String>, pub memory_usage_bytes: Option<i64>,
263 pub cpu_usage_percent: Option<f64>,
264 pub thread_count: Option<i32>,
265 pub uptime_seconds: Option<i64>,
266 pub workspace_id: Option<String>,
267 pub environment: Option<String>,
268 pub created_at: Option<i64>,
269}
270
271#[derive(Debug, Clone, Default, Serialize, Deserialize)]
273pub struct AnalyticsFilter {
274 pub start_time: Option<i64>,
275 pub end_time: Option<i64>,
276 pub protocol: Option<String>,
277 pub endpoint: Option<String>,
278 pub method: Option<String>,
279 pub status_code: Option<i32>,
280 pub workspace_id: Option<String>,
281 pub environment: Option<String>,
282 pub limit: Option<i64>,
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize)]
287pub struct OverviewMetrics {
288 pub total_requests: i64,
289 pub total_errors: i64,
290 pub error_rate: f64,
291 pub avg_latency_ms: f64,
292 pub p95_latency_ms: f64,
293 pub p99_latency_ms: f64,
294 pub active_connections: i64,
295 pub total_bytes_sent: i64,
296 pub total_bytes_received: i64,
297 pub requests_per_second: f64,
298 pub top_protocols: Vec<ProtocolStat>,
299 pub top_endpoints: Vec<EndpointStat>,
300}
301
302#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct ProtocolStat {
305 pub protocol: String,
306 pub request_count: i64,
307 pub error_count: i64,
308 pub avg_latency_ms: f64,
309}
310
311#[derive(Debug, Clone, Serialize, Deserialize)]
313pub struct EndpointStat {
314 pub endpoint: String,
315 pub protocol: String,
316 pub method: Option<String>,
317 pub request_count: i64,
318 pub error_count: i64,
319 pub error_rate: f64,
320 pub avg_latency_ms: f64,
321 pub p95_latency_ms: f64,
322}
323
324#[derive(Debug, Clone, Serialize, Deserialize)]
326pub struct TimeSeriesPoint {
327 pub timestamp: i64,
328 pub value: f64,
329}
330
331#[derive(Debug, Clone, Serialize, Deserialize)]
333pub struct TimeSeries {
334 pub label: String,
335 pub data: Vec<TimeSeriesPoint>,
336}
337
338#[derive(Debug, Clone, Serialize, Deserialize)]
340pub struct LatencyTrend {
341 pub timestamp: i64,
342 pub p50: f64,
343 pub p95: f64,
344 pub p99: f64,
345 pub avg: f64,
346 pub min: f64,
347 pub max: f64,
348}
349
350#[derive(Debug, Clone, Serialize, Deserialize)]
352pub struct ErrorSummary {
353 pub error_type: String,
354 pub error_category: String,
355 pub count: i64,
356 pub endpoints: Vec<String>,
357 pub last_occurrence: DateTime<Utc>,
358}
359
360#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
362#[serde(rename_all = "lowercase")]
363pub enum ExportFormat {
364 Csv,
365 Json,
366}
367
368#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
374pub struct ScenarioUsageMetrics {
375 pub id: Option<i64>,
376 pub scenario_id: String,
377 pub workspace_id: Option<String>,
378 pub org_id: Option<String>,
379 pub usage_count: i64,
380 pub last_used_at: Option<i64>,
381 pub usage_pattern: Option<String>, pub created_at: Option<i64>,
383 pub updated_at: Option<i64>,
384}
385
386#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
388pub struct PersonaCIHit {
389 pub id: Option<i64>,
390 pub persona_id: String,
391 pub workspace_id: Option<String>,
392 pub org_id: Option<String>,
393 pub ci_run_id: Option<String>,
394 pub hit_count: i64,
395 pub hit_at: i64,
396 pub created_at: Option<i64>,
397}
398
399#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
401pub struct EndpointCoverage {
402 pub id: Option<i64>,
403 pub endpoint: String,
404 pub method: Option<String>,
405 pub protocol: String,
406 pub workspace_id: Option<String>,
407 pub org_id: Option<String>,
408 pub test_count: i64,
409 pub last_tested_at: Option<i64>,
410 pub coverage_percentage: Option<f64>,
411 pub created_at: Option<i64>,
412 pub updated_at: Option<i64>,
413}
414
415#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
417pub struct RealityLevelStaleness {
418 pub id: Option<i64>,
419 pub workspace_id: String,
420 pub org_id: Option<String>,
421 pub endpoint: Option<String>,
422 pub method: Option<String>,
423 pub protocol: Option<String>,
424 pub current_reality_level: Option<String>,
425 pub last_updated_at: Option<i64>,
426 pub staleness_days: Option<i32>,
427 pub created_at: Option<i64>,
428 pub updated_at: Option<i64>,
429}
430
431#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
433pub struct DriftPercentageMetrics {
434 pub id: Option<i64>,
435 pub workspace_id: String,
436 pub org_id: Option<String>,
437 pub total_mocks: i64,
438 pub drifting_mocks: i64,
439 pub drift_percentage: f64,
440 pub measured_at: i64,
441 pub created_at: Option<i64>,
442}
443
444#[cfg(test)]
445mod tests {
446 use super::*;
447
448 #[test]
451 fn test_granularity_serialize() {
452 let minute = Granularity::Minute;
453 let hour = Granularity::Hour;
454 let day = Granularity::Day;
455
456 assert_eq!(serde_json::to_string(&minute).unwrap(), "\"minute\"");
457 assert_eq!(serde_json::to_string(&hour).unwrap(), "\"hour\"");
458 assert_eq!(serde_json::to_string(&day).unwrap(), "\"day\"");
459 }
460
461 #[test]
462 fn test_granularity_deserialize() {
463 let minute: Granularity = serde_json::from_str("\"minute\"").unwrap();
464 let hour: Granularity = serde_json::from_str("\"hour\"").unwrap();
465 let day: Granularity = serde_json::from_str("\"day\"").unwrap();
466
467 assert_eq!(minute, Granularity::Minute);
468 assert_eq!(hour, Granularity::Hour);
469 assert_eq!(day, Granularity::Day);
470 }
471
472 #[test]
473 fn test_granularity_clone() {
474 let g = Granularity::Hour;
475 let cloned = g.clone();
476 assert_eq!(g, cloned);
477 }
478
479 #[test]
482 fn test_error_category_from_status_code_client() {
483 assert_eq!(ErrorCategory::from_status_code(400), ErrorCategory::ClientError);
484 assert_eq!(ErrorCategory::from_status_code(401), ErrorCategory::ClientError);
485 assert_eq!(ErrorCategory::from_status_code(403), ErrorCategory::ClientError);
486 assert_eq!(ErrorCategory::from_status_code(404), ErrorCategory::ClientError);
487 assert_eq!(ErrorCategory::from_status_code(499), ErrorCategory::ClientError);
488 }
489
490 #[test]
491 fn test_error_category_from_status_code_server() {
492 assert_eq!(ErrorCategory::from_status_code(500), ErrorCategory::ServerError);
493 assert_eq!(ErrorCategory::from_status_code(502), ErrorCategory::ServerError);
494 assert_eq!(ErrorCategory::from_status_code(503), ErrorCategory::ServerError);
495 assert_eq!(ErrorCategory::from_status_code(504), ErrorCategory::ServerError);
496 assert_eq!(ErrorCategory::from_status_code(599), ErrorCategory::ServerError);
497 }
498
499 #[test]
500 fn test_error_category_from_status_code_other() {
501 assert_eq!(ErrorCategory::from_status_code(200), ErrorCategory::Other);
502 assert_eq!(ErrorCategory::from_status_code(301), ErrorCategory::Other);
503 assert_eq!(ErrorCategory::from_status_code(0), ErrorCategory::Other);
504 }
505
506 #[test]
507 fn test_error_category_as_str() {
508 assert_eq!(ErrorCategory::ClientError.as_str(), "client_error");
509 assert_eq!(ErrorCategory::ServerError.as_str(), "server_error");
510 assert_eq!(ErrorCategory::NetworkError.as_str(), "network_error");
511 assert_eq!(ErrorCategory::TimeoutError.as_str(), "timeout_error");
512 assert_eq!(ErrorCategory::Other.as_str(), "other");
513 }
514
515 #[test]
516 fn test_error_category_serialize() {
517 assert_eq!(serde_json::to_string(&ErrorCategory::ClientError).unwrap(), "\"client_error\"");
518 assert_eq!(serde_json::to_string(&ErrorCategory::ServerError).unwrap(), "\"server_error\"");
519 }
520
521 #[test]
524 fn test_export_format_serialize() {
525 assert_eq!(serde_json::to_string(&ExportFormat::Csv).unwrap(), "\"csv\"");
526 assert_eq!(serde_json::to_string(&ExportFormat::Json).unwrap(), "\"json\"");
527 }
528
529 #[test]
530 fn test_export_format_deserialize() {
531 let csv: ExportFormat = serde_json::from_str("\"csv\"").unwrap();
532 let json_fmt: ExportFormat = serde_json::from_str("\"json\"").unwrap();
533
534 assert_eq!(csv, ExportFormat::Csv);
535 assert_eq!(json_fmt, ExportFormat::Json);
536 }
537
538 #[test]
541 fn test_endpoint_stats_get_status_code_breakdown() {
542 let stats = EndpointStats {
543 id: Some(1),
544 endpoint: "/api/users".to_string(),
545 protocol: "http".to_string(),
546 method: Some("GET".to_string()),
547 workspace_id: None,
548 environment: None,
549 total_requests: 100,
550 total_errors: 5,
551 avg_latency_ms: Some(50.0),
552 min_latency_ms: Some(10.0),
553 max_latency_ms: Some(200.0),
554 p95_latency_ms: Some(150.0),
555 status_codes: Some(r#"{"200": 90, "404": 5, "500": 5}"#.to_string()),
556 total_bytes_sent: 10000,
557 total_bytes_received: 5000,
558 first_seen: 1000,
559 last_seen: 2000,
560 updated_at: None,
561 };
562
563 let breakdown = stats.get_status_code_breakdown().unwrap();
564 assert_eq!(breakdown.status_codes.get(&200), Some(&90));
565 assert_eq!(breakdown.status_codes.get(&404), Some(&5));
566 assert_eq!(breakdown.status_codes.get(&500), Some(&5));
567 }
568
569 #[test]
570 fn test_endpoint_stats_get_status_code_breakdown_none() {
571 let stats = EndpointStats {
572 id: None,
573 endpoint: "/api/test".to_string(),
574 protocol: "http".to_string(),
575 method: None,
576 workspace_id: None,
577 environment: None,
578 total_requests: 0,
579 total_errors: 0,
580 avg_latency_ms: None,
581 min_latency_ms: None,
582 max_latency_ms: None,
583 p95_latency_ms: None,
584 status_codes: None,
585 total_bytes_sent: 0,
586 total_bytes_received: 0,
587 first_seen: 0,
588 last_seen: 0,
589 updated_at: None,
590 };
591
592 let breakdown = stats.get_status_code_breakdown().unwrap();
593 assert!(breakdown.status_codes.is_empty());
594 }
595
596 #[test]
597 fn test_endpoint_stats_set_status_code_breakdown() {
598 let mut stats = EndpointStats {
599 id: None,
600 endpoint: "/api/test".to_string(),
601 protocol: "http".to_string(),
602 method: None,
603 workspace_id: None,
604 environment: None,
605 total_requests: 0,
606 total_errors: 0,
607 avg_latency_ms: None,
608 min_latency_ms: None,
609 max_latency_ms: None,
610 p95_latency_ms: None,
611 status_codes: None,
612 total_bytes_sent: 0,
613 total_bytes_received: 0,
614 first_seen: 0,
615 last_seen: 0,
616 updated_at: None,
617 };
618
619 let breakdown = StatusCodeBreakdown {
620 status_codes: HashMap::from([(200, 100), (500, 10)]),
621 };
622
623 stats.set_status_code_breakdown(&breakdown).unwrap();
624 assert!(stats.status_codes.is_some());
625
626 let restored = stats.get_status_code_breakdown().unwrap();
628 assert_eq!(restored.status_codes.get(&200), Some(&100));
629 assert_eq!(restored.status_codes.get(&500), Some(&10));
630 }
631
632 #[test]
635 fn test_analytics_filter_default() {
636 let filter = AnalyticsFilter::default();
637 assert!(filter.start_time.is_none());
638 assert!(filter.end_time.is_none());
639 assert!(filter.protocol.is_none());
640 assert!(filter.endpoint.is_none());
641 assert!(filter.limit.is_none());
642 }
643
644 #[test]
645 fn test_analytics_filter_serialize() {
646 let filter = AnalyticsFilter {
647 start_time: Some(1000),
648 end_time: Some(2000),
649 protocol: Some("http".to_string()),
650 endpoint: Some("/api/users".to_string()),
651 method: Some("GET".to_string()),
652 status_code: Some(200),
653 workspace_id: None,
654 environment: None,
655 limit: Some(100),
656 };
657
658 let json = serde_json::to_string(&filter).unwrap();
659 assert!(json.contains("1000"));
660 assert!(json.contains("http"));
661 assert!(json.contains("/api/users"));
662 }
663
664 #[test]
665 fn test_analytics_filter_clone() {
666 let filter = AnalyticsFilter {
667 start_time: Some(1000),
668 protocol: Some("grpc".to_string()),
669 ..Default::default()
670 };
671
672 let cloned = filter.clone();
673 assert_eq!(filter.start_time, cloned.start_time);
674 assert_eq!(filter.protocol, cloned.protocol);
675 }
676
677 #[test]
680 fn test_overview_metrics_serialize() {
681 let metrics = OverviewMetrics {
682 total_requests: 1000,
683 total_errors: 50,
684 error_rate: 0.05,
685 avg_latency_ms: 100.0,
686 p95_latency_ms: 250.0,
687 p99_latency_ms: 500.0,
688 active_connections: 10,
689 total_bytes_sent: 100000,
690 total_bytes_received: 50000,
691 requests_per_second: 10.5,
692 top_protocols: vec![],
693 top_endpoints: vec![],
694 };
695
696 let json = serde_json::to_string(&metrics).unwrap();
697 assert!(json.contains("1000"));
698 assert!(json.contains("0.05"));
699 }
700
701 #[test]
704 fn test_protocol_stat_serialize() {
705 let stat = ProtocolStat {
706 protocol: "http".to_string(),
707 request_count: 1000,
708 error_count: 10,
709 avg_latency_ms: 50.0,
710 };
711
712 let json = serde_json::to_string(&stat).unwrap();
713 assert!(json.contains("http"));
714 assert!(json.contains("1000"));
715 }
716
717 #[test]
720 fn test_endpoint_stat_serialize() {
721 let stat = EndpointStat {
722 endpoint: "/api/users".to_string(),
723 protocol: "http".to_string(),
724 method: Some("GET".to_string()),
725 request_count: 500,
726 error_count: 5,
727 error_rate: 0.01,
728 avg_latency_ms: 75.0,
729 p95_latency_ms: 150.0,
730 };
731
732 let json = serde_json::to_string(&stat).unwrap();
733 assert!(json.contains("/api/users"));
734 assert!(json.contains("GET"));
735 }
736
737 #[test]
740 fn test_time_series_point_serialize() {
741 let point = TimeSeriesPoint {
742 timestamp: 1234567890,
743 value: 42.5,
744 };
745
746 let json = serde_json::to_string(&point).unwrap();
747 assert!(json.contains("1234567890"));
748 assert!(json.contains("42.5"));
749 }
750
751 #[test]
754 fn test_time_series_serialize() {
755 let series = TimeSeries {
756 label: "requests".to_string(),
757 data: vec![
758 TimeSeriesPoint {
759 timestamp: 1000,
760 value: 10.0,
761 },
762 TimeSeriesPoint {
763 timestamp: 2000,
764 value: 20.0,
765 },
766 ],
767 };
768
769 let json = serde_json::to_string(&series).unwrap();
770 assert!(json.contains("requests"));
771 assert!(json.contains("1000"));
772 }
773
774 #[test]
777 fn test_latency_trend_serialize() {
778 let trend = LatencyTrend {
779 timestamp: 1000,
780 p50: 50.0,
781 p95: 150.0,
782 p99: 250.0,
783 avg: 75.0,
784 min: 10.0,
785 max: 500.0,
786 };
787
788 let json = serde_json::to_string(&trend).unwrap();
789 assert!(json.contains("p50"));
790 assert!(json.contains("p95"));
791 assert!(json.contains("p99"));
792 }
793
794 #[test]
797 fn test_metrics_aggregate_clone() {
798 let agg = MetricsAggregate {
799 id: Some(1),
800 timestamp: 1000,
801 protocol: "http".to_string(),
802 method: Some("GET".to_string()),
803 endpoint: Some("/api/users".to_string()),
804 status_code: Some(200),
805 workspace_id: None,
806 environment: None,
807 request_count: 100,
808 error_count: 5,
809 latency_sum: 5000.0,
810 latency_min: Some(10.0),
811 latency_max: Some(200.0),
812 latency_p50: Some(50.0),
813 latency_p95: Some(150.0),
814 latency_p99: Some(180.0),
815 bytes_sent: 10000,
816 bytes_received: 5000,
817 active_connections: Some(10),
818 created_at: None,
819 };
820
821 let cloned = agg.clone();
822 assert_eq!(agg.timestamp, cloned.timestamp);
823 assert_eq!(agg.request_count, cloned.request_count);
824 }
825
826 #[test]
829 fn test_error_event_serialize() {
830 let event = ErrorEvent {
831 id: Some(1),
832 timestamp: 1000,
833 protocol: "http".to_string(),
834 method: Some("POST".to_string()),
835 endpoint: Some("/api/orders".to_string()),
836 status_code: Some(500),
837 error_type: Some("InternalServerError".to_string()),
838 error_message: Some("Database connection failed".to_string()),
839 error_category: Some("server_error".to_string()),
840 request_id: Some("req-123".to_string()),
841 trace_id: None,
842 span_id: None,
843 client_ip: Some("192.168.1.1".to_string()),
844 user_agent: Some("TestClient/1.0".to_string()),
845 workspace_id: None,
846 environment: None,
847 metadata: None,
848 created_at: None,
849 };
850
851 let json = serde_json::to_string(&event).unwrap();
852 assert!(json.contains("InternalServerError"));
853 assert!(json.contains("Database connection failed"));
854 }
855
856 #[test]
859 fn test_traffic_pattern_serialize() {
860 let pattern = TrafficPattern {
861 id: Some(1),
862 date: "2024-01-15".to_string(),
863 hour: 14,
864 day_of_week: 1,
865 protocol: "http".to_string(),
866 workspace_id: None,
867 environment: None,
868 request_count: 500,
869 error_count: 10,
870 avg_latency_ms: Some(50.0),
871 unique_clients: Some(25),
872 created_at: None,
873 };
874
875 let json = serde_json::to_string(&pattern).unwrap();
876 assert!(json.contains("2024-01-15"));
877 assert!(json.contains("14"));
878 }
879
880 #[test]
883 fn test_drift_percentage_metrics_serialize() {
884 let metrics = DriftPercentageMetrics {
885 id: Some(1),
886 workspace_id: "ws-123".to_string(),
887 org_id: Some("org-456".to_string()),
888 total_mocks: 100,
889 drifting_mocks: 15,
890 drift_percentage: 15.0,
891 measured_at: 1000,
892 created_at: None,
893 };
894
895 let json = serde_json::to_string(&metrics).unwrap();
896 assert!(json.contains("ws-123"));
897 assert!(json.contains("15.0"));
898 }
899}