1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct ServerStatus {
9 pub server_type: String,
11 pub address: Option<String>,
13 pub running: bool,
15 pub start_time: Option<chrono::DateTime<chrono::Utc>>,
17 pub uptime_seconds: Option<u64>,
19 pub active_connections: u64,
21 pub total_requests: u64,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct RouteInfo {
28 pub method: Option<String>,
30 pub path: String,
32 pub priority: i32,
34 pub has_fixtures: bool,
36 pub latency_ms: Option<u64>,
38 pub request_count: u64,
40 pub last_request: Option<chrono::DateTime<chrono::Utc>>,
42 pub error_count: u64,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct RequestLog {
49 pub id: String,
51 pub timestamp: chrono::DateTime<chrono::Utc>,
53 pub method: String,
55 pub path: String,
57 pub status_code: u16,
59 pub response_time_ms: u64,
61 pub client_ip: Option<String>,
63 pub user_agent: Option<String>,
65 pub headers: HashMap<String, String>,
67 pub response_size_bytes: u64,
69 pub error_message: Option<String>,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct SystemInfo {
76 pub version: String,
78 pub uptime_seconds: u64,
80 pub memory_usage_mb: u64,
82 pub cpu_usage_percent: f64,
84 pub active_threads: usize,
86 pub total_routes: usize,
88 pub total_fixtures: usize,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct LatencyProfile {
95 pub name: String,
97 pub base_ms: u64,
99 pub jitter_ms: u64,
101 pub tag_overrides: HashMap<String, u64>,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct FaultConfig {
108 pub enabled: bool,
110 pub failure_rate: f64,
112 pub status_codes: Vec<u16>,
114 pub active_failures: u64,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct ProxyConfig {
121 pub enabled: bool,
123 pub upstream_url: Option<String>,
125 pub timeout_seconds: u64,
127 pub requests_proxied: u64,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct BandwidthConfig {
134 pub enabled: bool,
136 pub max_bytes_per_sec: u64,
138 pub burst_capacity_bytes: u64,
140 pub tag_overrides: HashMap<String, u64>,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct BurstLossConfig {
147 pub enabled: bool,
149 pub burst_probability: f64,
151 pub burst_duration_ms: u64,
153 pub loss_rate_during_burst: f64,
155 pub recovery_time_ms: u64,
157 pub tag_overrides: HashMap<String, BurstLossOverride>,
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct BurstLossOverride {
164 pub burst_probability: f64,
165 pub burst_duration_ms: u64,
166 pub loss_rate_during_burst: f64,
167 pub recovery_time_ms: u64,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct TrafficShapingConfig {
173 pub enabled: bool,
175 pub bandwidth: BandwidthConfig,
177 pub burst_loss: BurstLossConfig,
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct SimpleMetricsData {
184 pub total_requests: u64,
186 pub active_requests: u64,
188 pub average_response_time: f64,
190 pub error_rate: f64,
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct DashboardSystemInfo {
197 pub os: String,
199 pub arch: String,
201 pub uptime: u64,
203 pub memory_usage: u64,
205}
206
207#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct DashboardData {
210 pub server_info: ServerInfo,
212 pub system_info: DashboardSystemInfo,
214 pub metrics: SimpleMetricsData,
216 pub servers: Vec<ServerStatus>,
218 pub recent_logs: Vec<RequestLog>,
220 pub system: SystemInfo,
222}
223
224#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct ApiResponse<T> {
227 pub success: bool,
229 pub data: Option<T>,
231 pub error: Option<String>,
233 pub timestamp: chrono::DateTime<chrono::Utc>,
235}
236
237impl<T> ApiResponse<T> {
238 pub fn success(data: T) -> Self {
240 Self {
241 success: true,
242 data: Some(data),
243 error: None,
244 timestamp: chrono::Utc::now(),
245 }
246 }
247
248 pub fn error(message: String) -> Self {
250 Self {
251 success: false,
252 data: None,
253 error: Some(message),
254 timestamp: chrono::Utc::now(),
255 }
256 }
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct ConfigUpdate {
262 pub config_type: String,
264 pub data: serde_json::Value,
266}
267
268#[derive(Debug, Clone, Serialize, Deserialize)]
270pub struct RouteUpdate {
271 pub path: String,
273 pub method: Option<String>,
275 pub operation: String,
277 pub data: Option<serde_json::Value>,
279}
280
281#[derive(Debug, Clone, Serialize, Deserialize)]
283pub struct LogFilter {
284 pub method: Option<String>,
286 pub path_pattern: Option<String>,
288 pub status_code: Option<u16>,
290 pub hours_ago: Option<u64>,
292 pub limit: Option<usize>,
294}
295
296impl Default for LogFilter {
297 fn default() -> Self {
298 Self {
299 method: None,
300 path_pattern: None,
301 status_code: None,
302 hours_ago: Some(24),
303 limit: Some(100),
304 }
305 }
306}
307
308#[derive(Debug, Clone, Serialize, Deserialize)]
310pub struct MetricsData {
311 pub requests_by_endpoint: HashMap<String, u64>,
313 pub response_time_percentiles: HashMap<String, u64>,
315 #[serde(skip_serializing_if = "Option::is_none")]
317 pub endpoint_percentiles: Option<HashMap<String, HashMap<String, u64>>>,
318 #[serde(skip_serializing_if = "Option::is_none")]
320 pub latency_over_time: Option<Vec<(chrono::DateTime<chrono::Utc>, u64)>>,
321 pub error_rate_by_endpoint: HashMap<String, f64>,
323 pub memory_usage_over_time: Vec<(chrono::DateTime<chrono::Utc>, u64)>,
325 pub cpu_usage_over_time: Vec<(chrono::DateTime<chrono::Utc>, f64)>,
327}
328
329#[derive(Debug, Clone, Serialize, Deserialize)]
331pub struct ValidationSettings {
332 pub mode: String,
334 pub aggregate_errors: bool,
336 pub validate_responses: bool,
338 pub overrides: HashMap<String, String>,
340}
341
342#[derive(Debug, Clone, Serialize, Deserialize)]
344pub struct ValidationUpdate {
345 pub mode: String,
347 pub aggregate_errors: bool,
349 pub validate_responses: bool,
351 pub overrides: Option<HashMap<String, String>>,
353}
354
355#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct LogEntry {
358 pub timestamp: chrono::DateTime<chrono::Utc>,
360 pub status: u16,
362 pub method: String,
364 pub url: String,
366 pub response_time: u64,
368 pub size: u64,
370}
371
372#[derive(Debug, Clone, Serialize, Deserialize)]
374pub struct HealthCheck {
375 pub status: String,
377 pub services: HashMap<String, String>,
379 pub last_check: chrono::DateTime<chrono::Utc>,
381 pub issues: Vec<String>,
383}
384
385impl HealthCheck {
386 pub fn healthy() -> Self {
388 Self {
389 status: "healthy".to_string(),
390 services: HashMap::new(),
391 last_check: chrono::Utc::now(),
392 issues: Vec::new(),
393 }
394 }
395
396 pub fn unhealthy(issues: Vec<String>) -> Self {
398 Self {
399 status: "unhealthy".to_string(),
400 services: HashMap::new(),
401 last_check: chrono::Utc::now(),
402 issues,
403 }
404 }
405
406 pub fn with_service(mut self, name: String, status: String) -> Self {
408 self.services.insert(name, status);
409 self
410 }
411}
412
413#[derive(Debug, Clone, Serialize, Deserialize)]
415pub struct WorkspaceSummary {
416 pub id: String,
418 pub name: String,
420 pub description: Option<String>,
422 pub active: bool,
424 pub folder_count: usize,
426 pub request_count: usize,
428 pub created_at: chrono::DateTime<chrono::Utc>,
430 pub updated_at: chrono::DateTime<chrono::Utc>,
432}
433
434#[derive(Debug, Clone, Serialize, Deserialize)]
436pub struct FolderSummary {
437 pub id: String,
439 pub name: String,
441 pub description: Option<String>,
443 pub parent_id: Option<String>,
445 pub subfolder_count: usize,
447 pub request_count: usize,
449 pub created_at: chrono::DateTime<chrono::Utc>,
451 pub updated_at: chrono::DateTime<chrono::Utc>,
453}
454
455#[derive(Debug, Clone, Serialize, Deserialize)]
457pub struct RequestSummary {
458 pub id: String,
460 pub name: String,
462 pub description: Option<String>,
464 pub method: String,
466 pub path: String,
468 pub status_code: u16,
470 pub created_at: chrono::DateTime<chrono::Utc>,
472 pub updated_at: chrono::DateTime<chrono::Utc>,
474}
475
476#[derive(Debug, Clone, Serialize, Deserialize)]
478pub struct WorkspaceDetail {
479 pub summary: WorkspaceSummary,
481 pub folders: Vec<FolderSummary>,
483 pub requests: Vec<RequestSummary>,
485}
486
487#[derive(Debug, Clone, Serialize, Deserialize)]
489pub struct FolderDetail {
490 pub summary: FolderSummary,
492 pub subfolders: Vec<FolderSummary>,
494 pub requests: Vec<RequestSummary>,
496}
497
498#[derive(Debug, Clone, Serialize, Deserialize)]
500pub struct CreateWorkspaceRequest {
501 pub name: String,
503 pub description: Option<String>,
505}
506
507#[derive(Debug, Clone, Serialize, Deserialize)]
509pub struct CreateFolderRequest {
510 pub name: String,
512 pub description: Option<String>,
514 pub parent_id: Option<String>,
516}
517
518#[derive(Debug, Clone, Serialize, Deserialize)]
520pub struct CreateRequestRequest {
521 pub name: String,
523 pub description: Option<String>,
525 pub method: String,
527 pub path: String,
529 pub status_code: Option<u16>,
531 pub response_body: Option<String>,
533 pub folder_id: Option<String>,
535}
536
537#[derive(Debug, Clone, Serialize, Deserialize)]
539pub struct ImportToWorkspaceRequest {
540 pub format: String,
542 pub data: String,
544 pub folder_id: Option<String>,
546 pub create_folders: Option<bool>,
548 pub selected_routes: Option<Vec<usize>>,
550}
551
552#[derive(Debug, Clone, Serialize, Deserialize)]
554pub struct ExportWorkspacesRequest {
555 pub workspace_ids: Vec<String>,
557}
558
559#[derive(Debug, Clone, Serialize, Deserialize)]
561pub struct WorkspaceExportData {
562 pub workspaces: Vec<mockforge_core::Workspace>,
564 pub version: String,
566 pub exported_at: chrono::DateTime<chrono::Utc>,
568 pub exporter_version: String,
570}
571
572#[derive(Debug, Clone, Serialize, Deserialize)]
574pub struct EnvironmentColor {
575 pub hex: String,
577 pub name: Option<String>,
579}
580
581#[derive(Debug, Clone, Serialize, Deserialize)]
583pub struct EnvironmentSummary {
584 pub id: String,
586 pub name: String,
588 pub description: Option<String>,
590 pub color: Option<EnvironmentColor>,
592 pub variable_count: usize,
594 pub active: bool,
596 pub is_global: bool,
598 pub created_at: chrono::DateTime<chrono::Utc>,
600 pub updated_at: chrono::DateTime<chrono::Utc>,
602}
603
604#[derive(Debug, Clone, Serialize, Deserialize)]
606pub struct EnvironmentVariable {
607 pub name: String,
609 pub value: String,
611 pub from_global: bool,
613}
614
615#[derive(Debug, Clone, Serialize, Deserialize)]
617pub struct CreateEnvironmentRequest {
618 pub name: String,
620 pub description: Option<String>,
622}
623
624#[derive(Debug, Clone, Serialize, Deserialize)]
626pub struct UpdateEnvironmentRequest {
627 pub name: Option<String>,
629 pub description: Option<String>,
631 pub color: Option<EnvironmentColor>,
633}
634
635#[derive(Debug, Clone, Serialize, Deserialize)]
637pub struct SetVariableRequest {
638 pub name: String,
640 pub value: String,
642}
643
644#[derive(Debug, Clone, Serialize, Deserialize)]
646pub struct SyncConfig {
647 pub enabled: bool,
649 pub target_directory: Option<String>,
651 pub directory_structure: SyncDirectoryStructure,
653 pub sync_direction: SyncDirection,
655 pub include_metadata: bool,
657 pub realtime_monitoring: bool,
659 pub filename_pattern: String,
661 pub exclude_pattern: Option<String>,
663 pub force_overwrite: bool,
665}
666
667#[derive(Debug, Clone, Serialize, Deserialize)]
669pub enum SyncDirectoryStructure {
670 Flat,
672 Nested,
674 Grouped,
676}
677
678#[derive(Debug, Clone, Serialize, Deserialize)]
680pub enum SyncDirection {
681 Manual,
683 WorkspaceToDirectory,
685 Bidirectional,
687}
688
689impl From<mockforge_core::workspace::SyncDirection> for SyncDirection {
690 fn from(core: mockforge_core::workspace::SyncDirection) -> Self {
691 match core {
692 mockforge_core::workspace::SyncDirection::Manual => Self::Manual,
693 mockforge_core::workspace::SyncDirection::WorkspaceToDirectory => {
694 Self::WorkspaceToDirectory
695 }
696 mockforge_core::workspace::SyncDirection::Bidirectional => Self::Bidirectional,
697 }
698 }
699}
700
701#[derive(Debug, Clone, Serialize, Deserialize)]
703pub struct SyncStatus {
704 pub workspace_id: String,
706 pub enabled: bool,
708 pub target_directory: Option<String>,
710 pub sync_direction: SyncDirection,
712 pub realtime_monitoring: bool,
714 pub last_sync: Option<chrono::DateTime<chrono::Utc>>,
716 pub status: String,
718}
719
720#[derive(Debug, Clone, Serialize, Deserialize)]
722pub struct SyncChange {
723 pub change_type: String,
725 pub path: String,
727 pub description: String,
729 pub requires_confirmation: bool,
731}
732
733#[derive(Debug, Clone, Serialize, Deserialize)]
735pub struct ConfigureSyncRequest {
736 pub target_directory: String,
738 pub sync_direction: SyncDirection,
740 pub realtime_monitoring: bool,
742 pub directory_structure: Option<SyncDirectoryStructure>,
744 pub filename_pattern: Option<String>,
746}
747
748#[derive(Debug, Clone, Serialize, Deserialize)]
750pub struct ConfirmSyncChangesRequest {
751 pub workspace_id: String,
753 pub changes: Vec<SyncChange>,
755 pub apply_all: bool,
757}
758
759#[derive(Debug, Clone, Serialize, Deserialize)]
761pub struct AutocompleteSuggestion {
762 pub text: String,
764 pub kind: String,
766 pub description: Option<String>,
768}
769
770#[derive(Debug, Clone, Serialize, Deserialize)]
772pub struct AutocompleteRequest {
773 pub input: String,
775 pub cursor_position: usize,
777 pub context: Option<String>,
779}
780
781#[derive(Debug, Clone, Serialize, Deserialize)]
783pub struct AutocompleteResponse {
784 pub suggestions: Vec<AutocompleteSuggestion>,
786 pub start_position: usize,
788 pub end_position: usize,
790}
791
792#[derive(Debug, Clone, Serialize, Deserialize)]
794pub struct TimeSeriesPoint {
795 pub timestamp: chrono::DateTime<chrono::Utc>,
797 pub value: f64,
799}
800
801#[derive(Debug, Clone, Serialize, Deserialize)]
803pub struct TimeSeriesData {
804 pub points: Vec<TimeSeriesPoint>,
806 pub metric: String,
808}
809
810#[derive(Debug, Clone, Serialize, Deserialize)]
812pub struct RestartStatus {
813 pub restarting: bool,
815 pub progress: f64,
817 pub message: String,
819}
820
821#[derive(Debug, Clone, Serialize, Deserialize)]
823pub struct SmokeTestResult {
824 pub test_name: String,
826 pub passed: bool,
828 pub response_time_ms: Option<u64>,
830 pub error_message: Option<String>,
832}
833
834#[derive(Debug, Clone, Serialize, Deserialize)]
836pub struct SmokeTestContext {
837 pub suite_name: String,
839 pub total_tests: usize,
841 pub passed_tests: usize,
843 pub failed_tests: usize,
845 pub start_time: chrono::DateTime<chrono::Utc>,
847 pub end_time: Option<chrono::DateTime<chrono::Utc>>,
849}
850
851#[derive(Debug, Clone, Serialize, Deserialize)]
853pub struct ConfigurationState {
854 pub valid: bool,
856 pub errors: Vec<String>,
858 pub warnings: Vec<String>,
860}
861
862#[derive(Debug, Clone, Serialize, Deserialize)]
864pub struct ImportHistoryEntry {
865 pub id: String,
867 pub format: String,
869 pub timestamp: chrono::DateTime<chrono::Utc>,
871 pub routes_count: usize,
873 pub variables_count: usize,
875 pub warnings_count: usize,
877 pub success: bool,
879 pub filename: Option<String>,
881 pub environment: Option<String>,
883 pub base_url: Option<String>,
885 pub error_message: Option<String>,
887}
888
889#[derive(Debug, Clone, Serialize, Deserialize)]
891pub struct FixtureInfo {
892 pub id: String,
894 pub name: String,
896 pub path: String,
898 pub size_bytes: u64,
900 pub last_modified: chrono::DateTime<chrono::Utc>,
902 pub content_type: Option<String>,
904}
905
906#[derive(Debug, Clone, Serialize, Deserialize)]
908pub struct FixtureDeleteRequest {
909 pub fixture_id: String,
911}
912
913#[derive(Debug, Clone, Serialize, Deserialize)]
915pub struct FixtureBulkDeleteRequest {
916 pub fixture_ids: Vec<String>,
918}
919
920#[derive(Debug, Clone, Serialize, Deserialize)]
922pub struct FixtureBulkDeleteResult {
923 pub deleted: Vec<String>,
925 pub failed: HashMap<String, String>,
927}
928
929#[derive(Debug, Clone, Serialize, Deserialize)]
931pub struct ImportRoute {
932 pub method: String,
934 pub path: String,
936 pub headers: HashMap<String, String>,
938 pub body: Option<String>,
940 pub response: ImportResponse,
942}
943
944#[derive(Debug, Clone, Serialize, Deserialize)]
946pub struct ImportResponse {
947 pub status: u16,
949 pub headers: HashMap<String, String>,
951 pub body: serde_json::Value,
953}
954
955#[derive(Debug, Clone, Serialize, Deserialize)]
957pub struct ImportResult {
958 pub routes: Vec<ImportRoute>,
960 pub warnings: Vec<String>,
962 pub errors: Vec<String>,
964}
965
966#[derive(Debug, Clone, Serialize, Deserialize)]
968pub struct InsomniaImportResult {
969 pub routes: Vec<ImportRoute>,
971 pub variables: HashMap<String, String>,
973 pub warnings: Vec<String>,
975}
976
977#[derive(Debug, Clone, Serialize, Deserialize)]
979pub struct ServerInfo {
980 pub version: String,
982 pub build_time: String,
984 pub git_sha: String,
986 pub http_server: Option<String>,
988 pub ws_server: Option<String>,
990 pub grpc_server: Option<String>,
992 pub graphql_server: Option<String>,
994 pub api_enabled: bool,
996 pub admin_port: u16,
998}
999
1000#[cfg(test)]
1001mod tests {
1002 use super::*;
1003
1004 #[test]
1007 fn test_api_response_success() {
1008 let data = "test data".to_string();
1009 let response = ApiResponse::success(data.clone());
1010
1011 assert!(response.success);
1012 assert_eq!(response.data, Some(data));
1013 assert!(response.error.is_none());
1014 }
1015
1016 #[test]
1017 fn test_api_response_error() {
1018 let error_msg = "Something went wrong".to_string();
1019 let response: ApiResponse<String> = ApiResponse::error(error_msg.clone());
1020
1021 assert!(!response.success);
1022 assert!(response.data.is_none());
1023 assert_eq!(response.error, Some(error_msg));
1024 }
1025
1026 #[test]
1027 fn test_api_response_success_with_int() {
1028 let response = ApiResponse::success(42);
1029 assert!(response.success);
1030 assert_eq!(response.data, Some(42));
1031 }
1032
1033 #[test]
1036 fn test_health_check_healthy() {
1037 let health = HealthCheck::healthy();
1038
1039 assert_eq!(health.status, "healthy");
1040 assert!(health.issues.is_empty());
1041 assert!(health.services.is_empty());
1042 }
1043
1044 #[test]
1045 fn test_health_check_unhealthy() {
1046 let issues = vec!["Database down".to_string(), "Cache error".to_string()];
1047 let health = HealthCheck::unhealthy(issues.clone());
1048
1049 assert_eq!(health.status, "unhealthy");
1050 assert_eq!(health.issues, issues);
1051 }
1052
1053 #[test]
1054 fn test_health_check_with_service() {
1055 let health = HealthCheck::healthy()
1056 .with_service("http".to_string(), "running".to_string())
1057 .with_service("grpc".to_string(), "running".to_string());
1058
1059 assert_eq!(health.services.len(), 2);
1060 assert_eq!(health.services.get("http"), Some(&"running".to_string()));
1061 }
1062
1063 #[test]
1066 fn test_log_filter_default() {
1067 let filter = LogFilter::default();
1068
1069 assert!(filter.method.is_none());
1070 assert!(filter.path_pattern.is_none());
1071 assert_eq!(filter.hours_ago, Some(24));
1072 assert_eq!(filter.limit, Some(100));
1073 }
1074
1075 #[test]
1076 fn test_log_filter_custom() {
1077 let filter = LogFilter {
1078 method: Some("POST".to_string()),
1079 path_pattern: Some("/api/.*".to_string()),
1080 status_code: Some(200),
1081 hours_ago: Some(48),
1082 limit: Some(500),
1083 };
1084
1085 assert_eq!(filter.method.as_deref(), Some("POST"));
1086 assert_eq!(filter.status_code, Some(200));
1087 }
1088
1089 #[test]
1092 fn test_server_status() {
1093 let status = ServerStatus {
1094 server_type: "HTTP".to_string(),
1095 address: Some("127.0.0.1:3000".to_string()),
1096 running: true,
1097 start_time: Some(chrono::Utc::now()),
1098 uptime_seconds: Some(3600),
1099 active_connections: 10,
1100 total_requests: 1000,
1101 };
1102
1103 assert_eq!(status.server_type, "HTTP");
1104 assert!(status.running);
1105 assert_eq!(status.active_connections, 10);
1106 }
1107
1108 #[test]
1109 fn test_server_status_serialization() {
1110 let status = ServerStatus {
1111 server_type: "gRPC".to_string(),
1112 address: None,
1113 running: false,
1114 start_time: None,
1115 uptime_seconds: None,
1116 active_connections: 0,
1117 total_requests: 0,
1118 };
1119
1120 let json = serde_json::to_string(&status).unwrap();
1121 let deserialized: ServerStatus = serde_json::from_str(&json).unwrap();
1122 assert_eq!(deserialized.server_type, "gRPC");
1123 }
1124
1125 #[test]
1128 fn test_route_info() {
1129 let route = RouteInfo {
1130 method: Some("GET".to_string()),
1131 path: "/api/users".to_string(),
1132 priority: 100,
1133 has_fixtures: true,
1134 latency_ms: Some(50),
1135 request_count: 500,
1136 last_request: None,
1137 error_count: 5,
1138 };
1139
1140 assert_eq!(route.method, Some("GET".to_string()));
1141 assert_eq!(route.path, "/api/users");
1142 assert!(route.has_fixtures);
1143 }
1144
1145 #[test]
1146 fn test_route_info_clone() {
1147 let route = RouteInfo {
1148 method: Some("POST".to_string()),
1149 path: "/api/items".to_string(),
1150 priority: 50,
1151 has_fixtures: false,
1152 latency_ms: None,
1153 request_count: 0,
1154 last_request: None,
1155 error_count: 0,
1156 };
1157
1158 let cloned = route.clone();
1159 assert_eq!(cloned.path, route.path);
1160 assert_eq!(cloned.priority, route.priority);
1161 }
1162
1163 #[test]
1166 fn test_request_log() {
1167 let log = RequestLog {
1168 id: "req-123".to_string(),
1169 timestamp: chrono::Utc::now(),
1170 method: "GET".to_string(),
1171 path: "/api/data".to_string(),
1172 status_code: 200,
1173 response_time_ms: 45,
1174 client_ip: Some("192.168.1.1".to_string()),
1175 user_agent: Some("curl/7.64.0".to_string()),
1176 headers: HashMap::new(),
1177 response_size_bytes: 1024,
1178 error_message: None,
1179 };
1180
1181 assert_eq!(log.id, "req-123");
1182 assert_eq!(log.status_code, 200);
1183 }
1184
1185 #[test]
1188 fn test_system_info() {
1189 let info = SystemInfo {
1190 version: "0.3.8".to_string(),
1191 uptime_seconds: 86400,
1192 memory_usage_mb: 256,
1193 cpu_usage_percent: 25.5,
1194 active_threads: 8,
1195 total_routes: 50,
1196 total_fixtures: 100,
1197 };
1198
1199 assert_eq!(info.version, "0.3.8");
1200 assert_eq!(info.total_routes, 50);
1201 }
1202
1203 #[test]
1206 fn test_latency_profile() {
1207 let profile = LatencyProfile {
1208 name: "slow_network".to_string(),
1209 base_ms: 200,
1210 jitter_ms: 50,
1211 tag_overrides: HashMap::from([("critical".to_string(), 50)]),
1212 };
1213
1214 assert_eq!(profile.name, "slow_network");
1215 assert_eq!(profile.base_ms, 200);
1216 }
1217
1218 #[test]
1221 fn test_fault_config() {
1222 let config = FaultConfig {
1223 enabled: true,
1224 failure_rate: 0.1,
1225 status_codes: vec![500, 503],
1226 active_failures: 5,
1227 };
1228
1229 assert!(config.enabled);
1230 assert!(config.failure_rate > 0.0);
1231 assert_eq!(config.status_codes.len(), 2);
1232 }
1233
1234 #[test]
1237 fn test_proxy_config() {
1238 let config = ProxyConfig {
1239 enabled: true,
1240 upstream_url: Some("https://api.example.com".to_string()),
1241 timeout_seconds: 30,
1242 requests_proxied: 1000,
1243 };
1244
1245 assert!(config.enabled);
1246 assert!(config.upstream_url.is_some());
1247 }
1248
1249 #[test]
1252 fn test_bandwidth_config() {
1253 let config = BandwidthConfig {
1254 enabled: true,
1255 max_bytes_per_sec: 1_000_000,
1256 burst_capacity_bytes: 10_000,
1257 tag_overrides: HashMap::new(),
1258 };
1259
1260 assert!(config.enabled);
1261 assert_eq!(config.max_bytes_per_sec, 1_000_000);
1262 }
1263
1264 #[test]
1267 fn test_burst_loss_config() {
1268 let config = BurstLossConfig {
1269 enabled: false,
1270 burst_probability: 0.05,
1271 burst_duration_ms: 1000,
1272 loss_rate_during_burst: 0.3,
1273 recovery_time_ms: 5000,
1274 tag_overrides: HashMap::new(),
1275 };
1276
1277 assert!(!config.enabled);
1278 assert_eq!(config.burst_duration_ms, 1000);
1279 }
1280
1281 #[test]
1284 fn test_simple_metrics_data() {
1285 let metrics = SimpleMetricsData {
1286 total_requests: 10000,
1287 active_requests: 5,
1288 average_response_time: 45.5,
1289 error_rate: 0.02,
1290 };
1291
1292 assert_eq!(metrics.total_requests, 10000);
1293 assert!(metrics.error_rate < 0.05);
1294 }
1295
1296 #[test]
1299 fn test_config_update() {
1300 let update = ConfigUpdate {
1301 config_type: "latency".to_string(),
1302 data: serde_json::json!({"base_ms": 100}),
1303 };
1304
1305 assert_eq!(update.config_type, "latency");
1306 }
1307
1308 #[test]
1311 fn test_route_update() {
1312 let update = RouteUpdate {
1313 path: "/api/users".to_string(),
1314 method: Some("POST".to_string()),
1315 operation: "create".to_string(),
1316 data: Some(serde_json::json!({"fixture": "user.json"})),
1317 };
1318
1319 assert_eq!(update.path, "/api/users");
1320 assert_eq!(update.operation, "create");
1321 }
1322
1323 #[test]
1326 fn test_validation_settings() {
1327 let settings = ValidationSettings {
1328 mode: "enforce".to_string(),
1329 aggregate_errors: true,
1330 validate_responses: true,
1331 overrides: HashMap::new(),
1332 };
1333
1334 assert_eq!(settings.mode, "enforce");
1335 assert!(settings.validate_responses);
1336 }
1337
1338 #[test]
1341 fn test_log_entry() {
1342 let entry = LogEntry {
1343 timestamp: chrono::Utc::now(),
1344 status: 200,
1345 method: "GET".to_string(),
1346 url: "/api/test".to_string(),
1347 response_time: 50,
1348 size: 1024,
1349 };
1350
1351 assert_eq!(entry.status, 200);
1352 assert_eq!(entry.method, "GET");
1353 }
1354
1355 #[test]
1358 fn test_workspace_summary() {
1359 let summary = WorkspaceSummary {
1360 id: "ws-123".to_string(),
1361 name: "My Workspace".to_string(),
1362 description: Some("Test workspace".to_string()),
1363 active: true,
1364 folder_count: 5,
1365 request_count: 20,
1366 created_at: chrono::Utc::now(),
1367 updated_at: chrono::Utc::now(),
1368 };
1369
1370 assert_eq!(summary.name, "My Workspace");
1371 assert!(summary.active);
1372 }
1373
1374 #[test]
1377 fn test_folder_summary() {
1378 let folder = FolderSummary {
1379 id: "folder-123".to_string(),
1380 name: "API Tests".to_string(),
1381 description: None,
1382 parent_id: None,
1383 subfolder_count: 3,
1384 request_count: 10,
1385 created_at: chrono::Utc::now(),
1386 updated_at: chrono::Utc::now(),
1387 };
1388
1389 assert_eq!(folder.name, "API Tests");
1390 assert!(folder.parent_id.is_none());
1391 }
1392
1393 #[test]
1396 fn test_request_summary() {
1397 let request = RequestSummary {
1398 id: "req-123".to_string(),
1399 name: "Get Users".to_string(),
1400 description: Some("Fetch all users".to_string()),
1401 method: "GET".to_string(),
1402 path: "/api/users".to_string(),
1403 status_code: 200,
1404 created_at: chrono::Utc::now(),
1405 updated_at: chrono::Utc::now(),
1406 };
1407
1408 assert_eq!(request.method, "GET");
1409 assert_eq!(request.status_code, 200);
1410 }
1411
1412 #[test]
1415 fn test_create_workspace_request() {
1416 let request = CreateWorkspaceRequest {
1417 name: "New Workspace".to_string(),
1418 description: Some("A new workspace".to_string()),
1419 };
1420
1421 assert_eq!(request.name, "New Workspace");
1422 }
1423
1424 #[test]
1427 fn test_create_folder_request() {
1428 let request = CreateFolderRequest {
1429 name: "New Folder".to_string(),
1430 description: None,
1431 parent_id: Some("parent-123".to_string()),
1432 };
1433
1434 assert_eq!(request.name, "New Folder");
1435 assert!(request.parent_id.is_some());
1436 }
1437
1438 #[test]
1441 fn test_sync_direction_conversion() {
1442 let manual = mockforge_core::workspace::SyncDirection::Manual;
1443 let ui_manual: SyncDirection = manual.into();
1444 assert!(matches!(ui_manual, SyncDirection::Manual));
1445 }
1446
1447 #[test]
1448 fn test_sync_direction_workspace_to_directory() {
1449 let dir = mockforge_core::workspace::SyncDirection::WorkspaceToDirectory;
1450 let ui_dir: SyncDirection = dir.into();
1451 assert!(matches!(ui_dir, SyncDirection::WorkspaceToDirectory));
1452 }
1453
1454 #[test]
1455 fn test_sync_direction_bidirectional() {
1456 let bidir = mockforge_core::workspace::SyncDirection::Bidirectional;
1457 let ui_bidir: SyncDirection = bidir.into();
1458 assert!(matches!(ui_bidir, SyncDirection::Bidirectional));
1459 }
1460
1461 #[test]
1464 fn test_environment_color() {
1465 let color = EnvironmentColor {
1466 hex: "#FF5733".to_string(),
1467 name: Some("Orange".to_string()),
1468 };
1469
1470 assert_eq!(color.hex, "#FF5733");
1471 assert_eq!(color.name, Some("Orange".to_string()));
1472 }
1473
1474 #[test]
1475 fn test_environment_color_without_name() {
1476 let color = EnvironmentColor {
1477 hex: "#00FF00".to_string(),
1478 name: None,
1479 };
1480
1481 assert_eq!(color.hex, "#00FF00");
1482 assert!(color.name.is_none());
1483 }
1484
1485 #[test]
1488 fn test_environment_summary() {
1489 let env = EnvironmentSummary {
1490 id: "env-123".to_string(),
1491 name: "Production".to_string(),
1492 description: Some("Production environment".to_string()),
1493 color: Some(EnvironmentColor {
1494 hex: "#FF0000".to_string(),
1495 name: Some("Red".to_string()),
1496 }),
1497 variable_count: 10,
1498 active: true,
1499 is_global: false,
1500 created_at: chrono::Utc::now(),
1501 updated_at: chrono::Utc::now(),
1502 };
1503
1504 assert_eq!(env.name, "Production");
1505 assert!(env.active);
1506 assert!(!env.is_global);
1507 }
1508
1509 #[test]
1512 fn test_autocomplete_suggestion() {
1513 let suggestion = AutocompleteSuggestion {
1514 text: "{{base_url}}".to_string(),
1515 kind: "variable".to_string(),
1516 description: Some("Base URL variable".to_string()),
1517 };
1518
1519 assert_eq!(suggestion.kind, "variable");
1520 }
1521
1522 #[test]
1525 fn test_time_series_point() {
1526 let point = TimeSeriesPoint {
1527 timestamp: chrono::Utc::now(),
1528 value: 123.45,
1529 };
1530
1531 assert!((point.value - 123.45).abs() < f64::EPSILON);
1532 }
1533
1534 #[test]
1537 fn test_restart_status() {
1538 let status = RestartStatus {
1539 restarting: true,
1540 progress: 0.5,
1541 message: "Restarting services...".to_string(),
1542 };
1543
1544 assert!(status.restarting);
1545 assert!((status.progress - 0.5).abs() < f64::EPSILON);
1546 }
1547
1548 #[test]
1551 fn test_smoke_test_result_passed() {
1552 let result = SmokeTestResult {
1553 test_name: "Health Check".to_string(),
1554 passed: true,
1555 response_time_ms: Some(50),
1556 error_message: None,
1557 };
1558
1559 assert!(result.passed);
1560 assert!(result.error_message.is_none());
1561 }
1562
1563 #[test]
1564 fn test_smoke_test_result_failed() {
1565 let result = SmokeTestResult {
1566 test_name: "Database Connection".to_string(),
1567 passed: false,
1568 response_time_ms: None,
1569 error_message: Some("Connection timeout".to_string()),
1570 };
1571
1572 assert!(!result.passed);
1573 assert!(result.error_message.is_some());
1574 }
1575
1576 #[test]
1579 fn test_configuration_state_valid() {
1580 let state = ConfigurationState {
1581 valid: true,
1582 errors: vec![],
1583 warnings: vec!["Deprecated setting used".to_string()],
1584 };
1585
1586 assert!(state.valid);
1587 assert!(state.errors.is_empty());
1588 assert!(!state.warnings.is_empty());
1589 }
1590
1591 #[test]
1594 fn test_import_history_entry() {
1595 let entry = ImportHistoryEntry {
1596 id: "import-123".to_string(),
1597 format: "postman".to_string(),
1598 timestamp: chrono::Utc::now(),
1599 routes_count: 25,
1600 variables_count: 5,
1601 warnings_count: 2,
1602 success: true,
1603 filename: Some("collection.json".to_string()),
1604 environment: Some("development".to_string()),
1605 base_url: Some("https://api.example.com".to_string()),
1606 error_message: None,
1607 };
1608
1609 assert!(entry.success);
1610 assert_eq!(entry.routes_count, 25);
1611 }
1612
1613 #[test]
1616 fn test_fixture_info() {
1617 let fixture = FixtureInfo {
1618 id: "fixture-123".to_string(),
1619 name: "users.json".to_string(),
1620 path: "/fixtures/users.json".to_string(),
1621 size_bytes: 2048,
1622 last_modified: chrono::Utc::now(),
1623 content_type: Some("application/json".to_string()),
1624 };
1625
1626 assert_eq!(fixture.name, "users.json");
1627 assert_eq!(fixture.size_bytes, 2048);
1628 }
1629
1630 #[test]
1633 fn test_import_route() {
1634 let route = ImportRoute {
1635 method: "POST".to_string(),
1636 path: "/api/users".to_string(),
1637 headers: HashMap::from([("Content-Type".to_string(), "application/json".to_string())]),
1638 body: Some(r#"{"name": "test"}"#.to_string()),
1639 response: ImportResponse {
1640 status: 201,
1641 headers: HashMap::new(),
1642 body: serde_json::json!({"id": 1}),
1643 },
1644 };
1645
1646 assert_eq!(route.method, "POST");
1647 assert_eq!(route.response.status, 201);
1648 }
1649
1650 #[test]
1653 fn test_server_info() {
1654 let info = ServerInfo {
1655 version: "0.3.8".to_string(),
1656 build_time: "2024-01-01T00:00:00Z".to_string(),
1657 git_sha: "abc123".to_string(),
1658 http_server: Some("0.0.0.0:3000".to_string()),
1659 ws_server: Some("0.0.0.0:3001".to_string()),
1660 grpc_server: None,
1661 graphql_server: None,
1662 api_enabled: true,
1663 admin_port: 8080,
1664 };
1665
1666 assert_eq!(info.version, "0.3.8");
1667 assert!(info.api_enabled);
1668 }
1669
1670 #[test]
1673 fn test_log_filter_serialization() {
1674 let filter = LogFilter::default();
1675 let json = serde_json::to_string(&filter).unwrap();
1676 let deserialized: LogFilter = serde_json::from_str(&json).unwrap();
1677
1678 assert_eq!(deserialized.hours_ago, filter.hours_ago);
1679 }
1680
1681 #[test]
1682 fn test_health_check_serialization() {
1683 let health = HealthCheck::healthy().with_service("api".to_string(), "ok".to_string());
1684 let json = serde_json::to_string(&health).unwrap();
1685 let deserialized: HealthCheck = serde_json::from_str(&json).unwrap();
1686
1687 assert_eq!(deserialized.status, "healthy");
1688 }
1689}