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