mockforge_ui/
models.rs

1//! Data models for the admin UI
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6/// Server status information
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct ServerStatus {
9    /// Server type (HTTP, WebSocket, gRPC)
10    pub server_type: String,
11    /// Server address
12    pub address: Option<String>,
13    /// Whether server is running
14    pub running: bool,
15    /// Start time
16    pub start_time: Option<chrono::DateTime<chrono::Utc>>,
17    /// Uptime in seconds
18    pub uptime_seconds: Option<u64>,
19    /// Number of active connections
20    pub active_connections: u64,
21    /// Total requests served
22    pub total_requests: u64,
23}
24
25/// Route information
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct RouteInfo {
28    /// HTTP method
29    pub method: Option<String>,
30    /// Route path
31    pub path: String,
32    /// Route priority
33    pub priority: i32,
34    /// Whether route has fixtures
35    pub has_fixtures: bool,
36    /// Latency profile
37    pub latency_ms: Option<u64>,
38    /// Request count
39    pub request_count: u64,
40    /// Last request time
41    pub last_request: Option<chrono::DateTime<chrono::Utc>>,
42    /// Error count
43    pub error_count: u64,
44}
45
46/// Request log entry
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct RequestLog {
49    /// Request ID
50    pub id: String,
51    /// Timestamp
52    pub timestamp: chrono::DateTime<chrono::Utc>,
53    /// HTTP method
54    pub method: String,
55    /// Request path
56    pub path: String,
57    /// Response status code
58    pub status_code: u16,
59    /// Response time in milliseconds
60    pub response_time_ms: u64,
61    /// Client IP address
62    pub client_ip: Option<String>,
63    /// User agent
64    pub user_agent: Option<String>,
65    /// Request headers (filtered)
66    pub headers: HashMap<String, String>,
67    /// Response size in bytes
68    pub response_size_bytes: u64,
69    /// Error message (if any)
70    pub error_message: Option<String>,
71}
72
73/// System information
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct SystemInfo {
76    /// MockForge version
77    pub version: String,
78    /// Uptime in seconds
79    pub uptime_seconds: u64,
80    /// Memory usage in MB
81    pub memory_usage_mb: u64,
82    /// CPU usage percentage
83    pub cpu_usage_percent: f64,
84    /// Number of active threads
85    pub active_threads: usize,
86    /// Total routes configured
87    pub total_routes: usize,
88    /// Total fixtures available
89    pub total_fixtures: usize,
90}
91
92/// Latency profile configuration
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct LatencyProfile {
95    /// Profile name
96    pub name: String,
97    /// Base latency in milliseconds
98    pub base_ms: u64,
99    /// Jitter range in milliseconds
100    pub jitter_ms: u64,
101    /// Tag-based overrides
102    pub tag_overrides: HashMap<String, u64>,
103}
104
105/// Fault injection configuration
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct FaultConfig {
108    /// Whether fault injection is enabled
109    pub enabled: bool,
110    /// Failure rate (0.0 to 1.0)
111    pub failure_rate: f64,
112    /// HTTP status codes for failures
113    pub status_codes: Vec<u16>,
114    /// Current active failures
115    pub active_failures: u64,
116}
117
118/// Proxy configuration
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct ProxyConfig {
121    /// Whether proxy is enabled
122    pub enabled: bool,
123    /// Upstream URL
124    pub upstream_url: Option<String>,
125    /// Request timeout seconds
126    pub timeout_seconds: u64,
127    /// Total requests proxied
128    pub requests_proxied: u64,
129}
130
131/// Bandwidth configuration
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct BandwidthConfig {
134    /// Whether bandwidth throttling is enabled
135    pub enabled: bool,
136    /// Maximum bandwidth in bytes per second
137    pub max_bytes_per_sec: u64,
138    /// Burst capacity in bytes
139    pub burst_capacity_bytes: u64,
140    /// Tag-based overrides
141    pub tag_overrides: HashMap<String, u64>,
142}
143
144/// Burst loss configuration
145#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct BurstLossConfig {
147    /// Whether burst loss is enabled
148    pub enabled: bool,
149    /// Probability of entering burst (0.0 to 1.0)
150    pub burst_probability: f64,
151    /// Duration of burst in milliseconds
152    pub burst_duration_ms: u64,
153    /// Loss rate during burst (0.0 to 1.0)
154    pub loss_rate_during_burst: f64,
155    /// Recovery time between bursts in milliseconds
156    pub recovery_time_ms: u64,
157    /// Tag-based overrides
158    pub tag_overrides: HashMap<String, BurstLossOverride>,
159}
160
161/// Burst loss override for specific tags
162#[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/// Traffic shaping configuration
171#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct TrafficShapingConfig {
173    /// Whether traffic shaping is enabled
174    pub enabled: bool,
175    /// Bandwidth configuration
176    pub bandwidth: BandwidthConfig,
177    /// Burst loss configuration
178    pub burst_loss: BurstLossConfig,
179}
180
181/// Simple metrics data for admin dashboard
182#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct SimpleMetricsData {
184    /// Total requests served
185    pub total_requests: u64,
186    /// Active requests currently being processed
187    pub active_requests: u64,
188    /// Average response time in milliseconds
189    pub average_response_time: f64,
190    /// Error rate (0.0 to 1.0)
191    pub error_rate: f64,
192}
193
194/// Dashboard system information
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct DashboardSystemInfo {
197    /// Operating system
198    pub os: String,
199    /// Architecture
200    pub arch: String,
201    /// Uptime in seconds
202    pub uptime: u64,
203    /// Memory usage in bytes
204    pub memory_usage: u64,
205}
206
207/// Dashboard data
208#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct DashboardData {
210    /// Server information
211    pub server_info: ServerInfo,
212    /// System information
213    pub system_info: DashboardSystemInfo,
214    /// Metrics data
215    pub metrics: SimpleMetricsData,
216    /// Server status information
217    pub servers: Vec<ServerStatus>,
218    /// Recent logs
219    pub recent_logs: Vec<RequestLog>,
220    /// System information (for JS compatibility)
221    pub system: SystemInfo,
222}
223
224/// API response wrapper
225#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct ApiResponse<T> {
227    /// Whether request was successful
228    pub success: bool,
229    /// Response data
230    pub data: Option<T>,
231    /// Error message (if any)
232    pub error: Option<String>,
233    /// Response timestamp
234    pub timestamp: chrono::DateTime<chrono::Utc>,
235}
236
237impl<T> ApiResponse<T> {
238    /// Create a successful response
239    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    /// Create an error response
249    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/// Configuration update request
260#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct ConfigUpdate {
262    /// Configuration type
263    pub config_type: String,
264    /// Configuration data
265    pub data: serde_json::Value,
266}
267
268/// Route management request
269#[derive(Debug, Clone, Serialize, Deserialize)]
270pub struct RouteUpdate {
271    /// Route path
272    pub path: String,
273    /// HTTP method (optional)
274    pub method: Option<String>,
275    /// Update operation
276    pub operation: String,
277    /// Update data
278    pub data: Option<serde_json::Value>,
279}
280
281/// Log filter options
282#[derive(Debug, Clone, Serialize, Deserialize)]
283pub struct LogFilter {
284    /// Filter by HTTP method
285    pub method: Option<String>,
286    /// Filter by path pattern
287    pub path_pattern: Option<String>,
288    /// Filter by status code
289    pub status_code: Option<u16>,
290    /// Filter by time range (hours ago)
291    pub hours_ago: Option<u64>,
292    /// Maximum number of results
293    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/// Performance metrics data with detailed percentile analysis
309#[derive(Debug, Clone, Serialize, Deserialize)]
310pub struct MetricsData {
311    /// Request count by endpoint
312    pub requests_by_endpoint: HashMap<String, u64>,
313    /// Response time percentiles (p50, p75, p90, p95, p99, p999)
314    pub response_time_percentiles: HashMap<String, u64>,
315    /// Per-endpoint percentiles for detailed analysis
316    #[serde(skip_serializing_if = "Option::is_none")]
317    pub endpoint_percentiles: Option<HashMap<String, HashMap<String, u64>>>,
318    /// Latency over time (time-series data)
319    #[serde(skip_serializing_if = "Option::is_none")]
320    pub latency_over_time: Option<Vec<(chrono::DateTime<chrono::Utc>, u64)>>,
321    /// Error rate by endpoint
322    pub error_rate_by_endpoint: HashMap<String, f64>,
323    /// Memory usage over time
324    pub memory_usage_over_time: Vec<(chrono::DateTime<chrono::Utc>, u64)>,
325    /// CPU usage over time
326    pub cpu_usage_over_time: Vec<(chrono::DateTime<chrono::Utc>, f64)>,
327}
328
329/// Validation settings
330#[derive(Debug, Clone, Serialize, Deserialize)]
331pub struct ValidationSettings {
332    /// Validation mode: "enforce", "warn", or "off"
333    pub mode: String,
334    /// Whether to aggregate errors
335    pub aggregate_errors: bool,
336    /// Whether to validate responses
337    pub validate_responses: bool,
338    /// Per-route validation overrides
339    pub overrides: HashMap<String, String>,
340}
341
342/// Validation update request
343#[derive(Debug, Clone, Serialize, Deserialize)]
344pub struct ValidationUpdate {
345    /// Validation mode
346    pub mode: String,
347    /// Whether to aggregate errors
348    pub aggregate_errors: bool,
349    /// Whether to validate responses
350    pub validate_responses: bool,
351    /// Per-route validation overrides
352    pub overrides: Option<HashMap<String, String>>,
353}
354
355/// Log entry for admin UI
356#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct LogEntry {
358    /// Request timestamp
359    pub timestamp: chrono::DateTime<chrono::Utc>,
360    /// HTTP status code
361    pub status: u16,
362    /// HTTP method
363    pub method: String,
364    /// Request URL/path
365    pub url: String,
366    /// Response time in milliseconds
367    pub response_time: u64,
368    /// Response size in bytes
369    pub size: u64,
370}
371
372/// Health check response
373#[derive(Debug, Clone, Serialize, Deserialize)]
374pub struct HealthCheck {
375    /// Overall health status
376    pub status: String,
377    /// Individual service health
378    pub services: HashMap<String, String>,
379    /// Last health check time
380    pub last_check: chrono::DateTime<chrono::Utc>,
381    /// Any health issues
382    pub issues: Vec<String>,
383}
384
385impl HealthCheck {
386    /// Create a healthy status
387    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    /// Create an unhealthy status
397    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    /// Add service status
407    pub fn with_service(mut self, name: String, status: String) -> Self {
408        self.services.insert(name, status);
409        self
410    }
411}
412
413/// Workspace summary information
414#[derive(Debug, Clone, Serialize, Deserialize)]
415pub struct WorkspaceSummary {
416    /// Workspace ID
417    pub id: String,
418    /// Workspace name
419    pub name: String,
420    /// Description
421    pub description: Option<String>,
422    /// Whether this is the active workspace
423    pub active: bool,
424    /// Number of folders
425    pub folder_count: usize,
426    /// Number of requests
427    pub request_count: usize,
428    /// Created timestamp
429    pub created_at: chrono::DateTime<chrono::Utc>,
430    /// Updated timestamp
431    pub updated_at: chrono::DateTime<chrono::Utc>,
432}
433
434/// Folder summary information
435#[derive(Debug, Clone, Serialize, Deserialize)]
436pub struct FolderSummary {
437    /// Folder ID
438    pub id: String,
439    /// Folder name
440    pub name: String,
441    /// Description
442    pub description: Option<String>,
443    /// Parent folder ID (None if root)
444    pub parent_id: Option<String>,
445    /// Number of subfolders
446    pub subfolder_count: usize,
447    /// Number of requests
448    pub request_count: usize,
449    /// Created timestamp
450    pub created_at: chrono::DateTime<chrono::Utc>,
451    /// Updated timestamp
452    pub updated_at: chrono::DateTime<chrono::Utc>,
453}
454
455/// Request summary information
456#[derive(Debug, Clone, Serialize, Deserialize)]
457pub struct RequestSummary {
458    /// Request ID
459    pub id: String,
460    /// Request name
461    pub name: String,
462    /// Description
463    pub description: Option<String>,
464    /// HTTP method
465    pub method: String,
466    /// Request path
467    pub path: String,
468    /// Response status code
469    pub status_code: u16,
470    /// Created timestamp
471    pub created_at: chrono::DateTime<chrono::Utc>,
472    /// Updated timestamp
473    pub updated_at: chrono::DateTime<chrono::Utc>,
474}
475
476/// Workspace detailed information
477#[derive(Debug, Clone, Serialize, Deserialize)]
478pub struct WorkspaceDetail {
479    /// Workspace summary
480    pub summary: WorkspaceSummary,
481    /// Root folders
482    pub folders: Vec<FolderSummary>,
483    /// Root requests
484    pub requests: Vec<RequestSummary>,
485}
486
487/// Folder detailed information
488#[derive(Debug, Clone, Serialize, Deserialize)]
489pub struct FolderDetail {
490    /// Folder summary
491    pub summary: FolderSummary,
492    /// Subfolders
493    pub subfolders: Vec<FolderSummary>,
494    /// Requests in this folder
495    pub requests: Vec<RequestSummary>,
496}
497
498/// Create workspace request
499#[derive(Debug, Clone, Serialize, Deserialize)]
500pub struct CreateWorkspaceRequest {
501    /// Workspace name
502    pub name: String,
503    /// Description (optional)
504    pub description: Option<String>,
505}
506
507/// Create folder request
508#[derive(Debug, Clone, Serialize, Deserialize)]
509pub struct CreateFolderRequest {
510    /// Folder name
511    pub name: String,
512    /// Description (optional)
513    pub description: Option<String>,
514    /// Parent folder ID (optional)
515    pub parent_id: Option<String>,
516}
517
518/// Create request request
519#[derive(Debug, Clone, Serialize, Deserialize)]
520pub struct CreateRequestRequest {
521    /// Request name
522    pub name: String,
523    /// Description (optional)
524    pub description: Option<String>,
525    /// HTTP method
526    pub method: String,
527    /// Request path
528    pub path: String,
529    /// Response status code
530    pub status_code: Option<u16>,
531    /// Response body
532    pub response_body: Option<String>,
533    /// Folder ID (optional)
534    pub folder_id: Option<String>,
535}
536
537/// Import to workspace request
538#[derive(Debug, Clone, Serialize, Deserialize)]
539pub struct ImportToWorkspaceRequest {
540    /// Import format (postman, insomnia, curl)
541    pub format: String,
542    /// Import data (file content or URL)
543    pub data: String,
544    /// Folder ID to import into (optional)
545    pub folder_id: Option<String>,
546    /// Whether to create folders from import structure
547    pub create_folders: Option<bool>,
548    /// Indices of routes to import (for selective import)
549    pub selected_routes: Option<Vec<usize>>,
550}
551
552/// Export workspaces request
553#[derive(Debug, Clone, Serialize, Deserialize)]
554pub struct ExportWorkspacesRequest {
555    /// Workspace IDs to export
556    pub workspace_ids: Vec<String>,
557}
558
559/// Workspace export data
560#[derive(Debug, Clone, Serialize, Deserialize)]
561pub struct WorkspaceExportData {
562    /// Exported workspaces
563    pub workspaces: Vec<mockforge_core::Workspace>,
564    /// Export version
565    pub version: String,
566    /// Export timestamp
567    pub exported_at: chrono::DateTime<chrono::Utc>,
568    /// Exporter version
569    pub exporter_version: String,
570}
571
572/// Environment color information
573#[derive(Debug, Clone, Serialize, Deserialize)]
574pub struct EnvironmentColor {
575    /// Hex color code (e.g., "#FF5733")
576    pub hex: String,
577    /// Optional color name for accessibility
578    pub name: Option<String>,
579}
580
581/// Environment summary information
582#[derive(Debug, Clone, Serialize, Deserialize)]
583pub struct EnvironmentSummary {
584    /// Environment ID
585    pub id: String,
586    /// Environment name
587    pub name: String,
588    /// Description
589    pub description: Option<String>,
590    /// Color for visual distinction
591    pub color: Option<EnvironmentColor>,
592    /// Number of variables
593    pub variable_count: usize,
594    /// Whether this is the active environment
595    pub active: bool,
596    /// Whether this is the global environment
597    pub is_global: bool,
598    /// Created timestamp
599    pub created_at: chrono::DateTime<chrono::Utc>,
600    /// Updated timestamp
601    pub updated_at: chrono::DateTime<chrono::Utc>,
602}
603
604/// Environment variable information
605#[derive(Debug, Clone, Serialize, Deserialize)]
606pub struct EnvironmentVariable {
607    /// Variable name
608    pub name: String,
609    /// Variable value
610    pub value: String,
611    /// Whether this variable is from the global environment
612    pub from_global: bool,
613}
614
615/// Create environment request
616#[derive(Debug, Clone, Serialize, Deserialize)]
617pub struct CreateEnvironmentRequest {
618    /// Environment name
619    pub name: String,
620    /// Description
621    pub description: Option<String>,
622}
623
624/// Update environment request
625#[derive(Debug, Clone, Serialize, Deserialize)]
626pub struct UpdateEnvironmentRequest {
627    /// Environment name
628    pub name: Option<String>,
629    /// Description
630    pub description: Option<String>,
631    /// Color
632    pub color: Option<EnvironmentColor>,
633}
634
635/// Set variable request
636#[derive(Debug, Clone, Serialize, Deserialize)]
637pub struct SetVariableRequest {
638    /// Variable name
639    pub name: String,
640    /// Variable value
641    pub value: String,
642}
643
644/// Directory sync configuration
645#[derive(Debug, Clone, Serialize, Deserialize)]
646pub struct SyncConfig {
647    /// Enable directory syncing for this workspace
648    pub enabled: bool,
649    /// Target directory for sync (relative or absolute path)
650    pub target_directory: Option<String>,
651    /// Directory structure to use (flat, nested, grouped)
652    pub directory_structure: SyncDirectoryStructure,
653    /// Auto-sync direction (one-way workspace→directory, bidirectional, or manual)
654    pub sync_direction: SyncDirection,
655    /// Whether to include metadata files
656    pub include_metadata: bool,
657    /// Filesystem monitoring enabled for real-time sync
658    pub realtime_monitoring: bool,
659    /// Custom filename pattern for exported files
660    pub filename_pattern: String,
661    /// Regular expression for excluding workspaces/requests
662    pub exclude_pattern: Option<String>,
663    /// Force overwrite existing files during sync
664    pub force_overwrite: bool,
665}
666
667/// Directory structure options for sync
668#[derive(Debug, Clone, Serialize, Deserialize)]
669pub enum SyncDirectoryStructure {
670    /// All workspaces in flat structure: workspace-name.yaml
671    Flat,
672    /// Nested by workspace: workspaces/{name}/workspace.yaml + requests/
673    Nested,
674    /// Grouped by type: requests/, responses/, metadata/
675    Grouped,
676}
677
678/// Sync direction options
679#[derive(Debug, Clone, Serialize, Deserialize)]
680pub enum SyncDirection {
681    /// Manual sync only (one-off operations)
682    Manual,
683    /// One-way: workspace changes sync silently to directory
684    WorkspaceToDirectory,
685    /// Bidirectional: changes in either direction trigger sync
686    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/// Sync status information
702#[derive(Debug, Clone, Serialize, Deserialize)]
703pub struct SyncStatus {
704    /// Workspace ID
705    pub workspace_id: String,
706    /// Whether sync is enabled
707    pub enabled: bool,
708    /// Target directory
709    pub target_directory: Option<String>,
710    /// Current sync direction
711    pub sync_direction: SyncDirection,
712    /// Whether real-time monitoring is active
713    pub realtime_monitoring: bool,
714    /// Last sync timestamp
715    pub last_sync: Option<chrono::DateTime<chrono::Utc>>,
716    /// Sync status message
717    pub status: String,
718}
719
720/// Sync change information
721#[derive(Debug, Clone, Serialize, Deserialize)]
722pub struct SyncChange {
723    /// Change type
724    pub change_type: String,
725    /// File path
726    pub path: String,
727    /// Change description
728    pub description: String,
729    /// Whether this change requires confirmation
730    pub requires_confirmation: bool,
731}
732
733/// Configure sync request
734#[derive(Debug, Clone, Serialize, Deserialize)]
735pub struct ConfigureSyncRequest {
736    /// Target directory
737    pub target_directory: String,
738    /// Sync direction
739    pub sync_direction: SyncDirection,
740    /// Enable real-time monitoring
741    pub realtime_monitoring: bool,
742    /// Directory structure
743    pub directory_structure: Option<SyncDirectoryStructure>,
744    /// Filename pattern
745    pub filename_pattern: Option<String>,
746}
747
748/// Confirm sync changes request
749#[derive(Debug, Clone, Serialize, Deserialize)]
750pub struct ConfirmSyncChangesRequest {
751    /// Workspace ID
752    pub workspace_id: String,
753    /// Changes to confirm
754    pub changes: Vec<SyncChange>,
755    /// Whether to apply all changes
756    pub apply_all: bool,
757}
758
759/// Autocomplete suggestion
760#[derive(Debug, Clone, Serialize, Deserialize)]
761pub struct AutocompleteSuggestion {
762    /// Suggestion text
763    pub text: String,
764    /// Suggestion type (e.g., "variable", "template")
765    pub kind: String,
766    /// Optional description
767    pub description: Option<String>,
768}
769
770/// Autocomplete request
771#[derive(Debug, Clone, Serialize, Deserialize)]
772pub struct AutocompleteRequest {
773    /// Current input text
774    pub input: String,
775    /// Cursor position in the text
776    pub cursor_position: usize,
777    /// Context type (e.g., "header", "body", "url")
778    pub context: Option<String>,
779}
780
781/// Autocomplete response
782#[derive(Debug, Clone, Serialize, Deserialize)]
783pub struct AutocompleteResponse {
784    /// List of suggestions
785    pub suggestions: Vec<AutocompleteSuggestion>,
786    /// Start position of the token being completed
787    pub start_position: usize,
788    /// End position of the token being completed
789    pub end_position: usize,
790}
791
792/// Time series data point
793#[derive(Debug, Clone, Serialize, Deserialize)]
794pub struct TimeSeriesPoint {
795    /// Timestamp
796    pub timestamp: chrono::DateTime<chrono::Utc>,
797    /// Value
798    pub value: f64,
799}
800
801/// Time series data
802#[derive(Debug, Clone, Serialize, Deserialize)]
803pub struct TimeSeriesData {
804    /// Data points
805    pub points: Vec<TimeSeriesPoint>,
806    /// Metric name
807    pub metric: String,
808}
809
810/// Restart status
811#[derive(Debug, Clone, Serialize, Deserialize)]
812pub struct RestartStatus {
813    /// Whether restart is in progress
814    pub restarting: bool,
815    /// Restart progress (0.0 to 1.0)
816    pub progress: f64,
817    /// Status message
818    pub message: String,
819}
820
821/// Smoke test result
822#[derive(Debug, Clone, Serialize, Deserialize)]
823pub struct SmokeTestResult {
824    /// Test name
825    pub test_name: String,
826    /// Whether test passed
827    pub passed: bool,
828    /// Response time in milliseconds
829    pub response_time_ms: Option<u64>,
830    /// Error message (if failed)
831    pub error_message: Option<String>,
832}
833
834/// Smoke test context
835#[derive(Debug, Clone, Serialize, Deserialize)]
836pub struct SmokeTestContext {
837    /// Test suite name
838    pub suite_name: String,
839    /// Total tests
840    pub total_tests: usize,
841    /// Passed tests
842    pub passed_tests: usize,
843    /// Failed tests
844    pub failed_tests: usize,
845    /// Start time
846    pub start_time: chrono::DateTime<chrono::Utc>,
847    /// End time
848    pub end_time: Option<chrono::DateTime<chrono::Utc>>,
849}
850
851/// Configuration state
852#[derive(Debug, Clone, Serialize, Deserialize)]
853pub struct ConfigurationState {
854    /// Whether configuration is valid
855    pub valid: bool,
856    /// Configuration errors
857    pub errors: Vec<String>,
858    /// Configuration warnings
859    pub warnings: Vec<String>,
860}
861
862/// Import history entry
863#[derive(Debug, Clone, Serialize, Deserialize)]
864pub struct ImportHistoryEntry {
865    /// Entry ID
866    pub id: String,
867    /// Import format
868    pub format: String,
869    /// Import timestamp
870    pub timestamp: chrono::DateTime<chrono::Utc>,
871    /// Number of routes imported
872    pub routes_count: usize,
873    /// Number of variables imported
874    pub variables_count: usize,
875    /// Number of warnings
876    pub warnings_count: usize,
877    /// Whether import was successful
878    pub success: bool,
879    /// Filename (if applicable)
880    pub filename: Option<String>,
881    /// Environment name
882    pub environment: Option<String>,
883    /// Base URL
884    pub base_url: Option<String>,
885    /// Error message (if failed)
886    pub error_message: Option<String>,
887}
888
889/// Fixture information
890#[derive(Debug, Clone, Serialize, Deserialize)]
891pub struct FixtureInfo {
892    /// Fixture ID
893    pub id: String,
894    /// Fixture name
895    pub name: String,
896    /// Fixture path
897    pub path: String,
898    /// File size in bytes
899    pub size_bytes: u64,
900    /// Last modified timestamp
901    pub last_modified: chrono::DateTime<chrono::Utc>,
902    /// Content type
903    pub content_type: Option<String>,
904}
905
906/// Fixture delete request
907#[derive(Debug, Clone, Serialize, Deserialize)]
908pub struct FixtureDeleteRequest {
909    /// Fixture ID to delete
910    pub fixture_id: String,
911}
912
913/// Fixture bulk delete request
914#[derive(Debug, Clone, Serialize, Deserialize)]
915pub struct FixtureBulkDeleteRequest {
916    /// Fixture IDs to delete
917    pub fixture_ids: Vec<String>,
918}
919
920/// Fixture bulk delete result
921#[derive(Debug, Clone, Serialize, Deserialize)]
922pub struct FixtureBulkDeleteResult {
923    /// Successfully deleted fixture IDs
924    pub deleted: Vec<String>,
925    /// Failed deletions with error messages
926    pub failed: HashMap<String, String>,
927}
928
929/// Import route
930#[derive(Debug, Clone, Serialize, Deserialize)]
931pub struct ImportRoute {
932    /// HTTP method
933    pub method: String,
934    /// Route path
935    pub path: String,
936    /// Request headers
937    pub headers: HashMap<String, String>,
938    /// Request body
939    pub body: Option<String>,
940    /// Expected response
941    pub response: ImportResponse,
942}
943
944/// Import response
945#[derive(Debug, Clone, Serialize, Deserialize)]
946pub struct ImportResponse {
947    /// HTTP status code
948    pub status: u16,
949    /// Response headers
950    pub headers: HashMap<String, String>,
951    /// Response body
952    pub body: serde_json::Value,
953}
954
955/// Import result
956#[derive(Debug, Clone, Serialize, Deserialize)]
957pub struct ImportResult {
958    /// Imported routes
959    pub routes: Vec<ImportRoute>,
960    /// Import warnings
961    pub warnings: Vec<String>,
962    /// Import errors
963    pub errors: Vec<String>,
964}
965
966/// Insomnia import result
967#[derive(Debug, Clone, Serialize, Deserialize)]
968pub struct InsomniaImportResult {
969    /// Imported routes
970    pub routes: Vec<ImportRoute>,
971    /// Environment variables
972    pub variables: HashMap<String, String>,
973    /// Import warnings
974    pub warnings: Vec<String>,
975}
976
977/// Server information
978#[derive(Debug, Clone, Serialize, Deserialize)]
979pub struct ServerInfo {
980    /// Server version
981    pub version: String,
982    /// Build timestamp
983    pub build_time: String,
984    /// Git SHA
985    pub git_sha: String,
986    /// HTTP server address (optional)
987    pub http_server: Option<String>,
988    /// WebSocket server address (optional)
989    pub ws_server: Option<String>,
990    /// gRPC server address (optional)
991    pub grpc_server: Option<String>,
992    /// GraphQL server address (optional)
993    pub graphql_server: Option<String>,
994    /// Whether API endpoints are enabled
995    pub api_enabled: bool,
996    /// Admin server port
997    pub admin_port: u16,
998}
999
1000#[cfg(test)]
1001mod tests {
1002    use super::*;
1003
1004    // ==================== ApiResponse Tests ====================
1005
1006    #[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    // ==================== HealthCheck Tests ====================
1034
1035    #[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    // ==================== LogFilter Tests ====================
1064
1065    #[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    // ==================== ServerStatus Tests ====================
1090
1091    #[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    // ==================== RouteInfo Tests ====================
1126
1127    #[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    // ==================== RequestLog Tests ====================
1164
1165    #[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    // ==================== SystemInfo Tests ====================
1186
1187    #[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    // ==================== LatencyProfile Tests ====================
1204
1205    #[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    // ==================== FaultConfig Tests ====================
1219
1220    #[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    // ==================== ProxyConfig Tests ====================
1235
1236    #[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    // ==================== BandwidthConfig Tests ====================
1250
1251    #[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    // ==================== BurstLossConfig Tests ====================
1265
1266    #[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    // ==================== SimpleMetricsData Tests ====================
1282
1283    #[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    // ==================== ConfigUpdate Tests ====================
1297
1298    #[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    // ==================== RouteUpdate Tests ====================
1309
1310    #[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    // ==================== ValidationSettings Tests ====================
1324
1325    #[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    // ==================== LogEntry Tests ====================
1339
1340    #[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    // ==================== WorkspaceSummary Tests ====================
1356
1357    #[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    // ==================== FolderSummary Tests ====================
1375
1376    #[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    // ==================== RequestSummary Tests ====================
1394
1395    #[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    // ==================== CreateWorkspaceRequest Tests ====================
1413
1414    #[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    // ==================== CreateFolderRequest Tests ====================
1425
1426    #[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    // ==================== SyncConfig Tests ====================
1439
1440    #[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    // ==================== EnvironmentColor Tests ====================
1462
1463    #[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    // ==================== EnvironmentSummary Tests ====================
1486
1487    #[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    // ==================== AutocompleteSuggestion Tests ====================
1510
1511    #[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    // ==================== TimeSeriesPoint Tests ====================
1523
1524    #[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    // ==================== RestartStatus Tests ====================
1535
1536    #[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    // ==================== SmokeTestResult Tests ====================
1549
1550    #[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    // ==================== ConfigurationState Tests ====================
1577
1578    #[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    // ==================== ImportHistoryEntry Tests ====================
1592
1593    #[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    // ==================== FixtureInfo Tests ====================
1614
1615    #[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    // ==================== ImportRoute Tests ====================
1631
1632    #[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    // ==================== ServerInfo Tests ====================
1651
1652    #[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    // ==================== Serialization Tests ====================
1671
1672    #[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}