Skip to main content

mockforge_tui/api/
models.rs

1//! Standalone deserialization types mirroring the admin API JSON shapes.
2//! Intentionally duplicated from `mockforge-ui` for decoupling — this crate
3//! has zero internal `mockforge-*` dependencies.
4
5use std::collections::HashMap;
6
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9
10// ── API envelope ─────────────────────────────────────────────────────
11
12/// Generic API response wrapper used by all admin endpoints.
13/// The server may include a `timestamp` field which we ignore.
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct ApiResponse<T> {
16    pub success: bool,
17    pub data: Option<T>,
18    pub error: Option<String>,
19}
20
21// ── Endpoint wrapper types ──────────────────────────────────────────
22// Some endpoints return data in a named wrapper instead of a raw array.
23
24#[derive(Debug, Clone, Deserialize)]
25pub struct RoutesWrapper {
26    #[serde(default)]
27    pub routes: Vec<RouteInfo>,
28}
29
30#[derive(Debug, Clone, Deserialize)]
31pub struct PluginsWrapper {
32    #[serde(default)]
33    pub plugins: Vec<PluginInfo>,
34    #[serde(default)]
35    pub total: u64,
36}
37
38#[derive(Debug, Clone, Deserialize)]
39pub struct ContractDiffWrapper {
40    #[serde(default)]
41    pub captures: Vec<ContractDiffCapture>,
42}
43
44// ── Dashboard ────────────────────────────────────────────────────────
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct DashboardData {
48    pub server_info: ServerInfo,
49    pub system_info: DashboardSystemInfo,
50    pub metrics: SimpleMetrics,
51    pub servers: Vec<ServerStatus>,
52    pub recent_logs: Vec<RequestLog>,
53    pub system: SystemInfo,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct ServerInfo {
58    #[serde(default)]
59    pub version: String,
60    #[serde(default)]
61    pub build_time: String,
62    #[serde(default)]
63    pub git_sha: String,
64    pub http_server: Option<String>,
65    pub ws_server: Option<String>,
66    pub grpc_server: Option<String>,
67    pub graphql_server: Option<String>,
68    #[serde(default)]
69    pub api_enabled: bool,
70    #[serde(default)]
71    pub admin_port: u16,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct DashboardSystemInfo {
76    #[serde(default)]
77    pub os: String,
78    #[serde(default)]
79    pub arch: String,
80    #[serde(default)]
81    pub uptime: u64,
82    #[serde(default)]
83    pub memory_usage: u64,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct SimpleMetrics {
88    #[serde(default)]
89    pub total_requests: u64,
90    #[serde(default)]
91    pub active_requests: u64,
92    #[serde(default)]
93    pub average_response_time: f64,
94    #[serde(default)]
95    pub error_rate: f64,
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct ServerStatus {
100    pub server_type: String,
101    pub address: Option<String>,
102    #[serde(default)]
103    pub running: bool,
104    pub start_time: Option<DateTime<Utc>>,
105    pub uptime_seconds: Option<u64>,
106    #[serde(default)]
107    pub active_connections: u64,
108    #[serde(default)]
109    pub total_requests: u64,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct SystemInfo {
114    #[serde(default)]
115    pub version: String,
116    #[serde(default)]
117    pub uptime_seconds: u64,
118    #[serde(default)]
119    pub memory_usage_mb: u64,
120    #[serde(default)]
121    pub cpu_usage_percent: f64,
122    #[serde(default)]
123    pub active_threads: usize,
124    #[serde(default)]
125    pub total_routes: usize,
126    #[serde(default)]
127    pub total_fixtures: usize,
128    #[serde(default)]
129    pub peak_memory_usage_mb: u64,
130    #[serde(default)]
131    pub peak_cpu_usage_percent: f64,
132    #[serde(default)]
133    pub peak_error_rate: f64,
134    #[serde(default)]
135    pub peaks_since: Option<DateTime<Utc>>,
136    /// Successful (200..=399) responses per second, sampled over the last
137    /// metrics interval. "API transactions/sec" in load-testing terms.
138    #[serde(default)]
139    pub tps: f64,
140    #[serde(default)]
141    pub peak_tps: f64,
142    /// 200-OK responses per second over the last metrics interval.
143    #[serde(default)]
144    pub rps_200: f64,
145    #[serde(default)]
146    pub peak_rps_200: f64,
147    /// Accepted TCP connections per second (plain HTTP path; reads 0 for
148    /// HTTPS-only setups).
149    #[serde(default)]
150    pub cps: f64,
151    #[serde(default)]
152    pub peak_cps: f64,
153}
154
155// ── Request logs ─────────────────────────────────────────────────────
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct RequestLog {
159    pub id: String,
160    pub timestamp: DateTime<Utc>,
161    pub method: String,
162    pub path: String,
163    pub status_code: u16,
164    #[serde(default)]
165    pub response_time_ms: u64,
166    pub client_ip: Option<String>,
167    pub user_agent: Option<String>,
168    #[serde(default)]
169    pub headers: HashMap<String, String>,
170    #[serde(default)]
171    pub response_size_bytes: u64,
172    pub error_message: Option<String>,
173}
174
175// ── Routes ───────────────────────────────────────────────────────────
176
177#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct RouteInfo {
179    pub method: Option<String>,
180    pub path: String,
181    #[serde(default)]
182    pub priority: i32,
183    #[serde(default)]
184    pub has_fixtures: bool,
185    pub latency_ms: Option<u64>,
186    #[serde(default)]
187    pub request_count: u64,
188    pub last_request: Option<DateTime<Utc>>,
189    #[serde(default)]
190    pub error_count: u64,
191}
192
193// ── Metrics ──────────────────────────────────────────────────────────
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct MetricsData {
197    #[serde(default)]
198    pub requests_by_endpoint: HashMap<String, u64>,
199    #[serde(default)]
200    pub response_time_percentiles: HashMap<String, u64>,
201    pub endpoint_percentiles: Option<HashMap<String, HashMap<String, u64>>>,
202    pub latency_over_time: Option<Vec<(DateTime<Utc>, u64)>>,
203    #[serde(default)]
204    pub error_rate_by_endpoint: HashMap<String, f64>,
205    #[serde(default)]
206    pub memory_usage_over_time: Vec<(DateTime<Utc>, u64)>,
207    #[serde(default)]
208    pub cpu_usage_over_time: Vec<(DateTime<Utc>, f64)>,
209}
210
211// ── Health ───────────────────────────────────────────────────────────
212
213#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct HealthCheck {
215    pub status: String,
216    #[serde(default)]
217    pub services: HashMap<String, String>,
218    pub last_check: Option<DateTime<Utc>>,
219    #[serde(default)]
220    pub issues: Vec<String>,
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize)]
224pub struct HealthProbe {
225    pub status: String,
226    #[serde(default)]
227    pub checks: HashMap<String, serde_json::Value>,
228}
229
230// ── Config ───────────────────────────────────────────────────────────
231
232#[derive(Debug, Clone, Default, Serialize, Deserialize)]
233pub struct ConfigState {
234    #[serde(default)]
235    pub latency: LatencyConfig,
236    #[serde(default)]
237    pub faults: FaultConfig,
238    #[serde(default)]
239    pub proxy: ProxyConfig,
240    #[serde(default)]
241    pub traffic_shaping: TrafficShapingConfig,
242    #[serde(default)]
243    pub validation: ValidationConfig,
244}
245
246#[derive(Debug, Clone, Default, Serialize, Deserialize)]
247pub struct LatencyConfig {
248    #[serde(default)]
249    pub enabled: bool,
250    #[serde(default)]
251    pub base_ms: u64,
252    #[serde(default)]
253    pub jitter_ms: u64,
254    #[serde(default)]
255    pub tag_overrides: HashMap<String, u64>,
256}
257
258#[derive(Debug, Clone, Default, Serialize, Deserialize)]
259pub struct FaultConfig {
260    #[serde(default)]
261    pub enabled: bool,
262    #[serde(default)]
263    pub failure_rate: f64,
264    #[serde(default)]
265    pub status_codes: Vec<u16>,
266}
267
268#[derive(Debug, Clone, Default, Serialize, Deserialize)]
269pub struct ProxyConfig {
270    #[serde(default)]
271    pub enabled: bool,
272    pub upstream_url: Option<String>,
273    #[serde(default)]
274    pub timeout_seconds: u64,
275}
276
277#[derive(Debug, Clone, Default, Serialize, Deserialize)]
278pub struct TrafficShapingConfig {
279    #[serde(default)]
280    pub enabled: bool,
281    #[serde(default)]
282    pub bandwidth: serde_json::Value,
283    #[serde(default)]
284    pub burst_loss: serde_json::Value,
285}
286
287#[derive(Debug, Clone, Default, Serialize, Deserialize)]
288pub struct ValidationConfig {
289    #[serde(default)]
290    pub mode: String,
291    #[serde(default)]
292    pub aggregate_errors: bool,
293    #[serde(default)]
294    pub validate_responses: bool,
295    #[serde(default)]
296    pub overrides: serde_json::Value,
297}
298
299// ── Plugins ──────────────────────────────────────────────────────────
300
301#[derive(Debug, Clone, Serialize, Deserialize)]
302pub struct PluginInfo {
303    pub id: String,
304    pub name: String,
305    #[serde(default)]
306    pub version: String,
307    #[serde(default)]
308    pub types: Vec<String>,
309    #[serde(default)]
310    pub status: String,
311    #[serde(default)]
312    pub healthy: bool,
313    #[serde(default)]
314    pub description: String,
315    #[serde(default)]
316    pub author: String,
317}
318
319// ── Fixtures ─────────────────────────────────────────────────────────
320
321#[derive(Debug, Clone, Serialize, Deserialize)]
322pub struct FixtureInfo {
323    pub id: String,
324    #[serde(default)]
325    pub protocol: String,
326    #[serde(default)]
327    pub method: String,
328    #[serde(default)]
329    pub path: String,
330    pub saved_at: Option<DateTime<Utc>>,
331    #[serde(default)]
332    pub file_size: u64,
333    #[serde(default)]
334    pub file_path: String,
335    #[serde(default)]
336    pub fingerprint: String,
337    #[serde(default)]
338    pub metadata: serde_json::Value,
339}
340
341// ── Smoke tests ──────────────────────────────────────────────────────
342
343#[derive(Debug, Clone, Serialize, Deserialize)]
344pub struct SmokeTestResult {
345    pub id: String,
346    #[serde(default)]
347    pub name: String,
348    #[serde(default)]
349    pub method: String,
350    #[serde(default)]
351    pub path: String,
352    #[serde(default)]
353    pub description: String,
354    pub last_run: Option<DateTime<Utc>>,
355    #[serde(default)]
356    pub status: String,
357    pub response_time_ms: Option<u64>,
358    pub error_message: Option<String>,
359    pub status_code: Option<u16>,
360    pub duration_seconds: Option<f64>,
361}
362
363// ── Workspaces ───────────────────────────────────────────────────────
364
365#[derive(Debug, Clone, Serialize, Deserialize)]
366pub struct WorkspaceInfo {
367    pub id: String,
368    #[serde(default)]
369    pub name: String,
370    #[serde(default)]
371    pub description: String,
372    #[serde(default)]
373    pub active: bool,
374    pub created_at: Option<DateTime<Utc>>,
375    #[serde(default)]
376    pub environments: Vec<String>,
377}
378
379// ── Chaos ────────────────────────────────────────────────────────────
380
381#[derive(Debug, Clone, Serialize, Deserialize)]
382pub struct ChaosStatus {
383    #[serde(default)]
384    pub enabled: bool,
385    pub active_scenario: Option<String>,
386    #[serde(default)]
387    pub settings: serde_json::Value,
388}
389
390// ── Time Travel ──────────────────────────────────────────────────────
391
392#[derive(Debug, Clone, Serialize, Deserialize)]
393pub struct TimeTravelStatus {
394    #[serde(default)]
395    pub enabled: bool,
396    pub current_time: Option<DateTime<Utc>>,
397    /// Server sends `scale_factor`, older API sends `time_scale`
398    #[serde(default, alias = "time_scale")]
399    pub scale_factor: Option<f64>,
400    pub real_time: Option<DateTime<Utc>>,
401    #[serde(default)]
402    pub scheduled_responses: u64,
403}
404
405// ── Chains ───────────────────────────────────────────────────────────
406
407#[derive(Debug, Clone, Serialize, Deserialize)]
408pub struct ChainInfo {
409    pub id: String,
410    #[serde(default)]
411    pub name: String,
412    #[serde(default)]
413    pub steps: Vec<serde_json::Value>,
414    #[serde(default)]
415    pub description: String,
416}
417
418// ── Audit ────────────────────────────────────────────────────────────
419
420#[derive(Debug, Clone, Serialize, Deserialize)]
421pub struct AuditEntry {
422    pub id: String,
423    pub timestamp: Option<DateTime<Utc>>,
424    #[serde(default)]
425    pub action: String,
426    #[serde(default)]
427    pub user: String,
428    #[serde(default)]
429    pub details: serde_json::Value,
430}
431
432// ── Analytics ────────────────────────────────────────────────────────
433
434#[derive(Debug, Clone, Serialize, Deserialize)]
435pub struct AnalyticsSummary {
436    /// Server sends `request_rate` or `total_requests`
437    #[serde(default, alias = "total_requests")]
438    pub request_rate: f64,
439    #[serde(default)]
440    pub unique_endpoints: u64,
441    /// Server sends `error_rate_percent` or `error_rate`
442    #[serde(default, alias = "error_rate")]
443    pub error_rate_percent: f64,
444    #[serde(default, alias = "avg_response_time")]
445    pub p95_latency_ms: f64,
446    #[serde(default)]
447    pub active_connections: f64,
448    #[serde(default)]
449    pub top_endpoints: Vec<EndpointStat>,
450    /// Server may include a timestamp inside the data
451    #[serde(default)]
452    pub timestamp: Option<String>,
453}
454
455#[derive(Debug, Clone, Serialize, Deserialize)]
456pub struct EndpointStat {
457    #[serde(default)]
458    pub endpoint: String,
459    #[serde(default)]
460    pub count: u64,
461    #[serde(default)]
462    pub avg_time: f64,
463}
464
465// ── Recorder ─────────────────────────────────────────────────────────
466
467#[derive(Debug, Clone, Serialize, Deserialize)]
468pub struct RecorderStatus {
469    #[serde(default)]
470    pub recording: bool,
471    #[serde(default)]
472    pub recorded_count: u64,
473}
474
475// ── Verification ─────────────────────────────────────────────────────
476
477#[derive(Debug, Clone, Serialize, Deserialize)]
478pub struct VerificationResult {
479    #[serde(default)]
480    pub matched: bool,
481    #[serde(default)]
482    pub count: u64,
483    #[serde(default)]
484    pub details: serde_json::Value,
485}
486
487// ── World State ──────────────────────────────────────────────────────
488
489#[derive(Debug, Clone, Serialize, Deserialize)]
490pub struct WorldStateEntry {
491    #[serde(default)]
492    pub key: String,
493    #[serde(default)]
494    pub value: serde_json::Value,
495    pub updated_at: Option<DateTime<Utc>>,
496}
497
498// ── Federation ───────────────────────────────────────────────────────
499
500#[derive(Debug, Clone, Serialize, Deserialize)]
501pub struct FederationPeer {
502    #[serde(default)]
503    pub id: String,
504    #[serde(default)]
505    pub url: String,
506    #[serde(default)]
507    pub status: String,
508    pub last_sync: Option<DateTime<Utc>>,
509}
510
511// ── Contract Diff ────────────────────────────────────────────────────
512
513#[derive(Debug, Clone, Serialize, Deserialize)]
514pub struct ContractDiffCapture {
515    pub id: String,
516    #[serde(default)]
517    pub path: String,
518    #[serde(default)]
519    pub method: String,
520    #[serde(default)]
521    pub diff_status: String,
522    pub captured_at: Option<DateTime<Utc>>,
523    /// Whether the capture has been analyzed
524    #[serde(default)]
525    pub analyzed: bool,
526    #[serde(default)]
527    pub source: String,
528    #[serde(default)]
529    pub headers: HashMap<String, String>,
530    #[serde(default)]
531    pub query_params: HashMap<String, String>,
532}
533
534#[cfg(test)]
535mod tests {
536    use super::*;
537
538    #[test]
539    fn deserialize_api_response_success() {
540        let json = r#"{
541            "success": true,
542            "data": "hello",
543            "error": null
544        }"#;
545        let resp: ApiResponse<String> = serde_json::from_str(json).unwrap();
546        assert!(resp.success);
547        assert_eq!(resp.data.unwrap(), "hello");
548        assert!(resp.error.is_none());
549    }
550
551    #[test]
552    fn deserialize_api_response_with_timestamp() {
553        // Server includes a timestamp field that TUI ignores
554        let json = r#"{
555            "success": true,
556            "data": "hello",
557            "error": null,
558            "timestamp": "2026-03-07T05:16:29.407667010Z"
559        }"#;
560        let resp: ApiResponse<String> = serde_json::from_str(json).unwrap();
561        assert!(resp.success);
562        assert_eq!(resp.data.unwrap(), "hello");
563    }
564
565    #[test]
566    fn deserialize_api_response_error() {
567        let json = r#"{
568            "success": false,
569            "data": null,
570            "error": "something went wrong"
571        }"#;
572        let resp: ApiResponse<String> = serde_json::from_str(json).unwrap();
573        assert!(!resp.success);
574        assert!(resp.data.is_none());
575        assert_eq!(resp.error.unwrap(), "something went wrong");
576    }
577
578    #[test]
579    fn deserialize_server_info_minimal() {
580        let json = r#"{
581            "version": "0.3.31"
582        }"#;
583        let info: ServerInfo = serde_json::from_str(json).unwrap();
584        assert_eq!(info.version, "0.3.31");
585        assert!(info.build_time.is_empty());
586        assert!(info.git_sha.is_empty());
587        assert!(info.http_server.is_none());
588        assert!(!info.api_enabled);
589        assert_eq!(info.admin_port, 0);
590    }
591
592    #[test]
593    fn deserialize_server_info_full() {
594        let json = r#"{
595            "version": "0.3.31",
596            "build_time": "2025-01-15T10:00:00Z",
597            "git_sha": "abc123",
598            "http_server": "http://0.0.0.0:3000",
599            "ws_server": "ws://0.0.0.0:3001",
600            "grpc_server": null,
601            "graphql_server": null,
602            "api_enabled": true,
603            "admin_port": 9080
604        }"#;
605        let info: ServerInfo = serde_json::from_str(json).unwrap();
606        assert_eq!(info.version, "0.3.31");
607        assert_eq!(info.http_server.unwrap(), "http://0.0.0.0:3000");
608        assert!(info.api_enabled);
609        assert_eq!(info.admin_port, 9080);
610    }
611
612    #[test]
613    fn deserialize_request_log() {
614        let json = r#"{
615            "id": "req-001",
616            "timestamp": "2025-06-15T12:00:00Z",
617            "method": "GET",
618            "path": "/api/users",
619            "status_code": 200,
620            "response_time_ms": 42,
621            "client_ip": "127.0.0.1",
622            "user_agent": "curl/8.0",
623            "headers": {"content-type": "application/json"},
624            "response_size_bytes": 1024,
625            "error_message": null
626        }"#;
627        let log: RequestLog = serde_json::from_str(json).unwrap();
628        assert_eq!(log.id, "req-001");
629        assert_eq!(log.method, "GET");
630        assert_eq!(log.path, "/api/users");
631        assert_eq!(log.status_code, 200);
632        assert_eq!(log.response_time_ms, 42);
633        assert_eq!(log.client_ip.unwrap(), "127.0.0.1");
634        assert_eq!(log.headers.get("content-type").unwrap(), "application/json");
635    }
636
637    #[test]
638    fn deserialize_request_log_minimal() {
639        let json = r#"{
640            "id": "req-002",
641            "timestamp": "2025-06-15T12:00:00Z",
642            "method": "POST",
643            "path": "/api/data",
644            "status_code": 500
645        }"#;
646        let log: RequestLog = serde_json::from_str(json).unwrap();
647        assert_eq!(log.id, "req-002");
648        assert_eq!(log.status_code, 500);
649        assert_eq!(log.response_time_ms, 0);
650        assert!(log.client_ip.is_none());
651        assert!(log.headers.is_empty());
652    }
653
654    #[test]
655    fn deserialize_route_info() {
656        let json = r#"{
657            "method": "GET",
658            "path": "/api/users/{id}",
659            "priority": 10,
660            "has_fixtures": true,
661            "latency_ms": 50,
662            "request_count": 100,
663            "last_request": "2025-06-15T12:30:00Z",
664            "error_count": 2
665        }"#;
666        let route: RouteInfo = serde_json::from_str(json).unwrap();
667        assert_eq!(route.method.unwrap(), "GET");
668        assert_eq!(route.path, "/api/users/{id}");
669        assert_eq!(route.priority, 10);
670        assert!(route.has_fixtures);
671        assert_eq!(route.latency_ms.unwrap(), 50);
672        assert_eq!(route.request_count, 100);
673        assert_eq!(route.error_count, 2);
674    }
675
676    #[test]
677    fn deserialize_route_info_minimal() {
678        let json = r#"{
679            "path": "/health"
680        }"#;
681        let route: RouteInfo = serde_json::from_str(json).unwrap();
682        assert!(route.method.is_none());
683        assert_eq!(route.path, "/health");
684        assert_eq!(route.priority, 0);
685        assert!(!route.has_fixtures);
686        assert!(route.latency_ms.is_none());
687    }
688
689    #[test]
690    fn deserialize_health_check() {
691        let json = r#"{
692            "status": "healthy",
693            "services": {"http": "up", "grpc": "up"},
694            "last_check": "2025-06-15T12:00:00Z",
695            "issues": []
696        }"#;
697        let health: HealthCheck = serde_json::from_str(json).unwrap();
698        assert_eq!(health.status, "healthy");
699        assert_eq!(health.services.len(), 2);
700        assert!(health.issues.is_empty());
701    }
702
703    #[test]
704    fn deserialize_health_check_with_issues() {
705        let json = r#"{
706            "status": "degraded",
707            "issues": ["kafka disconnected", "high latency"]
708        }"#;
709        let health: HealthCheck = serde_json::from_str(json).unwrap();
710        assert_eq!(health.status, "degraded");
711        assert_eq!(health.issues.len(), 2);
712        assert_eq!(health.issues[0], "kafka disconnected");
713    }
714
715    #[test]
716    fn deserialize_plugin_info() {
717        let json = r#"{
718            "id": "plugin-001",
719            "name": "response-graphql",
720            "version": "1.0.0",
721            "types": ["response", "graphql"],
722            "status": "active",
723            "healthy": true,
724            "description": "GraphQL response plugin",
725            "author": "MockForge"
726        }"#;
727        let plugin: PluginInfo = serde_json::from_str(json).unwrap();
728        assert_eq!(plugin.id, "plugin-001");
729        assert_eq!(plugin.name, "response-graphql");
730        assert!(plugin.healthy);
731        assert_eq!(plugin.types.len(), 2);
732    }
733
734    #[test]
735    fn deserialize_config_state() {
736        let json = r#"{
737            "latency": {
738                "enabled": true,
739                "base_ms": 100,
740                "jitter_ms": 20,
741                "tag_overrides": {"fast": 10}
742            },
743            "faults": {
744                "enabled": false,
745                "failure_rate": 0.05,
746                "status_codes": [500, 503]
747            },
748            "proxy": {
749                "enabled": false,
750                "upstream_url": null,
751                "timeout_seconds": 30
752            },
753            "traffic_shaping": {
754                "enabled": false,
755                "bandwidth": {},
756                "burst_loss": {}
757            },
758            "validation": {
759                "mode": "strict",
760                "aggregate_errors": true,
761                "validate_responses": false,
762                "overrides": {}
763            }
764        }"#;
765        let config: ConfigState = serde_json::from_str(json).unwrap();
766        assert!(config.latency.enabled);
767        assert_eq!(config.latency.base_ms, 100);
768        assert_eq!(config.latency.jitter_ms, 20);
769        assert!(!config.faults.enabled);
770        assert_eq!(config.faults.status_codes, vec![500, 503]);
771        assert!(!config.proxy.enabled);
772        assert_eq!(config.validation.mode, "strict");
773    }
774
775    #[test]
776    fn deserialize_fixture_info() {
777        let json = r#"{
778            "id": "fix-001",
779            "protocol": "http",
780            "method": "GET",
781            "path": "/api/users",
782            "saved_at": "2025-06-15T10:00:00Z",
783            "file_size": 2048,
784            "file_path": "/fixtures/users.json",
785            "fingerprint": "abc123def",
786            "metadata": {"tag": "v1"}
787        }"#;
788        let fixture: FixtureInfo = serde_json::from_str(json).unwrap();
789        assert_eq!(fixture.id, "fix-001");
790        assert_eq!(fixture.protocol, "http");
791        assert_eq!(fixture.file_size, 2048);
792    }
793
794    #[test]
795    fn deserialize_smoke_test_result() {
796        let json = r#"{
797            "id": "smoke-001",
798            "name": "Health endpoint",
799            "method": "GET",
800            "path": "/health",
801            "description": "Verify health endpoint returns 200",
802            "last_run": "2025-06-15T12:00:00Z",
803            "status": "passed",
804            "response_time_ms": 15,
805            "status_code": 200
806        }"#;
807        let result: SmokeTestResult = serde_json::from_str(json).unwrap();
808        assert_eq!(result.id, "smoke-001");
809        assert_eq!(result.status, "passed");
810        assert_eq!(result.status_code.unwrap(), 200);
811    }
812
813    #[test]
814    fn deserialize_workspace_info() {
815        let json = r#"{
816            "id": "ws-001",
817            "name": "default",
818            "description": "Default workspace",
819            "active": true,
820            "created_at": "2025-01-01T00:00:00Z",
821            "environments": ["dev", "staging"]
822        }"#;
823        let ws: WorkspaceInfo = serde_json::from_str(json).unwrap();
824        assert_eq!(ws.id, "ws-001");
825        assert!(ws.active);
826        assert_eq!(ws.environments, vec!["dev", "staging"]);
827    }
828
829    #[test]
830    fn deserialize_chaos_status() {
831        let json = r#"{
832            "enabled": true,
833            "active_scenario": "network-partition",
834            "settings": {"probability": 0.1}
835        }"#;
836        let chaos: ChaosStatus = serde_json::from_str(json).unwrap();
837        assert!(chaos.enabled);
838        assert_eq!(chaos.active_scenario.unwrap(), "network-partition");
839    }
840
841    #[test]
842    fn deserialize_time_travel_status() {
843        let json = r#"{
844            "enabled": true,
845            "current_time": "2025-01-01T00:00:00Z",
846            "scale_factor": 2.0,
847            "scheduled_responses": 5
848        }"#;
849        let tt: TimeTravelStatus = serde_json::from_str(json).unwrap();
850        assert!(tt.enabled);
851        assert!((tt.scale_factor.unwrap() - 2.0).abs() < f64::EPSILON);
852        assert_eq!(tt.scheduled_responses, 5);
853    }
854
855    #[test]
856    fn deserialize_time_travel_status_with_alias() {
857        // Server also sends time_scale (aliased to scale_factor)
858        let json = r#"{
859            "enabled": false,
860            "time_scale": 1.5
861        }"#;
862        let tt: TimeTravelStatus = serde_json::from_str(json).unwrap();
863        assert!(!tt.enabled);
864        assert!((tt.scale_factor.unwrap() - 1.5).abs() < f64::EPSILON);
865    }
866
867    #[test]
868    fn deserialize_chain_info() {
869        let json = r#"{
870            "id": "chain-001",
871            "name": "User flow",
872            "steps": [{"action": "create"}, {"action": "read"}],
873            "description": "End-to-end user CRUD"
874        }"#;
875        let chain: ChainInfo = serde_json::from_str(json).unwrap();
876        assert_eq!(chain.id, "chain-001");
877        assert_eq!(chain.steps.len(), 2);
878    }
879
880    #[test]
881    fn deserialize_audit_entry() {
882        let json = r#"{
883            "id": "audit-001",
884            "timestamp": "2025-06-15T12:00:00Z",
885            "action": "config.update",
886            "user": "admin",
887            "details": {"field": "latency_ms", "old": 50, "new": 100}
888        }"#;
889        let entry: AuditEntry = serde_json::from_str(json).unwrap();
890        assert_eq!(entry.id, "audit-001");
891        assert_eq!(entry.action, "config.update");
892        assert_eq!(entry.user, "admin");
893    }
894
895    #[test]
896    fn deserialize_analytics_summary() {
897        // Match actual server response format
898        let json = r#"{
899            "request_rate": 42.5,
900            "p95_latency_ms": 15.3,
901            "error_rate_percent": 0.5,
902            "active_connections": 12.0
903        }"#;
904        let summary: AnalyticsSummary = serde_json::from_str(json).unwrap();
905        assert!((summary.request_rate - 42.5).abs() < f64::EPSILON);
906        assert!((summary.error_rate_percent - 0.5).abs() < f64::EPSILON);
907    }
908
909    #[test]
910    fn deserialize_analytics_summary_with_aliases() {
911        // Also accept legacy field names
912        let json = r#"{
913            "total_requests": 10000,
914            "unique_endpoints": 25,
915            "error_rate": 0.02,
916            "avg_response_time": 45.5,
917            "top_endpoints": [
918                {"endpoint": "/api/users", "count": 5000, "avg_time": 30.0}
919            ]
920        }"#;
921        let summary: AnalyticsSummary = serde_json::from_str(json).unwrap();
922        assert!((summary.request_rate - 10000.0).abs() < f64::EPSILON);
923        assert_eq!(summary.unique_endpoints, 25);
924        assert_eq!(summary.top_endpoints.len(), 1);
925    }
926
927    #[test]
928    fn deserialize_recorder_status() {
929        let json = r#"{
930            "recording": true,
931            "recorded_count": 42
932        }"#;
933        let recorder: RecorderStatus = serde_json::from_str(json).unwrap();
934        assert!(recorder.recording);
935        assert_eq!(recorder.recorded_count, 42);
936    }
937
938    #[test]
939    fn deserialize_verification_result() {
940        let json = r#"{
941            "matched": true,
942            "count": 3,
943            "details": {"methods": ["GET", "POST"]}
944        }"#;
945        let result: VerificationResult = serde_json::from_str(json).unwrap();
946        assert!(result.matched);
947        assert_eq!(result.count, 3);
948    }
949
950    #[test]
951    fn deserialize_world_state_entry() {
952        let json = r#"{
953            "key": "user.count",
954            "value": 42,
955            "updated_at": "2025-06-15T12:00:00Z"
956        }"#;
957        let entry: WorldStateEntry = serde_json::from_str(json).unwrap();
958        assert_eq!(entry.key, "user.count");
959        assert_eq!(entry.value, serde_json::json!(42));
960    }
961
962    #[test]
963    fn deserialize_federation_peer() {
964        let json = r#"{
965            "id": "peer-001",
966            "url": "http://peer1:9080",
967            "status": "connected",
968            "last_sync": "2025-06-15T12:00:00Z"
969        }"#;
970        let peer: FederationPeer = serde_json::from_str(json).unwrap();
971        assert_eq!(peer.id, "peer-001");
972        assert_eq!(peer.url, "http://peer1:9080");
973        assert_eq!(peer.status, "connected");
974    }
975
976    #[test]
977    fn deserialize_contract_diff_capture() {
978        let json = r#"{
979            "id": "diff-001",
980            "path": "/api/users",
981            "method": "GET",
982            "diff_status": "changed",
983            "captured_at": "2025-06-15T12:00:00Z"
984        }"#;
985        let capture: ContractDiffCapture = serde_json::from_str(json).unwrap();
986        assert_eq!(capture.id, "diff-001");
987        assert_eq!(capture.diff_status, "changed");
988    }
989
990    #[test]
991    fn deserialize_metrics_data() {
992        let json = r#"{
993            "requests_by_endpoint": {"/api/users": 100, "/api/orders": 50},
994            "response_time_percentiles": {"p50": 20, "p99": 200},
995            "error_rate_by_endpoint": {"/api/users": 0.01}
996        }"#;
997        let metrics: MetricsData = serde_json::from_str(json).unwrap();
998        assert_eq!(metrics.requests_by_endpoint.len(), 2);
999        assert_eq!(*metrics.requests_by_endpoint.get("/api/users").unwrap(), 100);
1000        assert_eq!(metrics.response_time_percentiles.len(), 2);
1001    }
1002
1003    #[test]
1004    fn deserialize_system_info() {
1005        let json = r#"{
1006            "version": "0.3.31",
1007            "uptime_seconds": 3600,
1008            "memory_usage_mb": 128,
1009            "cpu_usage_percent": 15.5,
1010            "active_threads": 8,
1011            "total_routes": 42,
1012            "total_fixtures": 10
1013        }"#;
1014        let sys: SystemInfo = serde_json::from_str(json).unwrap();
1015        assert_eq!(sys.version, "0.3.31");
1016        assert_eq!(sys.uptime_seconds, 3600);
1017        assert_eq!(sys.total_routes, 42);
1018    }
1019
1020    #[test]
1021    fn deserialize_health_probe() {
1022        let json = r#"{
1023            "status": "ok",
1024            "checks": {"db": true, "redis": "connected"}
1025        }"#;
1026        let probe: HealthProbe = serde_json::from_str(json).unwrap();
1027        assert_eq!(probe.status, "ok");
1028        assert_eq!(probe.checks.len(), 2);
1029    }
1030
1031    #[test]
1032    fn roundtrip_serialize_deserialize() {
1033        let original = RecorderStatus {
1034            recording: true,
1035            recorded_count: 99,
1036        };
1037        let json = serde_json::to_string(&original).unwrap();
1038        let deserialized: RecorderStatus = serde_json::from_str(&json).unwrap();
1039        assert_eq!(deserialized.recording, original.recording);
1040        assert_eq!(deserialized.recorded_count, original.recorded_count);
1041    }
1042
1043    #[test]
1044    fn api_response_with_complex_data() {
1045        let json = r#"{
1046            "success": true,
1047            "data": [
1048                {"method": "GET", "path": "/users/{id}", "priority": 1, "has_fixtures": false, "request_count": 0, "error_count": 0},
1049                {"method": "POST", "path": "/users", "priority": 2, "has_fixtures": true, "request_count": 5, "error_count": 1}
1050            ],
1051            "error": null
1052        }"#;
1053        let resp: ApiResponse<Vec<RouteInfo>> = serde_json::from_str(json).unwrap();
1054        assert!(resp.success);
1055        let routes = resp.data.unwrap();
1056        assert_eq!(routes.len(), 2);
1057        assert_eq!(routes[0].path, "/users/{id}");
1058        assert_eq!(routes[1].request_count, 5);
1059    }
1060}