//! Download events for progress tracking and status updates use serde::{Deserialize, Serialize}; /// Events emitted during download operations #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "camelCase")] pub enum DownloadEvent { /// Download has been queued #[serde(rename_all = "camelCase")] Queued { download_id: i64, item_id: String }, /// Download has started #[serde(rename_all = "camelCase")] Started { download_id: i64, item_id: String }, /// Download progress update #[serde(rename_all = "camelCase")] Progress { download_id: i64, item_id: String, bytes_downloaded: i64, total_bytes: Option, progress: f64, // 0.0 to 1.0 }, /// Download completed successfully #[serde(rename_all = "camelCase")] Completed { download_id: i64, item_id: String, file_path: String, }, /// Download failed with error #[serde(rename_all = "camelCase")] Failed { download_id: i64, item_id: String, error: String, }, /// Download paused #[serde(rename_all = "camelCase")] Paused { download_id: i64, item_id: String }, /// Download cancelled #[serde(rename_all = "camelCase")] Cancelled { download_id: i64, item_id: String }, /// The queue is holding: WiFi-only is enabled and the current network is /// metered/cellular. Pending rows stay pending and resume on network change. /// /// TRACES: UR-053 | DR-074 WaitingForNetwork, } #[cfg(test)] mod tests { use super::*; #[test] fn test_download_event_serialization_roundtrip() { let event = DownloadEvent::Progress { download_id: 1, item_id: "test123".to_string(), bytes_downloaded: 1024, total_bytes: Some(2048), progress: 0.5, }; let json = serde_json::to_string(&event).unwrap(); let deserialized: DownloadEvent = serde_json::from_str(&json).unwrap(); match deserialized { DownloadEvent::Progress { download_id, item_id, progress, .. } => { assert_eq!(download_id, 1); assert_eq!(item_id, "test123"); assert_eq!(progress, 0.5); } _ => panic!("Wrong variant"), } } #[test] fn test_download_event_completed() { let event = DownloadEvent::Completed { download_id: 42, item_id: "song456".to_string(), file_path: "/path/to/file.mp3".to_string(), }; let json = serde_json::to_string(&event).unwrap(); assert!(json.contains("\"type\":\"completed\"")); // Verify camelCase field names assert!( json.contains("\"downloadId\":42"), "Expected downloadId (camelCase), got: {}", json ); assert!( json.contains("\"itemId\":\"song456\""), "Expected itemId (camelCase), got: {}", json ); assert!( json.contains("\"filePath\":"), "Expected filePath (camelCase), got: {}", json ); // Verify roundtrip let deserialized: DownloadEvent = serde_json::from_str(&json).unwrap(); match deserialized { DownloadEvent::Completed { file_path, .. } => { assert_eq!(file_path, "/path/to/file.mp3"); } _ => panic!("Wrong variant"), } } #[test] fn test_download_event_failed() { let event = DownloadEvent::Failed { download_id: 10, item_id: "failed_item".to_string(), error: "Network timeout".to_string(), }; let json = serde_json::to_string(&event).unwrap(); assert!(json.contains("\"type\":\"failed\"")); // Verify roundtrip let deserialized: DownloadEvent = serde_json::from_str(&json).unwrap(); match deserialized { DownloadEvent::Failed { error, .. } => { assert_eq!(error, "Network timeout"); } _ => panic!("Wrong variant"), } } }