//! Comprehensive coverage tests for data crate //! //! This test suite targets uncovered error paths, edge cases, and validation logic //! to improve overall test coverage toward 95% #![allow( clippy::absurd_extreme_comparisons, clippy::assertions_on_constants, clippy::assertions_on_result_states, clippy::clone_on_copy, clippy::decimal_literal_representation, clippy::doc_lazy_continuation, clippy::doc_markdown, clippy::double_comparisons, clippy::else_if_without_else, clippy::empty_drop, clippy::expect_used, clippy::field_reassign_with_default, clippy::format_push_string, clippy::if_then_some_else_none, clippy::indexing_slicing, clippy::integer_division, clippy::len_zero, clippy::let_and_return, clippy::let_underscore_must_use, clippy::manual_let_else, clippy::manual_range_contains, clippy::map_err_ignore, clippy::missing_const_for_fn, clippy::modulo_arithmetic, clippy::needless_range_loop, clippy::new_without_default, clippy::non_ascii_literal, clippy::nonminimal_bool, clippy::octal_escapes, clippy::overly_complex_bool_expr, clippy::redundant_clone, clippy::shadow_reuse, clippy::shadow_unrelated, clippy::similar_names, clippy::single_component_path_imports, clippy::single_match_else, clippy::str_to_string, clippy::string_slice, clippy::tests_outside_test_module, clippy::unnecessary_cast, clippy::unnecessary_get_then_check, clippy::unnecessary_unwrap, clippy::unseparated_literal_suffix, clippy::unwrap_or_default, clippy::unwrap_used, clippy::use_debug, clippy::useless_vec, clippy::wildcard_enum_match_arm, dead_code, deprecated, unreachable_pub, unused_assignments, unused_comparisons, unused_imports, unused_variables )] use chrono::Utc; use config::data_config::{ DataCompressionAlgorithm, DataStorageConfig, DataStorageFormat, DataValidationConfig, MissingDataHandling, OutlierDetectionMethod, }; use data::error::{DataError, ErrorSeverity}; // Import ConnectionState from traits module which is re-exported at providers level use data::providers::ConnectionState; use data::storage::StorageManager; use data::validation::{ DataValidator, ErrorSeverity as ValidationErrorSeverity, ValidationError, ValidationErrorType, ValidationWarning, ValidationWarningType, }; use std::collections::HashMap; // ============================================================================ // Error Module Tests - Testing Error Path Coverage // ============================================================================ #[test] fn test_error_helper_methods() { // Test all error creation helper methods let net_err = DataError::network("Connection refused"); assert!(matches!(net_err, DataError::Network { .. })); assert_eq!(net_err.category(), "NETWORK"); let fix_err = DataError::fix_protocol("Invalid FIX message"); assert!(matches!(fix_err, DataError::FixProtocol { .. })); assert_eq!(fix_err.category(), "FIX_PROTOCOL"); let auth_err = DataError::authentication("Invalid credentials"); assert!(matches!(auth_err, DataError::Authentication { .. })); assert_eq!(auth_err.severity(), ErrorSeverity::Critical); let msg_err = DataError::message_parsing("Malformed message"); assert!(matches!(msg_err, DataError::MessageParsing { .. })); let session_err = DataError::session("Session expired"); assert!(matches!(session_err, DataError::Session { .. })); let order_err = DataError::order("Order rejected"); assert!(matches!(order_err, DataError::Order { .. })); assert_eq!(order_err.severity(), ErrorSeverity::High); let internal_err = DataError::internal("Internal failure"); assert!(matches!(internal_err, DataError::Broker { .. })); } #[test] fn test_error_conversion_edge_cases() { // Test edge cases in error conversions let empty_config_err = DataError::configuration("", "Missing field"); assert!(matches!(empty_config_err, DataError::Configuration { .. })); let long_message = "a".repeat(1000); let long_err = DataError::network(&long_message); assert!(format!("{}", long_err).contains(&long_message)); } #[test] fn test_error_retryability_comprehensive() { // Comprehensive retryability testing assert!(DataError::Connection("test".to_string()).is_retryable()); assert!(DataError::RateLimit.is_retryable()); // Non-retryable errors assert!(!DataError::Compression("test".to_string()).is_retryable()); assert!(!DataError::InvalidFormat("test".to_string()).is_retryable()); assert!(!DataError::Conversion("test".to_string()).is_retryable()); assert!(!DataError::Unsupported("test".to_string()).is_retryable()); assert!(!DataError::NotImplemented("test".to_string()).is_retryable()); } #[test] fn test_error_severity_ordering() { // Test severity level ordering assert!(ErrorSeverity::Critical > ErrorSeverity::High); assert!(ErrorSeverity::High > ErrorSeverity::Medium); assert!(ErrorSeverity::Medium > ErrorSeverity::Low); // Test severity for various errors assert_eq!( DataError::FixProtocol { message: "test".to_string() } .severity(), ErrorSeverity::High ); assert_eq!( DataError::NotFound("test".to_string()).severity(), ErrorSeverity::Medium ); } #[test] fn test_error_categories_comprehensive() { // Test all error category mappings assert_eq!( DataError::Unsupported("test".to_string()).category(), "UNSUPPORTED" ); assert_eq!( DataError::NotImplemented("test".to_string()).category(), "NOT_IMPLEMENTED" ); assert_eq!( DataError::Initialization("test".to_string()).category(), "INITIALIZATION" ); assert_eq!( DataError::InvalidFormat("test".to_string()).category(), "INVALID_FORMAT" ); assert_eq!( DataError::Conversion("test".to_string()).category(), "CONVERSION" ); assert_eq!( DataError::InvalidParameter { field: "test".to_string(), message: "test".to_string() } .category(), "INVALID_PARAMETER" ); } #[test] fn test_error_api_variants() { // Test API error with and without status code let api_with_status = DataError::api("Rate limited", Some("429")); assert!(matches!(api_with_status, DataError::Api { .. })); let api_no_status = DataError::api("Unknown error", None::); assert!(matches!(api_no_status, DataError::Api { .. })); } #[test] fn test_error_subscription_helper() { let sub_err = DataError::subscription("Symbol not available"); assert!(matches!(sub_err, DataError::Subscription { .. })); assert_eq!(sub_err.category(), "SUBSCRIPTION"); } // ============================================================================ // Storage Module Tests - Edge Cases and Error Paths // ============================================================================ #[tokio::test] async fn test_storage_initialization_with_invalid_path() { let config = DataStorageConfig { format: DataStorageFormat::Parquet, compression: config::data_config::DataCompressionConfig { enabled: false, algorithm: DataCompressionAlgorithm::ZSTD, level: Some(3), }, path: "/invalid/nonexistent/path/with/no/permissions".to_string(), base_directory: std::path::PathBuf::from("/invalid/nonexistent/path/with/no/permissions"), partition_by: vec![], versioning: config::data_config::DataVersioningConfig { enabled: false, version_format: "v%Y%m%d_%H%M%S".to_string(), keep_versions: 5, }, retention: config::data_config::DataRetentionConfig::default(), }; // This should fail gracefully let result = StorageManager::new(config).await; // We expect this to fail on most systems if let Err(err) = result { assert!(matches!(err, DataError::Io(_))); } } #[tokio::test] async fn test_storage_with_empty_base_directory() { let temp_dir = std::env::temp_dir().join("foxhunt_test_empty"); let _ = std::fs::remove_dir_all(&temp_dir); // Clean up if exists let config = DataStorageConfig { format: DataStorageFormat::Parquet, compression: config::data_config::DataCompressionConfig { enabled: true, algorithm: DataCompressionAlgorithm::ZSTD, level: Some(3), }, path: temp_dir.to_string_lossy().to_string(), base_directory: temp_dir.clone(), partition_by: vec![], versioning: config::data_config::DataVersioningConfig { enabled: true, version_format: "v%Y%m%d_%H%M%S".to_string(), keep_versions: 5, }, retention: config::data_config::DataRetentionConfig::default(), }; let storage = StorageManager::new(config).await; assert!(storage.is_ok()); // Cleanup let _ = std::fs::remove_dir_all(&temp_dir); } #[tokio::test] async fn test_storage_compression_edge_cases() { let temp_dir = std::env::temp_dir().join("foxhunt_test_compression"); let _ = std::fs::remove_dir_all(&temp_dir); let config = DataStorageConfig { format: DataStorageFormat::Parquet, compression: config::data_config::DataCompressionConfig { enabled: true, algorithm: DataCompressionAlgorithm::ZSTD, level: Some(22), // Maximum compression level }, path: temp_dir.to_string_lossy().to_string(), base_directory: temp_dir.clone(), partition_by: vec![], versioning: config::data_config::DataVersioningConfig { enabled: false, version_format: "v%Y%m%d_%H%M%S".to_string(), keep_versions: 1, }, retention: config::data_config::DataRetentionConfig::default(), }; let storage = StorageManager::new(config).await; assert!(storage.is_ok()); // Cleanup let _ = std::fs::remove_dir_all(&temp_dir); } // ============================================================================ // Validation Module Tests - Comprehensive Edge Cases // ============================================================================ #[test] fn test_validation_error_severity_levels() { let _low = ValidationErrorSeverity::Low; let _medium = ValidationErrorSeverity::Medium; let high = ValidationErrorSeverity::High; let critical = ValidationErrorSeverity::Critical; // Test ordering (if PartialOrd is implemented) assert!(format!("{:?}", critical).contains("Critical")); assert!(format!("{:?}", high).contains("High")); } #[test] fn test_validation_error_types() { let error_types = vec![ ValidationErrorType::PriceOutlier, ValidationErrorType::VolumeOutlier, ValidationErrorType::InvalidPrice, ValidationErrorType::InvalidVolume, ValidationErrorType::TimestampGap, ValidationErrorType::TimestampDrift, ValidationErrorType::DuplicateRecord, ValidationErrorType::MissingField, ValidationErrorType::InvalidFormat, ValidationErrorType::BusinessLogicViolation, ValidationErrorType::ConsistencyViolation, ]; // Ensure all variants are covered for error_type in error_types { let debug_str = format!("{:?}", error_type); assert!(!debug_str.is_empty()); } } #[test] fn test_validation_warning_types() { let warning_types = vec![ ValidationWarningType::UnusualVolume, ValidationWarningType::UnusualPrice, ValidationWarningType::HighVolatility, ValidationWarningType::LowLiquidity, ValidationWarningType::StaleTrade, ValidationWarningType::WideBidAsk, ValidationWarningType::InfrequentUpdates, ]; // Ensure all variants are covered for warning_type in warning_types { let debug_str = format!("{:?}", warning_type); assert!(!debug_str.is_empty()); } } #[tokio::test] async fn test_validation_config_edge_cases() { // Test validation with extreme config values let config = DataValidationConfig { enable_price_validation: true, enable_volume_validation: true, price_threshold: 0.0, volume_threshold: 0.0, outlier_method: OutlierDetectionMethod::ZScore, max_price_change: f64::MAX, max_volume_change: f64::MAX, max_timestamp_drift: 0, // Zero gap price_validation: true, volume_validation: true, timestamp_validation: true, outlier_detection: true, missing_data_handling: MissingDataHandling::Skip, }; let validator = DataValidator::new(config); assert!(validator.is_ok()); } #[test] fn test_validation_error_with_none_fields() { let error = ValidationError { error_type: ValidationErrorType::MissingField, message: "Field is missing".to_string(), field: None, // Test None case value: None, // Test None case timestamp: Utc::now(), severity: ValidationErrorSeverity::High, }; assert!(error.field.is_none()); assert!(error.value.is_none()); } #[test] fn test_validation_warning_with_none_field() { let warning = ValidationWarning { warning_type: ValidationWarningType::UnusualVolume, message: "Volume spike detected".to_string(), field: None, // Test None case timestamp: Utc::now(), }; assert!(warning.field.is_none()); } // ============================================================================ // Provider Common Tests - Connection State and Metrics // ============================================================================ #[test] fn test_connection_state_transitions() { // Test all connection states let states = vec![ ConnectionState::Disconnected, ConnectionState::Connecting, ConnectionState::Connected, ConnectionState::Reconnecting, ConnectionState::Failed, ]; for state in states { let debug_str = format!("{:?}", state); assert!(!debug_str.is_empty()); } } // ============================================================================ // Utils Module Tests - Edge Cases // ============================================================================ #[test] fn test_utils_checksum_validation_empty() { // Test checksum validation with empty data use sha2::Digest; let data: Vec = vec![]; let checksum = sha2::Sha256::digest(&data); let checksum_hex = format!("{:x}", checksum); assert_eq!(checksum_hex.len(), 64); // SHA-256 produces 64 hex chars } #[test] fn test_utils_checksum_validation_large() { // Test checksum validation with large data use sha2::Digest; let data: Vec = vec![0xFF; 1_000_000]; // 1MB of data let checksum = sha2::Sha256::digest(&data); let checksum_hex = format!("{:x}", checksum); assert_eq!(checksum_hex.len(), 64); } // ============================================================================ // Data Type Tests - Edge Cases // ============================================================================ #[test] fn test_data_types_serialization_edge_cases() { use serde_json; // Test serialization of various data structures let mut tags: HashMap = HashMap::new(); tags.insert("key".to_string(), "value".to_string()); tags.insert("empty".to_string(), "".to_string()); tags.insert("unicode".to_string(), "🦀".to_string()); let json = serde_json::to_string(&tags).unwrap(); assert!(json.contains("key")); assert!(json.contains("empty")); assert!(json.contains("unicode")); } // ============================================================================ // Integration Tests - Error Recovery // ============================================================================ #[tokio::test] async fn test_error_recovery_pattern() { // Test error recovery pattern with retryable errors let mut attempt = 0; let max_attempts = 3; let result = loop { attempt += 1; let err = DataError::network("Temporary failure"); if err.is_retryable() && attempt < max_attempts { continue; } break if attempt < max_attempts { Ok(()) } else { Err(err) }; }; assert!(result.is_err()); } #[test] fn test_error_chaining() { // Test error chaining with From trait let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Access denied"); let data_err: DataError = io_err.into(); assert!(matches!(data_err, DataError::Io(_))); assert!(data_err.is_retryable()); } // ============================================================================ // Boundary Value Tests // ============================================================================ #[test] fn test_compression_level_boundaries() { // Test compression level boundaries let algorithms = vec![ DataCompressionAlgorithm::ZSTD, DataCompressionAlgorithm::LZ4, DataCompressionAlgorithm::GZIP, DataCompressionAlgorithm::None, ]; for algo in algorithms { let debug_str = format!("{:?}", algo); assert!(!debug_str.is_empty()); } } #[test] fn test_storage_format_variants() { let formats = vec![ DataStorageFormat::Parquet, DataStorageFormat::Arrow, DataStorageFormat::Json, DataStorageFormat::Csv, ]; for format in formats { let debug_str = format!("{:?}", format); assert!(!debug_str.is_empty()); } } // ============================================================================ // Concurrency Tests // ============================================================================ #[tokio::test] async fn test_concurrent_error_creation() { use tokio::task; let handles: Vec<_> = (0..10) .map(|i| task::spawn(async move { DataError::network(format!("Error {}", i)) })) .collect(); for handle in handles { let err = handle.await.unwrap(); assert!(matches!(err, DataError::Network { .. })); } } #[test] fn test_validation_error_serialization() { use serde_json; let error = ValidationError { error_type: ValidationErrorType::PriceOutlier, message: "Price exceeds threshold".to_string(), field: Some("price".to_string()), value: Some("999.99".to_string()), timestamp: Utc::now(), severity: ValidationErrorSeverity::High, }; let json = serde_json::to_string(&error).unwrap(); let deserialized: ValidationError = serde_json::from_str(&json).unwrap(); assert_eq!(error.message, deserialized.message); assert_eq!(error.field, deserialized.field); } // ============================================================================ // Performance and Stress Tests // ============================================================================ #[test] fn test_large_error_message() { let large_message = "x".repeat(10_000); let err = DataError::network(&large_message); let display = format!("{}", err); assert!(display.len() > 10_000); } #[test] fn test_many_tags() { let mut tags: HashMap = HashMap::new(); for i in 0..1000 { tags.insert(format!("key{}", i), format!("value{}", i)); } assert_eq!(tags.len(), 1000); } // ============================================================================ // Null and Empty Tests // ============================================================================ #[test] fn test_empty_string_errors() { let err = DataError::network(""); assert!(matches!(err, DataError::Network { .. })); let err = DataError::configuration("", ""); assert!(matches!(err, DataError::Configuration { .. })); }