//! Advanced Retry Strategy and Error Classification Tests //! //! This module provides comprehensive tests for: //! - RetryStrategy::calculate_delay (exponential backoff, linear delays, caps, jitter) //! - CommonError::severity (all error categories and variants) //! - CommonError::retry_strategy (retryable vs non-retryable errors) use common::error::{CommonError, ErrorCategory, ErrorSeverity, RetryStrategy}; // ============================================================================= // RETRY STRATEGY: CALCULATE_DELAY TESTS // ============================================================================= #[test] fn test_retry_strategy_calculate_delay_linear_basic() { let strategy = RetryStrategy::Linear { base_delay_ms: 100 }; // Attempt 0: 100ms * 0 = 0ms let delay_0 = strategy.calculate_delay(0).expect("should have delay"); assert_eq!(delay_0.as_millis(), 0); // Attempt 1: 100ms * 1 = 100ms let delay_1 = strategy.calculate_delay(1).expect("should have delay"); assert_eq!(delay_1.as_millis(), 100); // Attempt 5: 100ms * 5 = 500ms let delay_5 = strategy.calculate_delay(5).expect("should have delay"); assert_eq!(delay_5.as_millis(), 500); } #[test] fn test_retry_strategy_calculate_delay_exponential_basic() { let strategy = RetryStrategy::Exponential { base_delay_ms: 100, max_delay_ms: 10000 }; // Attempt 0: 100 * 2^0 = 100ms (with jitter 90-100ms) let delay_0 = strategy.calculate_delay(0).expect("should have delay"); assert!(delay_0.as_millis() >= 90 && delay_0.as_millis() <= 100); // Attempt 1: 100 * 2^1 = 200ms (with jitter 180-200ms) let delay_1 = strategy.calculate_delay(1).expect("should have delay"); assert!(delay_1.as_millis() >= 180 && delay_1.as_millis() <= 200); // Attempt 3: 100 * 2^3 = 800ms (with jitter 720-800ms) let delay_3 = strategy.calculate_delay(3).expect("should have delay"); assert!(delay_3.as_millis() >= 720 && delay_3.as_millis() <= 800); } #[test] fn test_retry_strategy_calculate_delay_exponential_capping() { let strategy = RetryStrategy::Exponential { base_delay_ms: 100, max_delay_ms: 1000 }; // Attempt 10: 100 * 2^10 = 102400ms, but capped at min(10) = 100 * 2^10, then max_delay_ms = 1000ms let delay_10 = strategy.calculate_delay(10).expect("should have delay"); assert!(delay_10.as_millis() >= 900 && delay_10.as_millis() <= 1000); // Attempt 15: still capped at 1000ms let delay_15 = strategy.calculate_delay(15).expect("should have delay"); assert!(delay_15.as_millis() >= 900 && delay_15.as_millis() <= 1000); // Attempt 20: still capped let delay_20 = strategy.calculate_delay(20).expect("should have delay"); assert!(delay_20.as_millis() >= 900 && delay_20.as_millis() <= 1000); } #[test] fn test_retry_strategy_calculate_delay_no_retry() { let strategy = RetryStrategy::NoRetry; assert!(strategy.calculate_delay(0).is_none()); assert!(strategy.calculate_delay(1).is_none()); assert!(strategy.calculate_delay(100).is_none()); } #[test] fn test_retry_strategy_calculate_delay_immediate() { let strategy = RetryStrategy::Immediate; let delay_0 = strategy.calculate_delay(0).expect("should have delay"); assert_eq!(delay_0.as_millis(), 0); let delay_5 = strategy.calculate_delay(5).expect("should have delay"); assert_eq!(delay_5.as_millis(), 0); } #[test] fn test_retry_strategy_calculate_delay_circuit_breaker() { let strategy = RetryStrategy::CircuitBreaker; let delay_0 = strategy.calculate_delay(0).expect("should have delay"); assert_eq!(delay_0.as_secs(), 30); let delay_3 = strategy.calculate_delay(3).expect("should have delay"); assert_eq!(delay_3.as_secs(), 30); } // ============================================================================= // ERROR SEVERITY: COMPREHENSIVE CATEGORY TESTS // ============================================================================= #[test] fn test_common_error_severity_database() { let err = CommonError::Database( common::database::DatabaseError::PoolExhausted ); assert_eq!(err.severity(), ErrorSeverity::Critical); } #[test] fn test_common_error_severity_configuration() { let err = CommonError::Configuration("Missing API key".to_owned()); assert_eq!(err.severity(), ErrorSeverity::Critical); } #[test] fn test_common_error_severity_network() { let err = CommonError::Network("Connection timeout".to_owned()); assert_eq!(err.severity(), ErrorSeverity::Error); } #[test] fn test_common_error_severity_validation() { let err = CommonError::Validation("Price out of range".to_owned()); assert_eq!(err.severity(), ErrorSeverity::Warn); } #[test] fn test_common_error_severity_timeout() { let err = CommonError::Timeout { actual_ms: 5000, max_ms: 3000 }; assert_eq!(err.severity(), ErrorSeverity::Error); } #[test] fn test_common_error_severity_service_critical_categories() { // Critical severity categories let err = CommonError::service(ErrorCategory::Critical, "critical issue"); assert_eq!(err.severity(), ErrorSeverity::Critical); let err = CommonError::service(ErrorCategory::FinancialSafety, "safety breach"); assert_eq!(err.severity(), ErrorSeverity::Critical); let err = CommonError::service(ErrorCategory::Authentication, "auth failed"); assert_eq!(err.severity(), ErrorSeverity::Critical); } #[test] fn test_common_error_severity_service_error_categories() { // Error severity categories let err = CommonError::service(ErrorCategory::Trading, "trade failed"); assert_eq!(err.severity(), ErrorSeverity::Error); let err = CommonError::service(ErrorCategory::RiskManagement, "risk violation"); assert_eq!(err.severity(), ErrorSeverity::Error); let err = CommonError::service(ErrorCategory::Database, "service db issue"); assert_eq!(err.severity(), ErrorSeverity::Error); } #[test] fn test_common_error_severity_service_warn_categories() { // Warn severity categories (catch-all) let err = CommonError::service(ErrorCategory::MarketData, "stale data"); assert_eq!(err.severity(), ErrorSeverity::Warn); let err = CommonError::service(ErrorCategory::System, "system warning"); assert_eq!(err.severity(), ErrorSeverity::Warn); let err = CommonError::service(ErrorCategory::Configuration, "config mismatch"); assert_eq!(err.severity(), ErrorSeverity::Warn); let err = CommonError::service(ErrorCategory::Validation, "validation warning"); assert_eq!(err.severity(), ErrorSeverity::Warn); let err = CommonError::service(ErrorCategory::Connection, "unstable connection"); assert_eq!(err.severity(), ErrorSeverity::Warn); let err = CommonError::service(ErrorCategory::RateLimit, "throttled"); assert_eq!(err.severity(), ErrorSeverity::Warn); let err = CommonError::service(ErrorCategory::Parse, "parse warning"); assert_eq!(err.severity(), ErrorSeverity::Warn); let err = CommonError::service(ErrorCategory::Subscription, "sub warning"); assert_eq!(err.severity(), ErrorSeverity::Warn); let err = CommonError::service(ErrorCategory::Broker, "broker warning"); assert_eq!(err.severity(), ErrorSeverity::Warn); let err = CommonError::service(ErrorCategory::MachineLearning, "ml warning"); assert_eq!(err.severity(), ErrorSeverity::Warn); let err = CommonError::service(ErrorCategory::Security, "security warning"); assert_eq!(err.severity(), ErrorSeverity::Warn); let err = CommonError::service(ErrorCategory::BusinessLogic, "logic warning"); assert_eq!(err.severity(), ErrorSeverity::Warn); let err = CommonError::service(ErrorCategory::Resource, "resource warning"); assert_eq!(err.severity(), ErrorSeverity::Warn); let err = CommonError::service(ErrorCategory::Development, "dev warning"); assert_eq!(err.severity(), ErrorSeverity::Warn); let err = CommonError::service(ErrorCategory::Risk, "risk warning"); assert_eq!(err.severity(), ErrorSeverity::Warn); let err = CommonError::service(ErrorCategory::ML, "ml warning"); assert_eq!(err.severity(), ErrorSeverity::Warn); let err = CommonError::service(ErrorCategory::Other, "misc issue"); assert_eq!(err.severity(), ErrorSeverity::Warn); } // ============================================================================= // RETRY STRATEGY: ERROR-SPECIFIC TESTS // ============================================================================= #[test] fn test_common_error_retry_strategy_database() { let err = CommonError::Database( common::database::DatabaseError::PoolExhausted ); assert!(matches!(err.retry_strategy(), RetryStrategy::Exponential { .. })); } #[test] fn test_common_error_retry_strategy_network() { let err = CommonError::Network("Connection reset".to_owned()); assert!(matches!(err.retry_strategy(), RetryStrategy::Linear { .. })); } #[test] fn test_common_error_retry_strategy_timeout() { let err = CommonError::Timeout { actual_ms: 5000, max_ms: 3000 }; assert!(matches!(err.retry_strategy(), RetryStrategy::Linear { .. })); } #[test] fn test_common_error_retry_strategy_non_retryable() { // Configuration errors should not be retried let err = CommonError::Configuration("Invalid config".to_owned()); assert!(matches!(err.retry_strategy(), RetryStrategy::NoRetry)); // Validation errors should not be retried let err = CommonError::Validation("Invalid input".to_owned()); assert!(matches!(err.retry_strategy(), RetryStrategy::NoRetry)); } #[test] fn test_common_error_retry_strategy_service_network() { let err = CommonError::service(ErrorCategory::Network, "network service error"); assert!(matches!(err.retry_strategy(), RetryStrategy::Linear { .. })); let err = CommonError::service(ErrorCategory::Connection, "connection service error"); assert!(matches!(err.retry_strategy(), RetryStrategy::Linear { .. })); } #[test] fn test_common_error_retry_strategy_service_rate_limit() { let err = CommonError::service(ErrorCategory::RateLimit, "rate limited"); assert!(matches!(err.retry_strategy(), RetryStrategy::Exponential { .. })); } #[test] fn test_common_error_retry_strategy_service_authentication() { let err = CommonError::service(ErrorCategory::Authentication, "auth failed"); assert!(matches!(err.retry_strategy(), RetryStrategy::NoRetry)); } #[test] fn test_common_error_retry_strategy_service_immediate() { let err = CommonError::service(ErrorCategory::MarketData, "stale market data"); assert!(matches!(err.retry_strategy(), RetryStrategy::Immediate)); let err = CommonError::service(ErrorCategory::Trading, "trade warning"); assert!(matches!(err.retry_strategy(), RetryStrategy::Immediate)); } // ============================================================================= // EDGE CASES AND BOUNDARY CONDITIONS // ============================================================================= #[test] fn test_retry_strategy_exponential_zero_attempt() { let strategy = RetryStrategy::Exponential { base_delay_ms: 50, max_delay_ms: 5000 }; let delay = strategy.calculate_delay(0).expect("should have delay"); // 50 * 2^0 = 50ms with jitter (45-50ms) assert!(delay.as_millis() >= 45 && delay.as_millis() <= 50); } #[test] fn test_retry_strategy_exponential_large_max_delay() { let strategy = RetryStrategy::Exponential { base_delay_ms: 100, max_delay_ms: 100000 }; // Attempt 5: 100 * 2^5 = 3200ms (no capping) let delay_5 = strategy.calculate_delay(5).expect("should have delay"); assert!(delay_5.as_millis() >= 2880 && delay_5.as_millis() <= 3200); } #[test] fn test_retry_strategy_linear_zero_delay() { let strategy = RetryStrategy::Linear { base_delay_ms: 0 }; let delay = strategy.calculate_delay(0).expect("should have delay"); assert_eq!(delay.as_millis(), 0); }