leptos_sync_core/devtools/
mod.rs

1//! Developer Tools for debugging and monitoring Leptos-Sync
2//!
3//! This module provides comprehensive debugging and monitoring capabilities
4//! for CRDTs, sync operations, and transport layer.
5
6use crate::crdt::{CRDT, Mergeable, ReplicaId};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::sync::Arc;
10use tokio::sync::RwLock;
11
12/// DevTools configuration
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct DevToolsConfig {
15    /// Enable CRDT inspection
16    pub enable_crdt_inspection: bool,
17    /// Enable sync monitoring
18    pub enable_sync_monitoring: bool,
19    /// Enable transport monitoring
20    pub enable_transport_monitoring: bool,
21    /// Maximum number of events to keep in memory
22    pub max_events: usize,
23    /// Enable performance metrics
24    pub enable_performance_metrics: bool,
25}
26
27impl Default for DevToolsConfig {
28    fn default() -> Self {
29        Self {
30            enable_crdt_inspection: true,
31            enable_sync_monitoring: true,
32            enable_transport_monitoring: true,
33            max_events: 1000,
34            enable_performance_metrics: true,
35        }
36    }
37}
38
39/// Event types that can be monitored
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub enum DevToolsEvent {
42    /// CRDT operation performed
43    CrdtOperation {
44        crdt_id: String,
45        operation: String,
46        timestamp: u64,
47        replica_id: ReplicaId,
48    },
49    /// Sync operation started/completed
50    SyncOperation {
51        operation_id: String,
52        operation_type: String,
53        status: String,
54        timestamp: u64,
55        duration_ms: Option<u64>,
56    },
57    /// Transport event
58    TransportEvent {
59        transport_type: String,
60        event_type: String,
61        timestamp: u64,
62        details: String,
63    },
64    /// Performance metric
65    PerformanceMetric {
66        metric_name: String,
67        value: f64,
68        timestamp: u64,
69        unit: String,
70    },
71}
72
73/// CRDT inspection information
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct CrdtInspection {
76    /// CRDT identifier
77    pub id: String,
78    /// CRDT type name
79    pub type_name: String,
80    /// Replica ID
81    pub replica_id: ReplicaId,
82    /// Current state summary
83    pub state_summary: String,
84    /// Number of operations performed
85    pub operation_count: u64,
86    /// Last operation timestamp
87    pub last_operation_at: u64,
88    /// Memory usage estimate
89    pub memory_usage_bytes: usize,
90}
91
92/// Sync operation statistics
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct SyncStats {
95    /// Total sync operations
96    pub total_operations: u64,
97    /// Successful operations
98    pub successful_operations: u64,
99    /// Failed operations
100    pub failed_operations: u64,
101    /// Average operation duration
102    pub avg_duration_ms: f64,
103    /// Last sync timestamp
104    pub last_sync_at: u64,
105}
106
107/// Transport statistics
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct TransportStats {
110    /// Transport type
111    pub transport_type: String,
112    /// Total messages sent
113    pub messages_sent: u64,
114    /// Total messages received
115    pub messages_received: u64,
116    /// Connection status
117    pub is_connected: bool,
118    /// Last activity timestamp
119    pub last_activity_at: u64,
120    /// Error count
121    pub error_count: u64,
122}
123
124/// Performance metrics
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct PerformanceMetrics {
127    /// CRDT merge operations per second
128    pub crdt_merges_per_second: f64,
129    /// Sync operations per second
130    pub sync_operations_per_second: f64,
131    /// Memory usage in bytes
132    pub memory_usage_bytes: usize,
133    /// CPU usage percentage
134    pub cpu_usage_percent: f64,
135}
136
137/// Complete DevTools data export
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct DevToolsExport {
140    /// All recorded events
141    pub events: Vec<DevToolsEvent>,
142    /// CRDT inspections
143    pub crdt_inspections: HashMap<String, CrdtInspection>,
144    /// Sync statistics
145    pub sync_stats: SyncStats,
146    /// Transport statistics
147    pub transport_stats: HashMap<String, TransportStats>,
148    /// Performance metrics
149    pub performance_metrics: PerformanceMetrics,
150    /// Configuration
151    pub config: DevToolsConfig,
152}
153
154/// Main DevTools instance
155pub struct DevTools {
156    config: DevToolsConfig,
157    events: Arc<RwLock<Vec<DevToolsEvent>>>,
158    crdt_inspections: Arc<RwLock<HashMap<String, CrdtInspection>>>,
159    sync_stats: Arc<RwLock<SyncStats>>,
160    transport_stats: Arc<RwLock<HashMap<String, TransportStats>>>,
161    performance_metrics: Arc<RwLock<PerformanceMetrics>>,
162}
163
164impl DevTools {
165    /// Create a new DevTools instance
166    pub fn new(config: DevToolsConfig) -> Self {
167        Self {
168            config,
169            events: Arc::new(RwLock::new(Vec::new())),
170            crdt_inspections: Arc::new(RwLock::new(HashMap::new())),
171            sync_stats: Arc::new(RwLock::new(SyncStats {
172                total_operations: 0,
173                successful_operations: 0,
174                failed_operations: 0,
175                avg_duration_ms: 0.0,
176                last_sync_at: 0,
177            })),
178            transport_stats: Arc::new(RwLock::new(HashMap::new())),
179            performance_metrics: Arc::new(RwLock::new(PerformanceMetrics {
180                crdt_merges_per_second: 0.0,
181                sync_operations_per_second: 0.0,
182                memory_usage_bytes: 0,
183                cpu_usage_percent: 0.0,
184            })),
185        }
186    }
187
188    /// Record a CRDT operation
189    pub async fn record_crdt_operation(&self, crdt_id: String, operation: String, replica_id: ReplicaId) {
190        if !self.config.enable_crdt_inspection {
191            return;
192        }
193
194        let event = DevToolsEvent::CrdtOperation {
195            crdt_id: crdt_id.clone(),
196            operation,
197            timestamp: self.current_timestamp(),
198            replica_id,
199        };
200
201        self.add_event(event).await;
202
203        // Update CRDT inspection
204        self.update_crdt_inspection(crdt_id, replica_id).await;
205    }
206
207    /// Record a sync operation
208    pub async fn record_sync_operation(&self, operation_id: String, operation_type: String, status: String, duration_ms: Option<u64>) {
209        if !self.config.enable_sync_monitoring {
210            return;
211        }
212
213        let is_success = status == "success";
214        let event = DevToolsEvent::SyncOperation {
215            operation_id,
216            operation_type,
217            status,
218            timestamp: self.current_timestamp(),
219            duration_ms,
220        };
221
222        self.add_event(event).await;
223        self.update_sync_stats(is_success, duration_ms).await;
224    }
225
226    /// Record a transport event
227    pub async fn record_transport_event(&self, transport_type: String, event_type: String, details: String) {
228        if !self.config.enable_transport_monitoring {
229            return;
230        }
231
232        let event = DevToolsEvent::TransportEvent {
233            transport_type: transport_type.clone(),
234            event_type,
235            timestamp: self.current_timestamp(),
236            details,
237        };
238
239        self.add_event(event).await;
240        self.update_transport_stats(transport_type).await;
241    }
242
243    /// Record a performance metric
244    pub async fn record_performance_metric(&self, metric_name: String, value: f64, unit: String) {
245        if !self.config.enable_performance_metrics {
246            return;
247        }
248
249        let event = DevToolsEvent::PerformanceMetric {
250            metric_name,
251            value,
252            timestamp: self.current_timestamp(),
253            unit,
254        };
255
256        self.add_event(event).await;
257    }
258
259    /// Get all events
260    pub async fn get_events(&self) -> Vec<DevToolsEvent> {
261        self.events.read().await.clone()
262    }
263
264    /// Get CRDT inspections
265    pub async fn get_crdt_inspections(&self) -> HashMap<String, CrdtInspection> {
266        self.crdt_inspections.read().await.clone()
267    }
268
269    /// Get sync statistics
270    pub async fn get_sync_stats(&self) -> SyncStats {
271        self.sync_stats.read().await.clone()
272    }
273
274    /// Get transport statistics
275    pub async fn get_transport_stats(&self) -> HashMap<String, TransportStats> {
276        self.transport_stats.read().await.clone()
277    }
278
279    /// Get performance metrics
280    pub async fn get_performance_metrics(&self) -> PerformanceMetrics {
281        self.performance_metrics.read().await.clone()
282    }
283
284    /// Clear all events
285    pub async fn clear_events(&self) {
286        let mut events = self.events.write().await;
287        events.clear();
288    }
289
290    /// Get configuration
291    pub fn config(&self) -> &DevToolsConfig {
292        &self.config
293    }
294
295    /// Get events filtered by type
296    pub async fn get_events_by_type(&self, event_type: &str) -> Vec<DevToolsEvent> {
297        let events = self.events.read().await;
298        events.iter()
299            .filter(|event| match event {
300                DevToolsEvent::CrdtOperation { .. } => event_type == "crdt_operation",
301                DevToolsEvent::SyncOperation { .. } => event_type == "sync_operation",
302                DevToolsEvent::TransportEvent { .. } => event_type == "transport_event",
303                DevToolsEvent::PerformanceMetric { .. } => event_type == "performance_metric",
304            })
305            .cloned()
306            .collect()
307    }
308
309    /// Get recent events (last N events)
310    pub async fn get_recent_events(&self, count: usize) -> Vec<DevToolsEvent> {
311        let events = self.events.read().await;
312        let start = if events.len() > count {
313            events.len() - count
314        } else {
315            0
316        };
317        events[start..].to_vec()
318    }
319
320    /// Get event count by type
321    pub async fn get_event_counts(&self) -> HashMap<String, usize> {
322        let events = self.events.read().await;
323        let mut counts = HashMap::new();
324        
325        for event in events.iter() {
326            let event_type = match event {
327                DevToolsEvent::CrdtOperation { .. } => "crdt_operation",
328                DevToolsEvent::SyncOperation { .. } => "sync_operation",
329                DevToolsEvent::TransportEvent { .. } => "transport_event",
330                DevToolsEvent::PerformanceMetric { .. } => "performance_metric",
331            };
332            
333            *counts.entry(event_type.to_string()).or_insert(0) += 1;
334        }
335        
336        counts
337    }
338
339    /// Export all data as JSON
340    pub async fn export_data(&self) -> Result<String, serde_json::Error> {
341        let data = DevToolsExport {
342            events: self.get_events().await,
343            crdt_inspections: self.get_crdt_inspections().await,
344            sync_stats: self.get_sync_stats().await,
345            transport_stats: self.get_transport_stats().await,
346            performance_metrics: self.get_performance_metrics().await,
347            config: self.config().clone(),
348        };
349        
350        serde_json::to_string_pretty(&data)
351    }
352
353    // Private helper methods
354    async fn add_event(&self, event: DevToolsEvent) {
355        let mut events = self.events.write().await;
356        events.push(event);
357        
358        // Trim events if we exceed max_events
359        if events.len() > self.config.max_events {
360            let excess = events.len() - self.config.max_events;
361            events.drain(0..excess);
362        }
363    }
364
365    async fn update_crdt_inspection(&self, crdt_id: String, replica_id: ReplicaId) {
366        let mut inspections = self.crdt_inspections.write().await;
367        let inspection = inspections.entry(crdt_id.clone()).or_insert(CrdtInspection {
368            id: crdt_id,
369            type_name: "Unknown".to_string(),
370            replica_id,
371            state_summary: "Unknown".to_string(),
372            operation_count: 0,
373            last_operation_at: 0,
374            memory_usage_bytes: 0,
375        });
376        
377        inspection.operation_count += 1;
378        inspection.last_operation_at = self.current_timestamp();
379    }
380
381    async fn update_sync_stats(&self, success: bool, duration_ms: Option<u64>) {
382        let mut stats = self.sync_stats.write().await;
383        stats.total_operations += 1;
384        
385        if success {
386            stats.successful_operations += 1;
387        } else {
388            stats.failed_operations += 1;
389        }
390        
391        if let Some(duration) = duration_ms {
392            // Update average duration
393            let total_duration = stats.avg_duration_ms * (stats.total_operations - 1) as f64 + duration as f64;
394            stats.avg_duration_ms = total_duration / stats.total_operations as f64;
395        }
396        
397        stats.last_sync_at = self.current_timestamp();
398    }
399
400    async fn update_transport_stats(&self, transport_type: String) {
401        let mut stats = self.transport_stats.write().await;
402        let transport_type_clone = transport_type.clone();
403        let transport_stats = stats.entry(transport_type).or_insert(TransportStats {
404            transport_type: transport_type_clone,
405            messages_sent: 0,
406            messages_received: 0,
407            is_connected: false,
408            last_activity_at: 0,
409            error_count: 0,
410        });
411        
412        transport_stats.last_activity_at = self.current_timestamp();
413    }
414
415    fn current_timestamp(&self) -> u64 {
416        std::time::SystemTime::now()
417            .duration_since(std::time::UNIX_EPOCH)
418            .unwrap()
419            .as_millis() as u64
420    }
421}
422
423/// CRDT Inspector for detailed CRDT analysis
424pub struct CrdtInspector {
425    devtools: Arc<DevTools>,
426}
427
428impl CrdtInspector {
429    /// Create a new CRDT inspector
430    pub fn new(devtools: Arc<DevTools>) -> Self {
431        Self { devtools }
432    }
433
434    /// Inspect a CRDT and return detailed information
435    pub async fn inspect_crdt<T: CRDT + Mergeable>(&self, crdt: &T, crdt_id: String) -> CrdtInspection {
436        let replica_id = crdt.replica_id().clone();
437        let type_name = std::any::type_name::<T>().to_string();
438        
439        // This would be implemented with actual CRDT inspection logic
440        let state_summary = format!("CRDT of type {}", type_name);
441        let memory_usage_bytes = std::mem::size_of_val(crdt);
442        
443        let inspection = CrdtInspection {
444            id: crdt_id.clone(),
445            type_name,
446            replica_id,
447            state_summary,
448            operation_count: 0, // Would be tracked by the CRDT
449            last_operation_at: 0,
450            memory_usage_bytes,
451        };
452
453        // Record the inspection
454        let mut inspections = self.devtools.crdt_inspections.write().await;
455        inspections.insert(crdt_id, inspection.clone());
456        
457        inspection
458    }
459
460    /// Get all CRDT inspections
461    pub async fn get_all_inspections(&self) -> HashMap<String, CrdtInspection> {
462        self.devtools.get_crdt_inspections().await
463    }
464}
465
466#[cfg(test)]
467mod tests {
468    use super::*;
469    use crate::crdt::{LwwRegister, ReplicaId};
470    use uuid::Uuid;
471
472    fn create_replica(id: u64) -> ReplicaId {
473        ReplicaId::from(Uuid::from_u64_pair(0, id))
474    }
475
476    #[tokio::test]
477    async fn test_devtools_creation() {
478        let config = DevToolsConfig::default();
479        let devtools = DevTools::new(config);
480        
481        assert!(devtools.config().enable_crdt_inspection);
482        assert!(devtools.config().enable_sync_monitoring);
483        assert!(devtools.config().enable_transport_monitoring);
484        assert_eq!(devtools.config().max_events, 1000);
485    }
486
487    #[tokio::test]
488    async fn test_record_crdt_operation() {
489        let config = DevToolsConfig::default();
490        let devtools = DevTools::new(config);
491        let replica_id = create_replica(1);
492        
493        devtools.record_crdt_operation("test-crdt".to_string(), "add".to_string(), replica_id).await;
494        
495        let events = devtools.get_events().await;
496        assert_eq!(events.len(), 1);
497        
498        match &events[0] {
499            DevToolsEvent::CrdtOperation { crdt_id, operation, replica_id: recorded_replica_id, .. } => {
500                assert_eq!(crdt_id, "test-crdt");
501                assert_eq!(operation, "add");
502                assert_eq!(recorded_replica_id, &replica_id);
503            }
504            _ => panic!("Expected CrdtOperation event"),
505        }
506    }
507
508    #[tokio::test]
509    async fn test_record_sync_operation() {
510        let config = DevToolsConfig::default();
511        let devtools = DevTools::new(config);
512        
513        devtools.record_sync_operation(
514            "sync-1".to_string(),
515            "merge".to_string(),
516            "success".to_string(),
517            Some(150)
518        ).await;
519        
520        let events = devtools.get_events().await;
521        assert_eq!(events.len(), 1);
522        
523        match &events[0] {
524            DevToolsEvent::SyncOperation { operation_id, operation_type, status, duration_ms, .. } => {
525                assert_eq!(operation_id, "sync-1");
526                assert_eq!(operation_type, "merge");
527                assert_eq!(status, "success");
528                assert_eq!(duration_ms, &Some(150));
529            }
530            _ => panic!("Expected SyncOperation event"),
531        }
532    }
533
534    #[tokio::test]
535    async fn test_record_transport_event() {
536        let config = DevToolsConfig::default();
537        let devtools = DevTools::new(config);
538        
539        devtools.record_transport_event(
540            "websocket".to_string(),
541            "connected".to_string(),
542            "Connected to server".to_string()
543        ).await;
544        
545        let events = devtools.get_events().await;
546        assert_eq!(events.len(), 1);
547        
548        match &events[0] {
549            DevToolsEvent::TransportEvent { transport_type, event_type, details, .. } => {
550                assert_eq!(transport_type, "websocket");
551                assert_eq!(event_type, "connected");
552                assert_eq!(details, "Connected to server");
553            }
554            _ => panic!("Expected TransportEvent"),
555        }
556    }
557
558    #[tokio::test]
559    async fn test_record_performance_metric() {
560        let config = DevToolsConfig::default();
561        let devtools = DevTools::new(config);
562        
563        devtools.record_performance_metric(
564            "memory_usage".to_string(),
565            1024.0,
566            "bytes".to_string()
567        ).await;
568        
569        let events = devtools.get_events().await;
570        assert_eq!(events.len(), 1);
571        
572        match &events[0] {
573            DevToolsEvent::PerformanceMetric { metric_name, value, unit, .. } => {
574                assert_eq!(metric_name, "memory_usage");
575                assert_eq!(*value, 1024.0);
576                assert_eq!(unit, "bytes");
577            }
578            _ => panic!("Expected PerformanceMetric event"),
579        }
580    }
581
582    #[tokio::test]
583    async fn test_sync_stats_tracking() {
584        let config = DevToolsConfig::default();
585        let devtools = DevTools::new(config);
586        
587        // Record successful operation
588        devtools.record_sync_operation(
589            "sync-1".to_string(),
590            "merge".to_string(),
591            "success".to_string(),
592            Some(100)
593        ).await;
594        
595        // Record failed operation
596        devtools.record_sync_operation(
597            "sync-2".to_string(),
598            "merge".to_string(),
599            "failed".to_string(),
600            Some(200)
601        ).await;
602        
603        let stats = devtools.get_sync_stats().await;
604        assert_eq!(stats.total_operations, 2);
605        assert_eq!(stats.successful_operations, 1);
606        assert_eq!(stats.failed_operations, 1);
607        assert_eq!(stats.avg_duration_ms, 150.0); // (100 + 200) / 2
608    }
609
610    #[tokio::test]
611    async fn test_event_limit() {
612        let config = DevToolsConfig {
613            max_events: 3,
614            ..Default::default()
615        };
616        let devtools = DevTools::new(config);
617        
618        // Add more events than the limit
619        for i in 0..5 {
620            devtools.record_crdt_operation(
621                format!("crdt-{}", i),
622                "add".to_string(),
623                create_replica(1)
624            ).await;
625        }
626        
627        let events = devtools.get_events().await;
628        assert_eq!(events.len(), 3); // Should be limited to max_events
629    }
630
631    #[tokio::test]
632    async fn test_clear_events() {
633        let config = DevToolsConfig::default();
634        let devtools = DevTools::new(config);
635        
636        // Add some events
637        devtools.record_crdt_operation("test".to_string(), "add".to_string(), create_replica(1)).await;
638        assert_eq!(devtools.get_events().await.len(), 1);
639        
640        // Clear events
641        devtools.clear_events().await;
642        assert_eq!(devtools.get_events().await.len(), 0);
643    }
644
645    #[tokio::test]
646    async fn test_crdt_inspector() {
647        let config = DevToolsConfig::default();
648        let devtools = Arc::new(DevTools::new(config));
649        let inspector = CrdtInspector::new(devtools.clone());
650        
651        let replica_id = create_replica(1);
652        let crdt = LwwRegister::new("test".to_string(), replica_id.clone());
653        
654        let inspection = inspector.inspect_crdt(&crdt, "test-crdt".to_string()).await;
655        
656        assert_eq!(inspection.id, "test-crdt");
657        assert!(inspection.type_name.contains("LwwRegister"));
658        assert_eq!(inspection.replica_id, replica_id);
659        assert!(inspection.memory_usage_bytes > 0);
660        
661        // Check that inspection was stored
662        let inspections = inspector.get_all_inspections().await;
663        assert_eq!(inspections.len(), 1);
664        assert!(inspections.contains_key("test-crdt"));
665    }
666
667    #[tokio::test]
668    async fn test_disabled_features() {
669        let config = DevToolsConfig {
670            enable_crdt_inspection: false,
671            enable_sync_monitoring: false,
672            enable_transport_monitoring: false,
673            enable_performance_metrics: false,
674            ..Default::default()
675        };
676        let devtools = DevTools::new(config);
677        
678        // These should not record events
679        devtools.record_crdt_operation("test".to_string(), "add".to_string(), create_replica(1)).await;
680        devtools.record_sync_operation("test".to_string(), "merge".to_string(), "success".to_string(), None).await;
681        devtools.record_transport_event("test".to_string(), "connected".to_string(), "test".to_string()).await;
682        devtools.record_performance_metric("test".to_string(), 1.0, "test".to_string()).await;
683        
684        assert_eq!(devtools.get_events().await.len(), 0);
685    }
686
687    #[tokio::test]
688    async fn test_get_events_by_type() {
689        let config = DevToolsConfig::default();
690        let devtools = DevTools::new(config);
691        
692        // Add different types of events
693        devtools.record_crdt_operation("test".to_string(), "add".to_string(), create_replica(1)).await;
694        devtools.record_sync_operation("test".to_string(), "merge".to_string(), "success".to_string(), None).await;
695        devtools.record_transport_event("test".to_string(), "connected".to_string(), "test".to_string()).await;
696        devtools.record_performance_metric("test".to_string(), 1.0, "test".to_string()).await;
697        
698        // Test filtering by type
699        let crdt_events = devtools.get_events_by_type("crdt_operation").await;
700        assert_eq!(crdt_events.len(), 1);
701        
702        let sync_events = devtools.get_events_by_type("sync_operation").await;
703        assert_eq!(sync_events.len(), 1);
704        
705        let transport_events = devtools.get_events_by_type("transport_event").await;
706        assert_eq!(transport_events.len(), 1);
707        
708        let perf_events = devtools.get_events_by_type("performance_metric").await;
709        assert_eq!(perf_events.len(), 1);
710    }
711
712    #[tokio::test]
713    async fn test_get_recent_events() {
714        let config = DevToolsConfig::default();
715        let devtools = DevTools::new(config);
716        
717        // Add 5 events
718        for i in 0..5 {
719            devtools.record_crdt_operation(format!("test-{}", i), "add".to_string(), create_replica(1)).await;
720        }
721        
722        // Get last 3 events
723        let recent = devtools.get_recent_events(3).await;
724        assert_eq!(recent.len(), 3);
725        
726        // Get more events than we have
727        let all_recent = devtools.get_recent_events(10).await;
728        assert_eq!(all_recent.len(), 5);
729    }
730
731    #[tokio::test]
732    async fn test_get_event_counts() {
733        let config = DevToolsConfig::default();
734        let devtools = DevTools::new(config);
735        
736        // Add different types of events
737        devtools.record_crdt_operation("test".to_string(), "add".to_string(), create_replica(1)).await;
738        devtools.record_crdt_operation("test2".to_string(), "remove".to_string(), create_replica(1)).await;
739        devtools.record_sync_operation("test".to_string(), "merge".to_string(), "success".to_string(), None).await;
740        devtools.record_transport_event("test".to_string(), "connected".to_string(), "test".to_string()).await;
741        
742        let counts = devtools.get_event_counts().await;
743        assert_eq!(counts.get("crdt_operation"), Some(&2));
744        assert_eq!(counts.get("sync_operation"), Some(&1));
745        assert_eq!(counts.get("transport_event"), Some(&1));
746        // Performance metric count should be 0 since we didn't add any
747        assert_eq!(counts.get("performance_metric"), None);
748    }
749
750    #[tokio::test]
751    async fn test_export_data() {
752        let config = DevToolsConfig::default();
753        let devtools = DevTools::new(config);
754        
755        // Add some data
756        devtools.record_crdt_operation("test".to_string(), "add".to_string(), create_replica(1)).await;
757        devtools.record_sync_operation("test".to_string(), "merge".to_string(), "success".to_string(), None).await;
758        
759        // Export data
760        let export_json = devtools.export_data().await.unwrap();
761        assert!(export_json.contains("test"));
762        assert!(export_json.contains("CrdtOperation"));
763        assert!(export_json.contains("SyncOperation"));
764        
765        // Verify it's valid JSON
766        let parsed: DevToolsExport = serde_json::from_str(&export_json).unwrap();
767        assert_eq!(parsed.events.len(), 2);
768        assert_eq!(parsed.config.max_events, 1000);
769    }
770}