//! Error Conversion and Retry Logic Tests for Storage Crate //! //! This module provides comprehensive tests for: //! - StorageError to CommonError conversion (all 15 variants) //! - StorageError::retry_delay_ms (retryable vs non-retryable errors) //! - std::io::Error to StorageError conversion (all ErrorKind mappings) use storage::error::StorageError; use common::error::{CommonError, ErrorCategory}; use std::io::ErrorKind; // ============================================================================= // STORAGE ERROR TO COMMON ERROR CONVERSION // ============================================================================= #[test] fn test_storage_error_to_common_error_io() { let storage_err = StorageError::IoError { message: "disk full".to_string() }; let common_err: CommonError = storage_err.into(); assert_eq!(common_err.category(), ErrorCategory::System); } #[test] fn test_storage_error_to_common_error_network() { let storage_err = StorageError::NetworkError { message: "host unreachable".to_string() }; let common_err: CommonError = storage_err.into(); assert_eq!(common_err.category(), ErrorCategory::Network); } #[test] fn test_storage_error_to_common_error_auth() { let storage_err = StorageError::AuthError { message: "bad credentials".to_string() }; let common_err: CommonError = storage_err.into(); assert_eq!(common_err.category(), ErrorCategory::Security); } #[test] fn test_storage_error_to_common_error_config() { let storage_err = StorageError::ConfigError { message: "invalid path".to_string() }; let common_err: CommonError = storage_err.into(); assert_eq!(common_err.category(), ErrorCategory::Configuration); } #[test] fn test_storage_error_to_common_error_not_found() { let storage_err = StorageError::NotFound { path: "/data/model.bin".to_string() }; let common_err: CommonError = storage_err.into(); assert_eq!(common_err.category(), ErrorCategory::System); } #[test] fn test_storage_error_to_common_error_operation_failed() { use std::sync::Arc; let storage_err = StorageError::OperationFailed { operation: "write".to_string(), path: "/data/checkpoint.bin".to_string(), source: Arc::new(CommonError::Network("network failed".to_string())), }; let common_err: CommonError = storage_err.into(); assert_eq!(common_err.category(), ErrorCategory::System); } #[test] fn test_storage_error_to_common_error_permission_denied() { let storage_err = StorageError::PermissionDenied { path: "/etc/secrets".to_string() }; let common_err: CommonError = storage_err.into(); assert_eq!(common_err.category(), ErrorCategory::Security); } #[test] fn test_storage_error_to_common_error_timeout() { let storage_err = StorageError::Timeout { timeout_ms: 5000 }; let common_err: CommonError = storage_err.into(); assert_eq!(common_err.category(), ErrorCategory::System); } #[test] fn test_storage_error_to_common_error_rate_limited() { let storage_err = StorageError::RateLimited { retry_after_ms: 1000 }; let common_err: CommonError = storage_err.into(); assert_eq!(common_err.category(), ErrorCategory::System); } #[test] fn test_storage_error_to_common_error_quota_exceeded() { let storage_err = StorageError::QuotaExceeded { used: 1000, limit: 500 }; let common_err: CommonError = storage_err.into(); assert_eq!(common_err.category(), ErrorCategory::System); } #[test] fn test_storage_error_to_common_error_serialization() { let storage_err = StorageError::SerializationError { message: "invalid JSON".to_string() }; let common_err: CommonError = storage_err.into(); assert_eq!(common_err.category(), ErrorCategory::System); } #[test] fn test_storage_error_to_common_error_compression() { let storage_err = StorageError::CompressionError { message: "gzip failed".to_string() }; let common_err: CommonError = storage_err.into(); assert_eq!(common_err.category(), ErrorCategory::System); } #[test] fn test_storage_error_to_common_error_integrity_error() { let storage_err = StorageError::IntegrityError { path: "/data/model.bin".to_string(), expected: "abc123".to_string(), actual: "def456".to_string(), }; let common_err: CommonError = storage_err.into(); assert_eq!(common_err.category(), ErrorCategory::System); } #[test] fn test_storage_error_to_common_error_generic() { let storage_err = StorageError::Generic { message: "unknown error".to_string() }; let common_err: CommonError = storage_err.into(); assert_eq!(common_err.category(), ErrorCategory::System); } #[cfg(feature = "s3")] #[test] fn test_storage_error_to_common_error_s3() { let storage_err = StorageError::S3Error { message: "S3 operation failed".to_string() }; let common_err: CommonError = storage_err.into(); assert_eq!(common_err.category(), ErrorCategory::Network); } // ============================================================================= // RETRY DELAY TESTS // ============================================================================= #[test] fn test_storage_error_retry_delay_ms_network_error() { let err = StorageError::NetworkError { message: "connection failed".to_string() }; assert_eq!(err.retry_delay_ms(), Some(100)); } #[test] fn test_storage_error_retry_delay_ms_timeout() { let err = StorageError::Timeout { timeout_ms: 500 }; assert_eq!(err.retry_delay_ms(), Some(200)); } #[test] fn test_storage_error_retry_delay_ms_rate_limited() { let err = StorageError::RateLimited { retry_after_ms: 1500 }; assert_eq!(err.retry_delay_ms(), Some(1500)); } #[test] fn test_storage_error_retry_delay_ms_generic() { let err = StorageError::Generic { message: "generic error".to_string() }; assert_eq!(err.retry_delay_ms(), Some(100)); } #[cfg(feature = "s3")] #[test] fn test_storage_error_retry_delay_ms_s3() { let err = StorageError::S3Error { message: "S3 error".to_string() }; assert_eq!(err.retry_delay_ms(), Some(100)); } #[test] fn test_storage_error_retry_delay_ms_not_found() { let err = StorageError::NotFound { path: "/data/missing.bin".to_string() }; assert_eq!(err.retry_delay_ms(), None); } #[test] fn test_storage_error_retry_delay_ms_quota_exceeded() { let err = StorageError::QuotaExceeded { used: 1000, limit: 500 }; assert_eq!(err.retry_delay_ms(), None); } #[test] fn test_storage_error_retry_delay_ms_permission_denied() { let err = StorageError::PermissionDenied { path: "/etc/forbidden".to_string() }; assert_eq!(err.retry_delay_ms(), None); } #[test] fn test_storage_error_retry_delay_ms_serialization_error() { let err = StorageError::SerializationError { message: "bad JSON".to_string() }; assert_eq!(err.retry_delay_ms(), None); } #[test] fn test_storage_error_retry_delay_ms_integrity_error() { let err = StorageError::IntegrityError { path: "/data/file.bin".to_string(), expected: "abc".to_string(), actual: "def".to_string(), }; assert_eq!(err.retry_delay_ms(), None); } // ============================================================================= // IO ERROR TO STORAGE ERROR CONVERSION // ============================================================================= #[test] fn test_io_error_to_storage_error_not_found() { let io_err = std::io::Error::new(ErrorKind::NotFound, "file not found"); let storage_err: StorageError = io_err.into(); assert!(matches!(storage_err, StorageError::NotFound { .. })); if let StorageError::NotFound { path } = storage_err { assert_eq!(path, "unknown"); // Default value from conversion } } #[test] fn test_io_error_to_storage_error_permission_denied() { let io_err = std::io::Error::new(ErrorKind::PermissionDenied, "access denied"); let storage_err: StorageError = io_err.into(); assert!(matches!(storage_err, StorageError::PermissionDenied { .. })); if let StorageError::PermissionDenied { path } = storage_err { assert_eq!(path, "unknown"); // Default value from conversion } } #[test] fn test_io_error_to_storage_error_already_exists() { let io_err = std::io::Error::new(ErrorKind::AlreadyExists, "file exists"); let storage_err: StorageError = io_err.into(); // AlreadyExists maps to IoError in current implementation assert!(matches!(storage_err, StorageError::IoError { .. })); if let StorageError::IoError { message } = storage_err { assert!(message.contains("file exists")); } } #[test] fn test_io_error_to_storage_error_timed_out() { let io_err = std::io::Error::new(ErrorKind::TimedOut, "operation timed out"); let storage_err: StorageError = io_err.into(); assert!(matches!(storage_err, StorageError::Timeout { timeout_ms: 5000 })); } #[test] fn test_io_error_to_storage_error_broken_pipe() { let io_err = std::io::Error::new(ErrorKind::BrokenPipe, "pipe broken"); let storage_err: StorageError = io_err.into(); assert!(matches!(storage_err, StorageError::IoError { .. })); if let StorageError::IoError { message } = storage_err { assert!(message.contains("pipe broken")); } } #[test] fn test_io_error_to_storage_error_connection_reset() { let io_err = std::io::Error::new(ErrorKind::ConnectionReset, "connection reset"); let storage_err: StorageError = io_err.into(); assert!(matches!(storage_err, StorageError::IoError { .. })); if let StorageError::IoError { message } = storage_err { assert!(message.contains("connection reset")); } } #[test] fn test_io_error_to_storage_error_other() { let io_err = std::io::Error::new(ErrorKind::Other, "unknown error"); let storage_err: StorageError = io_err.into(); assert!(matches!(storage_err, StorageError::IoError { .. })); if let StorageError::IoError { message } = storage_err { assert!(message.contains("unknown error")); } } // ============================================================================= // EDGE CASES AND BOUNDARY CONDITIONS // ============================================================================= #[test] fn test_storage_error_empty_message() { let storage_err = StorageError::Generic { message: String::new() }; let common_err: CommonError = storage_err.into(); assert_eq!(common_err.category(), ErrorCategory::System); } #[test] fn test_storage_error_long_path() { let long_path = "a".repeat(1000); let storage_err = StorageError::NotFound { path: long_path.clone() }; assert!(matches!(storage_err, StorageError::NotFound { .. })); if let StorageError::NotFound { path } = storage_err { assert_eq!(path.len(), 1000); } } #[test] fn test_storage_error_rate_limited_zero_retry() { let err = StorageError::RateLimited { retry_after_ms: 0 }; assert_eq!(err.retry_delay_ms(), Some(0)); } #[test] fn test_storage_error_timeout_zero() { let err = StorageError::Timeout { timeout_ms: 0 }; assert_eq!(err.retry_delay_ms(), Some(200)); } #[test] fn test_storage_error_integrity_error_same_values() { let err = StorageError::IntegrityError { path: "/data/file.bin".to_string(), expected: "abc123".to_string(), actual: "abc123".to_string(), }; // Even with matching values, it's still an integrity error (logic error in caller) assert_eq!(err.retry_delay_ms(), None); }