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