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,
13 Hour,
15 Day,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
21#[allow(clippy::missing_docs_in_private_items)]
22pub struct MetricsAggregate {
23 pub id: Option<i64>,
25 pub timestamp: i64,
27 pub protocol: String,
29 pub method: Option<String>,
31 pub endpoint: Option<String>,
33 pub status_code: Option<i32>,
35 pub workspace_id: Option<String>,
37 pub environment: Option<String>,
39 pub request_count: i64,
41 pub error_count: i64,
43 pub latency_sum: f64,
45 pub latency_min: Option<f64>,
47 pub latency_max: Option<f64>,
49 pub latency_p50: Option<f64>,
51 pub latency_p95: Option<f64>,
53 pub latency_p99: Option<f64>,
55 pub bytes_sent: i64,
57 pub bytes_received: i64,
59 pub active_connections: Option<i64>,
61 pub created_at: Option<i64>,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
67pub struct HourMetricsAggregate {
68 pub id: Option<i64>,
70 pub timestamp: i64,
72 pub protocol: String,
74 pub method: Option<String>,
76 pub endpoint: Option<String>,
78 pub status_code: Option<i32>,
80 pub workspace_id: Option<String>,
82 pub environment: Option<String>,
84 pub request_count: i64,
86 pub error_count: i64,
88 pub latency_sum: f64,
90 pub latency_min: Option<f64>,
92 pub latency_max: Option<f64>,
94 pub latency_p50: Option<f64>,
96 pub latency_p95: Option<f64>,
98 pub latency_p99: Option<f64>,
100 pub bytes_sent: i64,
102 pub bytes_received: i64,
104 pub active_connections_avg: Option<f64>,
106 pub active_connections_max: Option<i64>,
108 pub created_at: Option<i64>,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
114pub struct DayMetricsAggregate {
115 pub id: Option<i64>,
117 pub date: String,
119 pub timestamp: i64,
121 pub protocol: String,
123 pub method: Option<String>,
125 pub endpoint: Option<String>,
127 pub status_code: Option<i32>,
129 pub workspace_id: Option<String>,
131 pub environment: Option<String>,
133 pub request_count: i64,
135 pub error_count: i64,
137 pub latency_sum: f64,
139 pub latency_min: Option<f64>,
141 pub latency_max: Option<f64>,
143 pub latency_p50: Option<f64>,
145 pub latency_p95: Option<f64>,
147 pub latency_p99: Option<f64>,
149 pub bytes_sent: i64,
151 pub bytes_received: i64,
153 pub active_connections_avg: Option<f64>,
155 pub active_connections_max: Option<i64>,
157 pub unique_clients: Option<i64>,
159 pub peak_hour: Option<i32>,
161 pub created_at: Option<i64>,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
167pub struct EndpointStats {
168 pub id: Option<i64>,
170 pub endpoint: String,
172 pub protocol: String,
174 pub method: Option<String>,
176 pub workspace_id: Option<String>,
178 pub environment: Option<String>,
180 pub total_requests: i64,
182 pub total_errors: i64,
184 pub avg_latency_ms: Option<f64>,
186 pub min_latency_ms: Option<f64>,
188 pub max_latency_ms: Option<f64>,
190 pub p95_latency_ms: Option<f64>,
192 pub status_codes: Option<String>,
194 pub total_bytes_sent: i64,
196 pub total_bytes_received: i64,
198 pub first_seen: i64,
200 pub last_seen: i64,
202 pub updated_at: Option<i64>,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct StatusCodeBreakdown {
209 pub status_codes: HashMap<u16, i64>,
211}
212
213impl EndpointStats {
214 pub fn get_status_code_breakdown(&self) -> Result<StatusCodeBreakdown, serde_json::Error> {
220 if let Some(ref json) = self.status_codes {
221 let map: HashMap<String, i64> = serde_json::from_str(json)?;
222 let status_codes = map
223 .into_iter()
224 .filter_map(|(k, v)| k.parse::<u16>().ok().map(|code| (code, v)))
225 .collect();
226 Ok(StatusCodeBreakdown { status_codes })
227 } else {
228 Ok(StatusCodeBreakdown {
229 status_codes: HashMap::new(),
230 })
231 }
232 }
233
234 pub fn set_status_code_breakdown(
240 &mut self,
241 breakdown: &StatusCodeBreakdown,
242 ) -> Result<(), serde_json::Error> {
243 let map: HashMap<String, i64> =
244 breakdown.status_codes.iter().map(|(k, v)| (k.to_string(), *v)).collect();
245 self.status_codes = Some(serde_json::to_string(&map)?);
246 Ok(())
247 }
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
252pub struct ErrorEvent {
253 pub id: Option<i64>,
255 pub timestamp: i64,
257 pub protocol: String,
259 pub method: Option<String>,
261 pub endpoint: Option<String>,
263 pub status_code: Option<i32>,
265 pub error_type: Option<String>,
267 pub error_message: Option<String>,
269 pub error_category: Option<String>,
271 pub request_id: Option<String>,
273 pub trace_id: Option<String>,
275 pub span_id: Option<String>,
277 pub client_ip: Option<String>,
279 pub user_agent: Option<String>,
281 pub workspace_id: Option<String>,
283 pub environment: Option<String>,
285 pub metadata: Option<String>,
287 pub created_at: Option<i64>,
289}
290
291#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
293#[serde(rename_all = "snake_case")]
294pub enum ErrorCategory {
295 ClientError,
297 ServerError,
299 NetworkError,
301 TimeoutError,
303 Other,
305}
306
307impl ErrorCategory {
308 #[must_use]
310 pub const fn from_status_code(status_code: u16) -> Self {
311 match status_code {
312 400..=499 => Self::ClientError,
313 500..=599 => Self::ServerError,
314 _ => Self::Other,
315 }
316 }
317
318 #[must_use]
320 pub const fn as_str(&self) -> &'static str {
321 match self {
322 Self::ClientError => "client_error",
323 Self::ServerError => "server_error",
324 Self::NetworkError => "network_error",
325 Self::TimeoutError => "timeout_error",
326 Self::Other => "other",
327 }
328 }
329}
330
331#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
333pub struct ClientAnalytics {
334 pub id: Option<i64>,
336 pub timestamp: i64,
338 pub client_ip: String,
340 pub user_agent: Option<String>,
342 pub user_agent_family: Option<String>,
344 pub user_agent_version: Option<String>,
346 pub protocol: String,
348 pub workspace_id: Option<String>,
350 pub environment: Option<String>,
352 pub request_count: i64,
354 pub error_count: i64,
356 pub avg_latency_ms: Option<f64>,
358 pub bytes_sent: i64,
360 pub bytes_received: i64,
362 pub top_endpoints: Option<String>,
364 pub created_at: Option<i64>,
366}
367
368#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
370pub struct TrafficPattern {
371 pub id: Option<i64>,
373 pub date: String,
375 pub hour: i32,
377 pub day_of_week: i32,
379 pub protocol: String,
381 pub workspace_id: Option<String>,
383 pub environment: Option<String>,
385 pub request_count: i64,
387 pub error_count: i64,
389 pub avg_latency_ms: Option<f64>,
391 pub unique_clients: Option<i64>,
393 pub created_at: Option<i64>,
395}
396
397#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
399pub struct AnalyticsSnapshot {
400 pub id: Option<i64>,
402 pub timestamp: i64,
404 pub snapshot_type: String,
406 pub total_requests: i64,
408 pub total_errors: i64,
410 pub avg_latency_ms: Option<f64>,
412 pub active_connections: Option<i64>,
414 pub protocol_stats: Option<String>,
416 pub top_endpoints: Option<String>,
418 pub memory_usage_bytes: Option<i64>,
420 pub cpu_usage_percent: Option<f64>,
422 pub thread_count: Option<i32>,
424 pub uptime_seconds: Option<i64>,
426 pub workspace_id: Option<String>,
428 pub environment: Option<String>,
430 pub created_at: Option<i64>,
432}
433
434#[derive(Debug, Clone, Default, Serialize, Deserialize)]
436pub struct AnalyticsFilter {
437 pub start_time: Option<i64>,
439 pub end_time: Option<i64>,
441 pub protocol: Option<String>,
443 pub endpoint: Option<String>,
445 pub method: Option<String>,
447 pub status_code: Option<i32>,
449 pub workspace_id: Option<String>,
451 pub environment: Option<String>,
453 pub limit: Option<i64>,
455}
456
457#[derive(Debug, Clone, Serialize, Deserialize)]
459pub struct OverviewMetrics {
460 pub total_requests: i64,
462 pub total_errors: i64,
464 pub error_rate: f64,
466 pub avg_latency_ms: f64,
468 pub p95_latency_ms: f64,
470 pub p99_latency_ms: f64,
472 pub active_connections: i64,
474 pub total_bytes_sent: i64,
476 pub total_bytes_received: i64,
478 pub requests_per_second: f64,
480 pub top_protocols: Vec<ProtocolStat>,
482 pub top_endpoints: Vec<EndpointStat>,
484}
485
486#[derive(Debug, Clone, Serialize, Deserialize)]
488pub struct ProtocolStat {
489 pub protocol: String,
491 pub request_count: i64,
493 pub error_count: i64,
495 pub avg_latency_ms: f64,
497}
498
499#[derive(Debug, Clone, Serialize, Deserialize)]
501pub struct EndpointStat {
502 pub endpoint: String,
504 pub protocol: String,
506 pub method: Option<String>,
508 pub request_count: i64,
510 pub error_count: i64,
512 pub error_rate: f64,
514 pub avg_latency_ms: f64,
516 pub p95_latency_ms: f64,
518}
519
520#[derive(Debug, Clone, Serialize, Deserialize)]
522pub struct TimeSeriesPoint {
523 pub timestamp: i64,
525 pub value: f64,
527}
528
529#[derive(Debug, Clone, Serialize, Deserialize)]
531pub struct TimeSeries {
532 pub label: String,
534 pub data: Vec<TimeSeriesPoint>,
536}
537
538#[derive(Debug, Clone, Serialize, Deserialize)]
540pub struct LatencyTrend {
541 pub timestamp: i64,
543 pub p50: f64,
545 pub p95: f64,
547 pub p99: f64,
549 pub avg: f64,
551 pub min: f64,
553 pub max: f64,
555}
556
557#[derive(Debug, Clone, Serialize, Deserialize)]
559pub struct ErrorSummary {
560 pub error_type: String,
562 pub error_category: String,
564 pub count: i64,
566 pub endpoints: Vec<String>,
568 pub last_occurrence: DateTime<Utc>,
570}
571
572#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
574#[serde(rename_all = "lowercase")]
575pub enum ExportFormat {
576 Csv,
578 Json,
580}
581
582#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
588pub struct ScenarioUsageMetrics {
589 pub id: Option<i64>,
591 pub scenario_id: String,
593 pub workspace_id: Option<String>,
595 pub org_id: Option<String>,
597 pub usage_count: i64,
599 pub last_used_at: Option<i64>,
601 pub usage_pattern: Option<String>,
603 pub created_at: Option<i64>,
605 pub updated_at: Option<i64>,
607}
608
609#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
611pub struct PersonaCIHit {
612 pub id: Option<i64>,
614 pub persona_id: String,
616 pub workspace_id: Option<String>,
618 pub org_id: Option<String>,
620 pub ci_run_id: Option<String>,
622 pub hit_count: i64,
624 pub hit_at: i64,
626 pub created_at: Option<i64>,
628}
629
630#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
632pub struct EndpointCoverage {
633 pub id: Option<i64>,
635 pub endpoint: String,
637 pub method: Option<String>,
639 pub protocol: String,
641 pub workspace_id: Option<String>,
643 pub org_id: Option<String>,
645 pub test_count: i64,
647 pub last_tested_at: Option<i64>,
649 pub coverage_percentage: Option<f64>,
651 pub created_at: Option<i64>,
653 pub updated_at: Option<i64>,
655}
656
657#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
659pub struct RealityLevelStaleness {
660 pub id: Option<i64>,
662 pub workspace_id: String,
664 pub org_id: Option<String>,
666 pub endpoint: Option<String>,
668 pub method: Option<String>,
670 pub protocol: Option<String>,
672 pub current_reality_level: Option<String>,
674 pub last_updated_at: Option<i64>,
676 pub staleness_days: Option<i32>,
678 pub created_at: Option<i64>,
680 pub updated_at: Option<i64>,
682}
683
684#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
686pub struct DriftPercentageMetrics {
687 pub id: Option<i64>,
689 pub workspace_id: String,
691 pub org_id: Option<String>,
693 pub total_mocks: i64,
695 pub drifting_mocks: i64,
697 pub drift_percentage: f64,
699 pub measured_at: i64,
701 pub created_at: Option<i64>,
703}
704
705#[cfg(test)]
706mod tests {
707 use super::*;
708
709 #[test]
712 fn test_granularity_serialize() {
713 let minute = Granularity::Minute;
714 let hour = Granularity::Hour;
715 let day = Granularity::Day;
716
717 assert_eq!(serde_json::to_string(&minute).unwrap(), "\"minute\"");
718 assert_eq!(serde_json::to_string(&hour).unwrap(), "\"hour\"");
719 assert_eq!(serde_json::to_string(&day).unwrap(), "\"day\"");
720 }
721
722 #[test]
723 fn test_granularity_deserialize() {
724 let minute: Granularity = serde_json::from_str("\"minute\"").unwrap();
725 let hour: Granularity = serde_json::from_str("\"hour\"").unwrap();
726 let day: Granularity = serde_json::from_str("\"day\"").unwrap();
727
728 assert_eq!(minute, Granularity::Minute);
729 assert_eq!(hour, Granularity::Hour);
730 assert_eq!(day, Granularity::Day);
731 }
732
733 #[test]
734 fn test_granularity_copy() {
735 let g = Granularity::Hour;
736 let copied = g;
737 assert_eq!(g, copied);
738 }
739
740 #[test]
743 fn test_error_category_from_status_code_client() {
744 assert_eq!(ErrorCategory::from_status_code(400), ErrorCategory::ClientError);
745 assert_eq!(ErrorCategory::from_status_code(401), ErrorCategory::ClientError);
746 assert_eq!(ErrorCategory::from_status_code(403), ErrorCategory::ClientError);
747 assert_eq!(ErrorCategory::from_status_code(404), ErrorCategory::ClientError);
748 assert_eq!(ErrorCategory::from_status_code(499), ErrorCategory::ClientError);
749 }
750
751 #[test]
752 fn test_error_category_from_status_code_server() {
753 assert_eq!(ErrorCategory::from_status_code(500), ErrorCategory::ServerError);
754 assert_eq!(ErrorCategory::from_status_code(502), ErrorCategory::ServerError);
755 assert_eq!(ErrorCategory::from_status_code(503), ErrorCategory::ServerError);
756 assert_eq!(ErrorCategory::from_status_code(504), ErrorCategory::ServerError);
757 assert_eq!(ErrorCategory::from_status_code(599), ErrorCategory::ServerError);
758 }
759
760 #[test]
761 fn test_error_category_from_status_code_other() {
762 assert_eq!(ErrorCategory::from_status_code(200), ErrorCategory::Other);
763 assert_eq!(ErrorCategory::from_status_code(301), ErrorCategory::Other);
764 assert_eq!(ErrorCategory::from_status_code(0), ErrorCategory::Other);
765 }
766
767 #[test]
768 fn test_error_category_as_str() {
769 assert_eq!(ErrorCategory::ClientError.as_str(), "client_error");
770 assert_eq!(ErrorCategory::ServerError.as_str(), "server_error");
771 assert_eq!(ErrorCategory::NetworkError.as_str(), "network_error");
772 assert_eq!(ErrorCategory::TimeoutError.as_str(), "timeout_error");
773 assert_eq!(ErrorCategory::Other.as_str(), "other");
774 }
775
776 #[test]
777 fn test_error_category_serialize() {
778 assert_eq!(serde_json::to_string(&ErrorCategory::ClientError).unwrap(), "\"client_error\"");
779 assert_eq!(serde_json::to_string(&ErrorCategory::ServerError).unwrap(), "\"server_error\"");
780 }
781
782 #[test]
785 fn test_export_format_serialize() {
786 assert_eq!(serde_json::to_string(&ExportFormat::Csv).unwrap(), "\"csv\"");
787 assert_eq!(serde_json::to_string(&ExportFormat::Json).unwrap(), "\"json\"");
788 }
789
790 #[test]
791 fn test_export_format_deserialize() {
792 let csv: ExportFormat = serde_json::from_str("\"csv\"").unwrap();
793 let json_fmt: ExportFormat = serde_json::from_str("\"json\"").unwrap();
794
795 assert_eq!(csv, ExportFormat::Csv);
796 assert_eq!(json_fmt, ExportFormat::Json);
797 }
798
799 #[test]
802 fn test_endpoint_stats_get_status_code_breakdown() {
803 let stats = EndpointStats {
804 id: Some(1),
805 endpoint: "/api/users".to_string(),
806 protocol: "http".to_string(),
807 method: Some("GET".to_string()),
808 workspace_id: None,
809 environment: None,
810 total_requests: 100,
811 total_errors: 5,
812 avg_latency_ms: Some(50.0),
813 min_latency_ms: Some(10.0),
814 max_latency_ms: Some(200.0),
815 p95_latency_ms: Some(150.0),
816 status_codes: Some(r#"{"200": 90, "404": 5, "500": 5}"#.to_string()),
817 total_bytes_sent: 10_000,
818 total_bytes_received: 5_000,
819 first_seen: 1000,
820 last_seen: 2000,
821 updated_at: None,
822 };
823
824 let breakdown = stats.get_status_code_breakdown().unwrap();
825 assert_eq!(breakdown.status_codes.get(&200), Some(&90));
826 assert_eq!(breakdown.status_codes.get(&404), Some(&5));
827 assert_eq!(breakdown.status_codes.get(&500), Some(&5));
828 }
829
830 #[test]
831 fn test_endpoint_stats_get_status_code_breakdown_none() {
832 let stats = EndpointStats {
833 id: None,
834 endpoint: "/api/test".to_string(),
835 protocol: "http".to_string(),
836 method: None,
837 workspace_id: None,
838 environment: None,
839 total_requests: 0,
840 total_errors: 0,
841 avg_latency_ms: None,
842 min_latency_ms: None,
843 max_latency_ms: None,
844 p95_latency_ms: None,
845 status_codes: None,
846 total_bytes_sent: 0,
847 total_bytes_received: 0,
848 first_seen: 0,
849 last_seen: 0,
850 updated_at: None,
851 };
852
853 let breakdown = stats.get_status_code_breakdown().unwrap();
854 assert!(breakdown.status_codes.is_empty());
855 }
856
857 #[test]
858 fn test_endpoint_stats_set_status_code_breakdown() {
859 let mut stats = EndpointStats {
860 id: None,
861 endpoint: "/api/test".to_string(),
862 protocol: "http".to_string(),
863 method: None,
864 workspace_id: None,
865 environment: None,
866 total_requests: 0,
867 total_errors: 0,
868 avg_latency_ms: None,
869 min_latency_ms: None,
870 max_latency_ms: None,
871 p95_latency_ms: None,
872 status_codes: None,
873 total_bytes_sent: 0,
874 total_bytes_received: 0,
875 first_seen: 0,
876 last_seen: 0,
877 updated_at: None,
878 };
879
880 let breakdown = StatusCodeBreakdown {
881 status_codes: HashMap::from([(200, 100), (500, 10)]),
882 };
883
884 stats.set_status_code_breakdown(&breakdown).unwrap();
885 assert!(stats.status_codes.is_some());
886
887 let restored = stats.get_status_code_breakdown().unwrap();
889 assert_eq!(restored.status_codes.get(&200), Some(&100));
890 assert_eq!(restored.status_codes.get(&500), Some(&10));
891 }
892
893 #[test]
896 fn test_analytics_filter_default() {
897 let filter = AnalyticsFilter::default();
898 assert!(filter.start_time.is_none());
899 assert!(filter.end_time.is_none());
900 assert!(filter.protocol.is_none());
901 assert!(filter.endpoint.is_none());
902 assert!(filter.limit.is_none());
903 }
904
905 #[test]
906 fn test_analytics_filter_serialize() {
907 let filter = AnalyticsFilter {
908 start_time: Some(1000),
909 end_time: Some(2000),
910 protocol: Some("http".to_string()),
911 endpoint: Some("/api/users".to_string()),
912 method: Some("GET".to_string()),
913 status_code: Some(200),
914 workspace_id: None,
915 environment: None,
916 limit: Some(100),
917 };
918
919 let json = serde_json::to_string(&filter).unwrap();
920 assert!(json.contains("1000"));
921 assert!(json.contains("http"));
922 assert!(json.contains("/api/users"));
923 }
924
925 #[test]
926 fn test_analytics_filter_clone() {
927 let filter = AnalyticsFilter {
928 start_time: Some(1000),
929 protocol: Some("grpc".to_string()),
930 ..Default::default()
931 };
932
933 let cloned = filter.clone();
934 assert_eq!(filter.start_time, cloned.start_time);
935 assert_eq!(filter.protocol, cloned.protocol);
936 }
937
938 #[test]
941 fn test_overview_metrics_serialize() {
942 let metrics = OverviewMetrics {
943 total_requests: 1000,
944 total_errors: 50,
945 error_rate: 0.05,
946 avg_latency_ms: 100.0,
947 p95_latency_ms: 250.0,
948 p99_latency_ms: 500.0,
949 active_connections: 10,
950 total_bytes_sent: 100_000,
951 total_bytes_received: 50_000,
952 requests_per_second: 10.5,
953 top_protocols: vec![],
954 top_endpoints: vec![],
955 };
956
957 let json = serde_json::to_string(&metrics).unwrap();
958 assert!(json.contains("1000"));
959 assert!(json.contains("0.05"));
960 }
961
962 #[test]
965 fn test_protocol_stat_serialize() {
966 let stat = ProtocolStat {
967 protocol: "http".to_string(),
968 request_count: 1000,
969 error_count: 10,
970 avg_latency_ms: 50.0,
971 };
972
973 let json = serde_json::to_string(&stat).unwrap();
974 assert!(json.contains("http"));
975 assert!(json.contains("1000"));
976 }
977
978 #[test]
981 fn test_endpoint_stat_serialize() {
982 let stat = EndpointStat {
983 endpoint: "/api/users".to_string(),
984 protocol: "http".to_string(),
985 method: Some("GET".to_string()),
986 request_count: 500,
987 error_count: 5,
988 error_rate: 0.01,
989 avg_latency_ms: 75.0,
990 p95_latency_ms: 150.0,
991 };
992
993 let json = serde_json::to_string(&stat).unwrap();
994 assert!(json.contains("/api/users"));
995 assert!(json.contains("GET"));
996 }
997
998 #[test]
1001 fn test_time_series_point_serialize() {
1002 let point = TimeSeriesPoint {
1003 timestamp: 1_234_567_890,
1004 value: 42.5,
1005 };
1006
1007 let json = serde_json::to_string(&point).unwrap();
1008 assert!(json.contains("1234567890"));
1009 assert!(json.contains("42.5"));
1010 }
1011
1012 #[test]
1015 fn test_time_series_serialize() {
1016 let series = TimeSeries {
1017 label: "requests".to_string(),
1018 data: vec![
1019 TimeSeriesPoint {
1020 timestamp: 1000,
1021 value: 10.0,
1022 },
1023 TimeSeriesPoint {
1024 timestamp: 2000,
1025 value: 20.0,
1026 },
1027 ],
1028 };
1029
1030 let json = serde_json::to_string(&series).unwrap();
1031 assert!(json.contains("requests"));
1032 assert!(json.contains("1000"));
1033 }
1034
1035 #[test]
1038 fn test_latency_trend_serialize() {
1039 let trend = LatencyTrend {
1040 timestamp: 1000,
1041 p50: 50.0,
1042 p95: 150.0,
1043 p99: 250.0,
1044 avg: 75.0,
1045 min: 10.0,
1046 max: 500.0,
1047 };
1048
1049 let json = serde_json::to_string(&trend).unwrap();
1050 assert!(json.contains("p50"));
1051 assert!(json.contains("p95"));
1052 assert!(json.contains("p99"));
1053 }
1054
1055 #[test]
1058 fn test_metrics_aggregate_clone() {
1059 let agg = MetricsAggregate {
1060 id: Some(1),
1061 timestamp: 1000,
1062 protocol: "http".to_string(),
1063 method: Some("GET".to_string()),
1064 endpoint: Some("/api/users".to_string()),
1065 status_code: Some(200),
1066 workspace_id: None,
1067 environment: None,
1068 request_count: 100,
1069 error_count: 5,
1070 latency_sum: 5000.0,
1071 latency_min: Some(10.0),
1072 latency_max: Some(200.0),
1073 latency_p50: Some(50.0),
1074 latency_p95: Some(150.0),
1075 latency_p99: Some(180.0),
1076 bytes_sent: 10000,
1077 bytes_received: 5000,
1078 active_connections: Some(10),
1079 created_at: None,
1080 };
1081
1082 let cloned = agg.clone();
1083 assert_eq!(agg.timestamp, cloned.timestamp);
1084 assert_eq!(agg.request_count, cloned.request_count);
1085 }
1086
1087 #[test]
1090 fn test_error_event_serialize() {
1091 let event = ErrorEvent {
1092 id: Some(1),
1093 timestamp: 1000,
1094 protocol: "http".to_string(),
1095 method: Some("POST".to_string()),
1096 endpoint: Some("/api/orders".to_string()),
1097 status_code: Some(500),
1098 error_type: Some("InternalServerError".to_string()),
1099 error_message: Some("Database connection failed".to_string()),
1100 error_category: Some("server_error".to_string()),
1101 request_id: Some("req-123".to_string()),
1102 trace_id: None,
1103 span_id: None,
1104 client_ip: Some("192.168.1.1".to_string()),
1105 user_agent: Some("TestClient/1.0".to_string()),
1106 workspace_id: None,
1107 environment: None,
1108 metadata: None,
1109 created_at: None,
1110 };
1111
1112 let json = serde_json::to_string(&event).unwrap();
1113 assert!(json.contains("InternalServerError"));
1114 assert!(json.contains("Database connection failed"));
1115 }
1116
1117 #[test]
1120 fn test_traffic_pattern_serialize() {
1121 let pattern = TrafficPattern {
1122 id: Some(1),
1123 date: "2024-01-15".to_string(),
1124 hour: 14,
1125 day_of_week: 1,
1126 protocol: "http".to_string(),
1127 workspace_id: None,
1128 environment: None,
1129 request_count: 500,
1130 error_count: 10,
1131 avg_latency_ms: Some(50.0),
1132 unique_clients: Some(25),
1133 created_at: None,
1134 };
1135
1136 let json = serde_json::to_string(&pattern).unwrap();
1137 assert!(json.contains("2024-01-15"));
1138 assert!(json.contains("14"));
1139 }
1140
1141 #[test]
1144 fn test_drift_percentage_metrics_serialize() {
1145 let metrics = DriftPercentageMetrics {
1146 id: Some(1),
1147 workspace_id: "ws-123".to_string(),
1148 org_id: Some("org-456".to_string()),
1149 total_mocks: 100,
1150 drifting_mocks: 15,
1151 drift_percentage: 15.0,
1152 measured_at: 1000,
1153 created_at: None,
1154 };
1155
1156 let json = serde_json::to_string(&metrics).unwrap();
1157 assert!(json.contains("ws-123"));
1158 assert!(json.contains("15.0"));
1159 }
1160}