Files
foxhunt/crates/common/tests/database_tests.rs
jgrusewski 9c3d741a08 refactor: restructure repo — crates/, bin/, testing/ layout
Move 17 library crates into crates/, CLI binary into bin/fxt,
consolidate 10 test crates into testing/, split config crate
from deployment config files.

Root directory reduced from 38+ to ~17 directories.
All Cargo.toml paths and build.rs proto refs updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 11:56:00 +01:00

381 lines
12 KiB
Rust

//! 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(_)));
}