//! Comprehensive tests for database module //! //! Tests cover: //! - Configuration creation and defaults //! - Config conversions from DatabaseConfig and BacktestingDatabaseConfig //! - Connection pool creation and management //! - Health checks //! - Pool statistics and utilization //! - Error handling use common::database::{ DatabaseError, DatabasePool, LocalDatabaseConfig, PerformanceConfig, PoolConfig, PoolStats, }; use config::database::DatabaseConfig; use config::database::{PoolConfig as ConfigPoolConfig, TransactionConfig}; use config::structures::BacktestingDatabaseConfig; use std::time::Duration; // ============================================================================ // Configuration Tests // ============================================================================ #[test] fn test_local_database_config_default() { let config = LocalDatabaseConfig::default(); assert_eq!( config.url, "postgresql://foxhunt:password@localhost:5432/foxhunt" ); assert_eq!(config.pool.max_connections, 50); assert_eq!(config.pool.min_connections, 10); assert_eq!(config.performance.query_timeout_micros, 800); assert!(config.performance.enable_prewarming); assert!(config.performance.enable_prepared_statements); } #[test] fn test_pool_config_default() { let config = PoolConfig::default(); assert_eq!(config.max_connections, 50); assert_eq!(config.min_connections, 10); assert_eq!(config.connect_timeout_ms, 100); assert_eq!(config.acquire_timeout_ms, 50); assert_eq!(config.max_lifetime_seconds, 3600); assert_eq!(config.idle_timeout_seconds, 300); } #[test] fn test_performance_config_default() { let config = PerformanceConfig::default(); assert_eq!(config.query_timeout_micros, 800); assert!(config.enable_prewarming); assert!(config.enable_prepared_statements); assert!(config.enable_slow_query_logging); assert_eq!(config.slow_query_threshold_micros, 1000); } #[test] fn test_from_database_config() { let db_config = DatabaseConfig { url: "postgresql://test:test@localhost:5432/test".to_string(), max_connections: 100, min_connections: 20, connect_timeout: Duration::from_millis(200), query_timeout: Duration::from_micros(1000), enable_query_logging: true, application_name: Some("test_app".to_string()), pool: ConfigPoolConfig::default(), transaction: TransactionConfig::default(), }; let local_config: LocalDatabaseConfig = db_config.into(); assert_eq!( local_config.url, "postgresql://test:test@localhost:5432/test" ); assert_eq!(local_config.pool.max_connections, 100); assert_eq!(local_config.pool.min_connections, 20); // 100 / 5 assert_eq!(local_config.pool.connect_timeout_ms, 100); // Capped at 100ms for HFT assert_eq!(local_config.performance.query_timeout_micros, 800); // Capped at 800μs for HFT assert!(local_config.performance.enable_slow_query_logging); } #[test] fn test_from_database_config_min_connections() { let db_config = DatabaseConfig { url: "postgresql://test:test@localhost:5432/test".to_string(), max_connections: 5, // Low value to test minimum min_connections: 2, connect_timeout: Duration::from_millis(50), query_timeout: Duration::from_micros(500), enable_query_logging: false, application_name: None, pool: ConfigPoolConfig::default(), transaction: TransactionConfig::default(), }; let local_config: LocalDatabaseConfig = db_config.into(); assert_eq!(local_config.pool.max_connections, 5); assert_eq!(local_config.pool.min_connections, 2); // max(5/5, 2) = 2 } #[test] fn test_from_backtesting_database_config() { let bt_config = BacktestingDatabaseConfig { database_url: "postgresql://bt:bt@localhost:5432/bt".to_string(), max_connections: Some(20), min_connections: Some(5), acquire_timeout_ms: Some(2000), statement_cache_capacity: Some(1000), enable_logging: Some(true), }; let local_config: LocalDatabaseConfig = bt_config.into(); assert_eq!(local_config.url, "postgresql://bt:bt@localhost:5432/bt"); assert_eq!(local_config.pool.max_connections, 20); assert_eq!(local_config.pool.min_connections, 5); // 20 / 4 assert_eq!(local_config.pool.connect_timeout_ms, 2000); assert_eq!(local_config.performance.query_timeout_micros, 10000); // 10ms for backtesting assert!(local_config.performance.enable_slow_query_logging); } #[test] fn test_from_backtesting_database_config_defaults() { let bt_config = BacktestingDatabaseConfig { database_url: "postgresql://bt:bt@localhost:5432/bt".to_string(), max_connections: None, min_connections: None, acquire_timeout_ms: None, statement_cache_capacity: None, enable_logging: None, }; let local_config: LocalDatabaseConfig = bt_config.into(); assert_eq!(local_config.pool.max_connections, 10); // Default assert_eq!(local_config.pool.min_connections, 2); // max(10/4, 2) = 2 assert_eq!(local_config.pool.connect_timeout_ms, 1000); // Default assert!(!local_config.performance.enable_slow_query_logging); // Default false } // ============================================================================ // Pool Statistics Tests // ============================================================================ #[test] fn test_pool_stats_utilization_percentage() { let stats = PoolStats { size: 50, idle: 30, active: 20, max_size: 50, }; assert_eq!(stats.utilization_percentage(), 40.0); // 20/50 * 100 } #[test] fn test_pool_stats_utilization_full() { let stats = PoolStats { size: 50, idle: 0, active: 50, max_size: 50, }; assert_eq!(stats.utilization_percentage(), 100.0); } #[test] fn test_pool_stats_utilization_zero() { let stats = PoolStats { size: 50, idle: 50, active: 0, max_size: 50, }; assert_eq!(stats.utilization_percentage(), 0.0); } #[test] fn test_pool_stats_is_healthy() { let stats = PoolStats { size: 50, idle: 30, active: 20, max_size: 50, }; assert!(stats.is_healthy()); // 40% < 80% } #[test] fn test_pool_stats_is_unhealthy() { let stats = PoolStats { size: 50, idle: 10, active: 40, max_size: 50, }; assert!(!stats.is_healthy()); // 80% >= 80% } #[test] fn test_pool_stats_is_unhealthy_critical() { let stats = PoolStats { size: 50, idle: 0, active: 50, max_size: 50, }; assert!(!stats.is_healthy()); // 100% > 80% } // ============================================================================ // Database Error Tests // ============================================================================ #[test] fn test_database_error_query_timeout_display() { let error = DatabaseError::QueryTimeout { actual_ms: 150, max_ms: 100, }; let error_str = error.to_string(); assert!(error_str.contains("Query timeout")); assert!(error_str.contains("150ms")); assert!(error_str.contains("100ms")); } #[test] fn test_database_error_pool_exhausted_display() { let error = DatabaseError::PoolExhausted; let error_str = error.to_string(); assert!(error_str.contains("Pool exhausted")); assert!(error_str.contains("no connections available")); } #[test] fn test_database_error_configuration_display() { let error = DatabaseError::Configuration("Invalid URL format".to_string()); let error_str = error.to_string(); assert!(error_str.contains("Configuration error")); assert!(error_str.contains("Invalid URL format")); } #[test] fn test_database_error_performance_display() { let error = DatabaseError::Performance("Query exceeded 1ms threshold".to_string()); let error_str = error.to_string(); assert!(error_str.contains("Performance violation")); assert!(error_str.contains("Query exceeded 1ms threshold")); } // ============================================================================ // Integration Tests (require database) // ============================================================================ // Note: These tests are marked with #[ignore] by default since they require // a running PostgreSQL instance. Run with: cargo test -- --ignored #[tokio::test] #[ignore = "requires PostgreSQL database"] async fn test_database_pool_creation() { let config = LocalDatabaseConfig { url: "postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string(), pool: PoolConfig { max_connections: 5, min_connections: 2, connect_timeout_ms: 5000, acquire_timeout_ms: 5000, max_lifetime_seconds: 3600, idle_timeout_seconds: 300, }, performance: PerformanceConfig { query_timeout_micros: 800, enable_prewarming: false, // Disable prewarming for faster test enable_prepared_statements: true, enable_slow_query_logging: true, slow_query_threshold_micros: 1000, }, }; let pool = DatabasePool::new(config).await; assert!( pool.is_ok(), "Failed to create database pool: {:?}", pool.err() ); } #[tokio::test] #[ignore = "requires PostgreSQL database"] async fn test_database_pool_health_check() { let config = LocalDatabaseConfig { url: "postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string(), ..Default::default() }; let pool = DatabasePool::new(config) .await .expect("Failed to create pool"); let health = pool.health_check().await; assert!(health.is_ok(), "Health check failed: {:?}", health.err()); } #[tokio::test] #[ignore = "requires PostgreSQL database"] async fn test_database_pool_stats() { let config = LocalDatabaseConfig { url: "postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string(), pool: PoolConfig { max_connections: 10, min_connections: 2, ..Default::default() }, performance: PerformanceConfig { enable_prewarming: false, ..Default::default() }, }; let pool = DatabasePool::new(config) .await .expect("Failed to create pool"); let stats = pool.pool_stats(); assert_eq!(stats.max_size, 10); assert!(stats.size <= 10); assert!(stats.idle <= stats.size); assert_eq!(stats.active, stats.size - stats.idle); } #[tokio::test] #[ignore = "requires PostgreSQL database"] async fn test_database_pool_invalid_url() { let config = LocalDatabaseConfig { url: "invalid-url-format".to_string(), ..Default::default() }; let result = DatabasePool::new(config).await; assert!(result.is_err()); if let Err(DatabaseError::Configuration(msg)) = result { assert!(msg.contains("Invalid URL")); } else { panic!("Expected Configuration error"); } } #[tokio::test] #[ignore = "requires PostgreSQL database"] async fn test_database_pool_connection_failure() { let config = LocalDatabaseConfig { url: "postgresql://invalid:invalid@localhost:9999/invalid".to_string(), pool: PoolConfig { connect_timeout_ms: 100, // Fast timeout ..Default::default() }, ..Default::default() }; let result = DatabasePool::new(config).await; assert!(result.is_err()); assert!(matches!(result.unwrap_err(), DatabaseError::Connection(_))); }