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