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