Skip to main content

trustformers_debug/
data_export.rs

1//! Data export capabilities for debugging tools
2//!
3//! This module provides comprehensive data export functionality supporting
4//! multiple formats including CSV, Excel, JSON, HDF5, and more.
5
6use anyhow::Result;
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use uuid::Uuid;
11
12/// Data export manager for debugging tools
13#[derive(Debug, Clone)]
14pub struct DataExportManager {
15    /// Export configuration
16    config: ExportConfig,
17    /// Active export jobs
18    active_jobs: HashMap<Uuid, ExportJob>,
19    /// Export history
20    export_history: Vec<ExportRecord>,
21    /// Supported formats
22    supported_formats: Vec<ExportFormat>,
23}
24
25/// Export configuration
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct ExportConfig {
28    /// Default export directory
29    pub default_directory: String,
30    /// Maximum file size (bytes)
31    pub max_file_size: u64,
32    /// Enable compression
33    pub enable_compression: bool,
34    /// Default format
35    pub default_format: ExportFormat,
36    /// Include metadata
37    pub include_metadata: bool,
38    /// Export templates
39    pub templates: Vec<ExportTemplate>,
40}
41
42/// Export job tracking
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct ExportJob {
45    /// Job identifier
46    pub id: Uuid,
47    /// Job name
48    pub name: String,
49    /// Export format
50    pub format: ExportFormat,
51    /// Output path
52    pub output_path: String,
53    /// Job status
54    pub status: ExportStatus,
55    /// Progress percentage
56    pub progress: f64,
57    /// Start time
58    pub started_at: DateTime<Utc>,
59    /// Completion time
60    pub completed_at: Option<DateTime<Utc>>,
61    /// Data size (bytes)
62    pub data_size: u64,
63    /// Error message (if failed)
64    pub error_message: Option<String>,
65    /// Export options
66    pub options: ExportOptions,
67}
68
69/// Export record for history
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct ExportRecord {
72    /// Record identifier
73    pub id: Uuid,
74    /// Export job ID
75    pub job_id: Uuid,
76    /// Export timestamp
77    pub timestamp: DateTime<Utc>,
78    /// File path
79    pub file_path: String,
80    /// File size
81    pub file_size: u64,
82    /// Export format
83    pub format: ExportFormat,
84    /// Success status
85    pub success: bool,
86    /// Duration (seconds)
87    pub duration: f64,
88}
89
90/// Export template for common configurations
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct ExportTemplate {
93    /// Template identifier
94    pub id: String,
95    /// Template name
96    pub name: String,
97    /// Description
98    pub description: String,
99    /// Export format
100    pub format: ExportFormat,
101    /// Export options
102    pub options: ExportOptions,
103    /// Data filters
104    pub filters: DataFilters,
105    /// Template tags
106    pub tags: Vec<String>,
107}
108
109/// Export options
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct ExportOptions {
112    /// Include headers (for CSV/Excel)
113    pub include_headers: bool,
114    /// Date format
115    pub date_format: String,
116    /// Precision for floats
117    pub float_precision: u32,
118    /// Field separator (for CSV)
119    pub separator: String,
120    /// Compression level (0-9)
121    pub compression_level: u32,
122    /// Include metadata
123    pub include_metadata: bool,
124    /// Custom formatting options
125    pub custom_options: HashMap<String, serde_json::Value>,
126}
127
128/// Data filters for selective export
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct DataFilters {
131    /// Date range filter
132    pub date_range: Option<DateRange>,
133    /// Include specific data types
134    pub data_types: Vec<DataType>,
135    /// Exclude fields
136    pub exclude_fields: Vec<String>,
137    /// Include only fields
138    pub include_fields: Option<Vec<String>>,
139    /// Custom filters
140    pub custom_filters: HashMap<String, serde_json::Value>,
141}
142
143/// Date range for filtering
144#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct DateRange {
146    /// Start date
147    pub start: DateTime<Utc>,
148    /// End date
149    pub end: DateTime<Utc>,
150}
151
152/// Export formats supported
153#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Hash, Eq)]
154pub enum ExportFormat {
155    /// Comma-separated values
156    Csv,
157    /// Excel workbook
158    Excel,
159    /// JSON format
160    Json,
161    /// Pretty-printed JSON
162    JsonPretty,
163    /// HDF5 format
164    Hdf5,
165    /// Parquet format
166    Parquet,
167    /// XML format
168    Xml,
169    /// YAML format
170    Yaml,
171    /// SQLite database
172    Sqlite,
173    /// MessagePack
174    MessagePack,
175    /// Apache Arrow
176    Arrow,
177    /// Custom format
178    Custom(String),
179}
180
181/// Export status
182#[derive(Debug, Clone, Serialize, Deserialize)]
183pub enum ExportStatus {
184    Pending,
185    InProgress,
186    Completed,
187    Failed,
188    Cancelled,
189}
190
191/// Data types that can be exported
192#[derive(Debug, Clone, Serialize, Deserialize)]
193pub enum DataType {
194    TensorData,
195    GradientData,
196    PerformanceMetrics,
197    MemoryProfiles,
198    ActivityLogs,
199    AnnotationData,
200    CommentData,
201    ModelDiagnostics,
202    TrainingDynamics,
203    ArchitectureAnalysis,
204    Custom(String),
205}
206
207/// Exportable data container
208#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct ExportableData {
210    /// Data identifier
211    pub id: Uuid,
212    /// Data name
213    pub name: String,
214    /// Data type
215    pub data_type: DataType,
216    /// Creation timestamp
217    pub timestamp: DateTime<Utc>,
218    /// Data content
219    pub content: ExportDataContent,
220    /// Metadata
221    pub metadata: HashMap<String, serde_json::Value>,
222    /// Data size
223    pub size: u64,
224}
225
226/// Content of exportable data
227#[derive(Debug, Clone, Serialize, Deserialize)]
228pub enum ExportDataContent {
229    /// Tabular data
230    Table(TableData),
231    /// Time series data
232    TimeSeries(TimeSeriesData),
233    /// Key-value pairs
234    KeyValue(HashMap<String, serde_json::Value>),
235    /// Structured data
236    Structured(serde_json::Value),
237    /// Binary data
238    Binary(Vec<u8>),
239    /// Text data
240    Text(String),
241}
242
243/// Tabular data structure
244#[derive(Debug, Clone, Serialize, Deserialize)]
245pub struct TableData {
246    /// Column headers
247    pub headers: Vec<String>,
248    /// Data rows
249    pub rows: Vec<Vec<serde_json::Value>>,
250    /// Column types
251    pub column_types: HashMap<String, ColumnType>,
252}
253
254/// Time series data structure
255#[derive(Debug, Clone, Serialize, Deserialize)]
256pub struct TimeSeriesData {
257    /// Timestamps
258    pub timestamps: Vec<DateTime<Utc>>,
259    /// Data series
260    pub series: HashMap<String, Vec<f64>>,
261    /// Series metadata
262    pub metadata: HashMap<String, String>,
263}
264
265/// Column data types
266#[derive(Debug, Clone, Serialize, Deserialize)]
267pub enum ColumnType {
268    Integer,
269    Float,
270    String,
271    Boolean,
272    DateTime,
273    Binary,
274}
275
276impl DataExportManager {
277    /// Create a new data export manager
278    pub fn new(config: ExportConfig) -> Self {
279        let supported_formats = vec![
280            ExportFormat::Csv,
281            ExportFormat::Excel,
282            ExportFormat::Json,
283            ExportFormat::JsonPretty,
284            ExportFormat::Xml,
285            ExportFormat::Yaml,
286            ExportFormat::Sqlite,
287        ];
288
289        Self {
290            config,
291            active_jobs: HashMap::new(),
292            export_history: Vec::new(),
293            supported_formats,
294        }
295    }
296
297    /// Start an export job
298    pub fn start_export(
299        &mut self,
300        name: String,
301        data: Vec<ExportableData>,
302        format: ExportFormat,
303        output_path: String,
304        options: ExportOptions,
305    ) -> Result<Uuid> {
306        let job_id = Uuid::new_v4();
307
308        // Calculate total data size
309        let data_size: u64 = data.iter().map(|d| d.size).sum();
310
311        // Check file size limit
312        if data_size > self.config.max_file_size {
313            return Err(anyhow::anyhow!("Data size exceeds maximum file size limit"));
314        }
315
316        let job = ExportJob {
317            id: job_id,
318            name: name.clone(),
319            format: format.clone(),
320            output_path: output_path.clone(),
321            status: ExportStatus::Pending,
322            progress: 0.0,
323            started_at: Utc::now(),
324            completed_at: None,
325            data_size,
326            error_message: None,
327            options: options.clone(),
328        };
329
330        self.active_jobs.insert(job_id, job);
331
332        // Start the actual export process
333        self.execute_export(job_id, data, options)?;
334
335        Ok(job_id)
336    }
337
338    /// Execute the export process
339    fn execute_export(
340        &mut self,
341        job_id: Uuid,
342        data: Vec<ExportableData>,
343        options: ExportOptions,
344    ) -> Result<()> {
345        // Extract job info to avoid multiple mutable borrows
346        let (format, output_path) = {
347            if let Some(job) = self.active_jobs.get_mut(&job_id) {
348                job.status = ExportStatus::InProgress;
349                (job.format.clone(), job.output_path.clone())
350            } else {
351                return Err(anyhow::anyhow!("Export job not found"));
352            }
353        };
354
355        let result = match format {
356            ExportFormat::Csv => self.export_csv(&data, &output_path, &options),
357            ExportFormat::Json => self.export_json(&data, &output_path, &options),
358            ExportFormat::JsonPretty => self.export_json_pretty(&data, &output_path, &options),
359            ExportFormat::Excel => self.export_excel(&data, &output_path, &options),
360            ExportFormat::Xml => self.export_xml(&data, &output_path, &options),
361            ExportFormat::Yaml => self.export_yaml(&data, &output_path, &options),
362            ExportFormat::Sqlite => self.export_sqlite(&data, &output_path, &options),
363            // Binary/columnar formats that require optional external crates or services.
364            // These are intentionally not implemented to keep the core dependency set
365            // minimal.  Callers should use JSON or CSV as a universal fallback.
366            ExportFormat::Hdf5 => Err(anyhow::anyhow!(
367                "HDF5 export is not supported in this build. Use JSON or CSV instead."
368            )),
369            ExportFormat::Parquet => Err(anyhow::anyhow!(
370                "Parquet export is not supported in this build. Use JSON or CSV instead."
371            )),
372            ExportFormat::MessagePack => Err(anyhow::anyhow!(
373                "MessagePack export is not supported in this build. Use JSON instead."
374            )),
375            ExportFormat::Arrow => Err(anyhow::anyhow!(
376                "Apache Arrow export is not supported in this build. Use JSON or CSV instead."
377            )),
378            ExportFormat::Custom(ref name) => Err(anyhow::anyhow!(
379                "Custom export format '{}' is not registered. \
380                 Register a handler or use one of the built-in formats.",
381                name
382            )),
383        };
384
385        // Update job status
386        if let Some(job) = self.active_jobs.get_mut(&job_id) {
387            match result {
388                Ok(_) => {
389                    job.status = ExportStatus::Completed;
390                    job.progress = 100.0;
391                    job.completed_at = Some(Utc::now());
392
393                    // Add to history by cloning the job
394                    let job_copy = job.clone();
395                    self.add_export_record(&job_copy);
396                },
397                Err(e) => {
398                    job.status = ExportStatus::Failed;
399                    job.error_message = Some(e.to_string());
400                },
401            }
402        }
403
404        Ok(())
405    }
406
407    /// Export to CSV format
408    fn export_csv(
409        &mut self,
410        data: &[ExportableData],
411        output_path: &str,
412        options: &ExportOptions,
413    ) -> Result<()> {
414        use std::fs::File;
415        use std::io::Write;
416
417        let mut file = File::create(output_path)?;
418
419        for item in data {
420            match &item.content {
421                ExportDataContent::Table(table_data) => {
422                    // Write headers
423                    if options.include_headers {
424                        let header_line = table_data.headers.join(&options.separator);
425                        writeln!(file, "{}", header_line)?;
426                    }
427
428                    // Write data rows
429                    for row in &table_data.rows {
430                        let row_values: Vec<String> =
431                            row.iter().map(|v| self.format_value_for_csv(v, options)).collect();
432                        let row_line = row_values.join(&options.separator);
433                        writeln!(file, "{}", row_line)?;
434                    }
435                },
436                ExportDataContent::TimeSeries(ts_data) => {
437                    // Write time series data
438                    if options.include_headers {
439                        let mut headers = vec!["timestamp".to_string()];
440                        headers.extend(ts_data.series.keys().cloned());
441                        let header_line = headers.join(&options.separator);
442                        writeln!(file, "{}", header_line)?;
443                    }
444
445                    for (i, timestamp) in ts_data.timestamps.iter().enumerate() {
446                        let mut row = vec![timestamp.format(&options.date_format).to_string()];
447                        for series_name in ts_data.series.keys() {
448                            if let Some(series) = ts_data.series.get(series_name) {
449                                if let Some(value) = series.get(i) {
450                                    row.push(format!(
451                                        "{:.precision$}",
452                                        value,
453                                        precision = options.float_precision as usize
454                                    ));
455                                } else {
456                                    row.push("".to_string());
457                                }
458                            }
459                        }
460                        let row_line = row.join(&options.separator);
461                        writeln!(file, "{}", row_line)?;
462                    }
463                },
464                _ => {
465                    // Convert other formats to JSON and then to CSV-like representation
466                    let json_str = serde_json::to_string(&item.content)?;
467                    writeln!(file, "{}", json_str)?;
468                },
469            }
470        }
471
472        Ok(())
473    }
474
475    /// Export to JSON format
476    fn export_json(
477        &mut self,
478        data: &[ExportableData],
479        output_path: &str,
480        _options: &ExportOptions,
481    ) -> Result<()> {
482        use std::fs::File;
483
484        let file = File::create(output_path)?;
485        serde_json::to_writer(file, data)?;
486        Ok(())
487    }
488
489    /// Export to pretty JSON format
490    fn export_json_pretty(
491        &mut self,
492        data: &[ExportableData],
493        output_path: &str,
494        _options: &ExportOptions,
495    ) -> Result<()> {
496        use std::fs::File;
497
498        let file = File::create(output_path)?;
499        serde_json::to_writer_pretty(file, data)?;
500        Ok(())
501    }
502
503    /// Export to Excel format (simplified implementation)
504    fn export_excel(
505        &mut self,
506        data: &[ExportableData],
507        output_path: &str,
508        options: &ExportOptions,
509    ) -> Result<()> {
510        // This is a simplified implementation
511        // In a real implementation, you would use a library like xlsxwriter or rust_xlsxwriter
512
513        // For now, we'll create a CSV file with .xlsx extension as a placeholder
514        self.export_csv(data, output_path, options)
515    }
516
517    /// Export to XML format
518    fn export_xml(
519        &mut self,
520        data: &[ExportableData],
521        output_path: &str,
522        _options: &ExportOptions,
523    ) -> Result<()> {
524        use std::fs::File;
525        use std::io::Write;
526
527        let mut file = File::create(output_path)?;
528
529        writeln!(file, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>")?;
530        writeln!(file, "<export_data>")?;
531
532        for item in data {
533            writeln!(
534                file,
535                "  <data_item id=\"{}\" type=\"{:?}\">",
536                item.id, item.data_type
537            )?;
538            writeln!(file, "    <name>{}</name>", item.name)?;
539            writeln!(
540                file,
541                "    <timestamp>{}</timestamp>",
542                item.timestamp.to_rfc3339()
543            )?;
544            writeln!(file, "    <size>{}</size>", item.size)?;
545
546            // Convert content to XML (simplified)
547            let content_json = serde_json::to_string(&item.content)?;
548            writeln!(file, "    <content><![CDATA[{}]]></content>", content_json)?;
549
550            writeln!(file, "  </data_item>")?;
551        }
552
553        writeln!(file, "</export_data>")?;
554        Ok(())
555    }
556
557    /// Export to YAML format
558    fn export_yaml(
559        &mut self,
560        data: &[ExportableData],
561        output_path: &str,
562        _options: &ExportOptions,
563    ) -> Result<()> {
564        use std::fs::File;
565
566        let file = File::create(output_path)?;
567        serde_json::to_writer_pretty(file, data)?;
568        Ok(())
569    }
570
571    /// Export to SQLite database
572    fn export_sqlite(
573        &mut self,
574        data: &[ExportableData],
575        output_path: &str,
576        _options: &ExportOptions,
577    ) -> Result<()> {
578        // This would use rusqlite or similar library
579        // For now, we'll create a JSON file as a placeholder
580        self.export_json(data, output_path, _options)
581    }
582
583    /// Helper function to format values for CSV
584    fn format_value_for_csv(&self, value: &serde_json::Value, options: &ExportOptions) -> String {
585        match value {
586            serde_json::Value::Number(n) => {
587                if let Some(f) = n.as_f64() {
588                    format!(
589                        "{:.precision$}",
590                        f,
591                        precision = options.float_precision as usize
592                    )
593                } else {
594                    n.to_string()
595                }
596            },
597            serde_json::Value::String(s) => {
598                // Escape quotes and commas
599                if s.contains(',') || s.contains('"') || s.contains('\n') {
600                    format!("\"{}\"", s.replace('"', "\"\""))
601                } else {
602                    s.clone()
603                }
604            },
605            _ => value.to_string(),
606        }
607    }
608
609    /// Add export record to history
610    fn add_export_record(&mut self, job: &ExportJob) {
611        let record = ExportRecord {
612            id: Uuid::new_v4(),
613            job_id: job.id,
614            timestamp: Utc::now(),
615            file_path: job.output_path.clone(),
616            file_size: job.data_size,
617            format: job.format.clone(),
618            success: matches!(job.status, ExportStatus::Completed),
619            duration: job
620                .completed_at
621                .map(|end| (end - job.started_at).num_milliseconds() as f64 / 1000.0)
622                .unwrap_or(0.0),
623        };
624
625        self.export_history.push(record);
626    }
627
628    /// Get export job status
629    pub fn get_job_status(&self, job_id: Uuid) -> Option<&ExportJob> {
630        self.active_jobs.get(&job_id)
631    }
632
633    /// Get export history
634    pub fn get_export_history(&self) -> &[ExportRecord] {
635        &self.export_history
636    }
637
638    /// Create export template
639    pub fn create_template(
640        &mut self,
641        name: String,
642        description: String,
643        format: ExportFormat,
644        options: ExportOptions,
645        filters: DataFilters,
646        tags: Vec<String>,
647    ) -> String {
648        let template_id = Uuid::new_v4().to_string();
649
650        let template = ExportTemplate {
651            id: template_id.clone(),
652            name,
653            description,
654            format,
655            options,
656            filters,
657            tags,
658        };
659
660        self.config.templates.push(template);
661        template_id
662    }
663
664    /// Apply export template
665    pub fn apply_template(
666        &self,
667        template_id: &str,
668    ) -> Option<(&ExportFormat, &ExportOptions, &DataFilters)> {
669        self.config
670            .templates
671            .iter()
672            .find(|t| t.id == template_id)
673            .map(|t| (&t.format, &t.options, &t.filters))
674    }
675
676    /// Get supported formats
677    pub fn get_supported_formats(&self) -> &[ExportFormat] {
678        &self.supported_formats
679    }
680
681    /// Cancel export job
682    pub fn cancel_job(&mut self, job_id: Uuid) -> Result<()> {
683        if let Some(job) = self.active_jobs.get_mut(&job_id) {
684            if matches!(job.status, ExportStatus::Pending | ExportStatus::InProgress) {
685                job.status = ExportStatus::Cancelled;
686                Ok(())
687            } else {
688                Err(anyhow::anyhow!("Job cannot be cancelled in current status"))
689            }
690        } else {
691            Err(anyhow::anyhow!("Job not found"))
692        }
693    }
694
695    /// Get export statistics
696    pub fn get_export_statistics(&self) -> ExportStatistics {
697        let total_exports = self.export_history.len();
698        let successful_exports = self.export_history.iter().filter(|r| r.success).count();
699        let total_size: u64 = self.export_history.iter().map(|r| r.file_size).sum();
700        let avg_duration = if total_exports > 0 {
701            self.export_history.iter().map(|r| r.duration).sum::<f64>() / total_exports as f64
702        } else {
703            0.0
704        };
705
706        let format_stats: HashMap<ExportFormat, usize> =
707            self.export_history.iter().fold(HashMap::new(), |mut acc, record| {
708                *acc.entry(record.format.clone()).or_insert(0) += 1;
709                acc
710            });
711
712        ExportStatistics {
713            total_exports,
714            successful_exports,
715            failed_exports: total_exports - successful_exports,
716            total_size_bytes: total_size,
717            average_duration_seconds: avg_duration,
718            format_statistics: format_stats,
719            active_jobs: self.active_jobs.len(),
720        }
721    }
722}
723
724/// Export statistics
725#[derive(Debug, Clone, Serialize, Deserialize)]
726pub struct ExportStatistics {
727    pub total_exports: usize,
728    pub successful_exports: usize,
729    pub failed_exports: usize,
730    pub total_size_bytes: u64,
731    pub average_duration_seconds: f64,
732    pub format_statistics: HashMap<ExportFormat, usize>,
733    pub active_jobs: usize,
734}
735
736impl Default for ExportConfig {
737    fn default() -> Self {
738        Self {
739            default_directory: "./exports".to_string(),
740            max_file_size: 1024 * 1024 * 1024, // 1GB
741            enable_compression: true,
742            default_format: ExportFormat::Json,
743            include_metadata: true,
744            templates: Vec::new(),
745        }
746    }
747}
748
749impl Default for ExportOptions {
750    fn default() -> Self {
751        Self {
752            include_headers: true,
753            date_format: "%Y-%m-%d %H:%M:%S UTC".to_string(),
754            float_precision: 6,
755            separator: ",".to_string(),
756            compression_level: 6,
757            include_metadata: true,
758            custom_options: HashMap::new(),
759        }
760    }
761}
762
763impl Default for DataFilters {
764    fn default() -> Self {
765        Self {
766            date_range: None,
767            data_types: vec![
768                DataType::TensorData,
769                DataType::GradientData,
770                DataType::PerformanceMetrics,
771            ],
772            exclude_fields: Vec::new(),
773            include_fields: None,
774            custom_filters: HashMap::new(),
775        }
776    }
777}
778
779#[cfg(test)]
780mod tests {
781    use super::*;
782    use tempfile::tempdir;
783
784    // These values are test data, not approximations of mathematical constants
785    #[allow(clippy::approx_constant)]
786    fn create_test_data() -> Vec<ExportableData> {
787        let table_data = TableData {
788            headers: vec![
789                "id".to_string(),
790                "value".to_string(),
791                "timestamp".to_string(),
792            ],
793            rows: vec![
794                vec![
795                    serde_json::Value::Number(serde_json::Number::from(1)),
796                    serde_json::Value::Number(
797                        serde_json::Number::from_f64(3.14).expect("operation failed in test"),
798                    ),
799                    serde_json::Value::String("2023-01-01T12:00:00Z".to_string()),
800                ],
801                vec![
802                    serde_json::Value::Number(serde_json::Number::from(2)),
803                    serde_json::Value::Number(
804                        serde_json::Number::from_f64(2.71).expect("operation failed in test"),
805                    ),
806                    serde_json::Value::String("2023-01-01T12:01:00Z".to_string()),
807                ],
808            ],
809            column_types: HashMap::new(),
810        };
811
812        vec![ExportableData {
813            id: Uuid::new_v4(),
814            name: "Test Data".to_string(),
815            data_type: DataType::TensorData,
816            timestamp: Utc::now(),
817            content: ExportDataContent::Table(table_data),
818            metadata: HashMap::new(),
819            size: 1024,
820        }]
821    }
822
823    #[test]
824    fn test_export_manager_creation() {
825        let config = ExportConfig::default();
826        let manager = DataExportManager::new(config);
827
828        assert!(manager.get_supported_formats().contains(&ExportFormat::Json));
829        assert!(manager.get_supported_formats().contains(&ExportFormat::Csv));
830    }
831
832    #[test]
833    fn test_csv_export() {
834        let config = ExportConfig::default();
835        let mut manager = DataExportManager::new(config);
836        let test_data = create_test_data();
837
838        let temp_dir = tempdir().expect("temp file creation failed");
839        let output_path = temp_dir.path().join("test.csv").to_string_lossy().to_string();
840
841        let job_id = manager
842            .start_export(
843                "Test CSV Export".to_string(),
844                test_data,
845                ExportFormat::Csv,
846                output_path.clone(),
847                ExportOptions::default(),
848            )
849            .expect("operation failed in test");
850
851        // Check job was created
852        assert!(manager.active_jobs.contains_key(&job_id));
853
854        // Check file was created
855        assert!(std::path::Path::new(&output_path).exists());
856    }
857
858    #[test]
859    fn test_json_export() {
860        let config = ExportConfig::default();
861        let mut manager = DataExportManager::new(config);
862        let test_data = create_test_data();
863
864        let temp_dir = tempdir().expect("temp file creation failed");
865        let output_path = temp_dir.path().join("test.json").to_string_lossy().to_string();
866
867        let job_id = manager
868            .start_export(
869                "Test JSON Export".to_string(),
870                test_data,
871                ExportFormat::Json,
872                output_path.clone(),
873                ExportOptions::default(),
874            )
875            .expect("operation failed in test");
876
877        assert!(manager.active_jobs.contains_key(&job_id));
878        assert!(std::path::Path::new(&output_path).exists());
879    }
880
881    #[test]
882    fn test_export_template() {
883        let config = ExportConfig::default();
884        let mut manager = DataExportManager::new(config);
885
886        let template_id = manager.create_template(
887            "CSV Template".to_string(),
888            "Standard CSV export".to_string(),
889            ExportFormat::Csv,
890            ExportOptions::default(),
891            DataFilters::default(),
892            vec!["csv".to_string(), "standard".to_string()],
893        );
894
895        let (format, options, _filters) =
896            manager.apply_template(&template_id).expect("temp file creation failed");
897        assert_eq!(*format, ExportFormat::Csv);
898        assert!(options.include_headers);
899    }
900
901    #[test]
902    fn test_export_statistics() {
903        let config = ExportConfig::default();
904        let mut manager = DataExportManager::new(config);
905
906        // Add some mock export records
907        manager.export_history.push(ExportRecord {
908            id: Uuid::new_v4(),
909            job_id: Uuid::new_v4(),
910            timestamp: Utc::now(),
911            file_path: "test1.csv".to_string(),
912            file_size: 1024,
913            format: ExportFormat::Csv,
914            success: true,
915            duration: 2.5,
916        });
917
918        manager.export_history.push(ExportRecord {
919            id: Uuid::new_v4(),
920            job_id: Uuid::new_v4(),
921            timestamp: Utc::now(),
922            file_path: "test2.json".to_string(),
923            file_size: 2048,
924            format: ExportFormat::Json,
925            success: true,
926            duration: 1.8,
927        });
928
929        let stats = manager.get_export_statistics();
930        assert_eq!(stats.total_exports, 2);
931        assert_eq!(stats.successful_exports, 2);
932        assert_eq!(stats.total_size_bytes, 3072);
933    }
934}