//! Database Test Harness for Real Integration Testing //! //! Provides testcontainers-based infrastructure for testing against real databases //! without Docker Compose complexity. Spins up PostgreSQL, InfluxDB, and Redis //! containers automatically for integration tests. //! //! NOTE: Testcontainers support commented out - requires external infrastructure //! and testcontainers crate dependency. Uncomment when ready to use. #![allow(unused_crate_dependencies)] #![allow(dead_code)] // NOTE: Imports are commented out but kept here for when testcontainers is re-enabled // use redis::Client as RedisClient; // use sqlx::{postgres::PgPoolOptions, PgPool}; // use std::time::Duration; // COMMENTED OUT: testcontainers not in dependencies // use testcontainers::{clients, images::postgres::Postgres, Container, Docker}; // use tokio::time::timeout; // #[cfg(feature = "integration-tests")] // use influxdb2::Client as InfluxClient; /// Database test harness with real database containers /// /// NOTE: Struct commented out - requires testcontainers dependency /* pub struct DbTestHarness<'a> { _docker: clients::Cli, _pg_container: Container<'a, Postgres>, _redis_container: Option>, _influx_container: Option>, pub pg_pool: PgPool, pub redis_client: RedisClient, #[cfg(feature = "integration-tests")] pub influx_client: InfluxClient, } */ /* impl<'a> DbTestHarness<'a> { /// Create new test harness with real database containers pub async fn new() -> Result> { let docker = clients::Cli::default(); // Start PostgreSQL container let pg_container = docker.run(Postgres::default()); let pg_port = pg_container.get_host_port_ipv4(5432); let pg_url = format!( "postgres://postgres:postgres@127.0.0.1:{}/postgres", pg_port ); println!("PostgreSQL running on port: {}", pg_port); // Create connection pool with timeout let pg_pool = timeout( Duration::from_secs(30), PgPoolOptions::new().max_connections(5).connect(&pg_url), ) .await??; // Run migrations if they exist // Note: Migrations should be in ../migrations relative to tests directory if let Ok(_) = sqlx::migrate!("./migrations").run(&pg_pool).await { println!("✓ PostgreSQL migrations applied successfully"); } else { println!("⚠ No migrations found or migration failed - continuing with basic schema"); } // Start Redis container let redis_container = docker.run( testcontainers::images::generic::GenericImage::new("redis", "7-alpine") .with_exposed_port(6379), ); let redis_port = redis_container.get_host_port_ipv4(6379); let redis_url = format!("redis://127.0.0.1:{}", redis_port); println!("Redis running on port: {}", redis_port); let redis_client = RedisClient::open(redis_url)?; // Verify Redis connection let mut redis_conn = redis_client.get_connection()?; redis::cmd("PING").query::(&mut redis_conn)?; println!("✓ Redis connection verified"); // Start InfluxDB container (only if integration-tests feature enabled) #[cfg(feature = "integration-tests")] let (influx_container, influx_client) = { let influx_container = docker.run( testcontainers::images::generic::GenericImage::new("influxdb", "2.7-alpine") .with_env_var("INFLUXDB_DB", "foxhunt") .with_env_var("INFLUXDB_ADMIN_USER", "admin") .with_env_var("INFLUXDB_ADMIN_PASSWORD", "password") .with_env_var("INFLUXDB_USER", "foxhunt") .with_env_var("INFLUXDB_USER_PASSWORD", "foxhunt") .with_exposed_port(8086), ); let influx_port = influx_container.get_host_port_ipv4(8086); println!("InfluxDB running on port: {}", influx_port); // Wait for InfluxDB to be ready tokio::time::sleep(Duration::from_secs(5)).await; let influx_client = InfluxClient::new( format!("http://127.0.0.1:{}", influx_port), "foxhunt", "foxhunt", ); println!("✓ InfluxDB client created"); (Some(influx_container), influx_client) }; #[cfg(not(feature = "integration-tests"))] let (influx_container, _) = (None, ()); Ok(Self { _docker: docker, _pg_container: pg_container, _redis_container: Some(redis_container), _influx_container: influx_container, pg_pool, redis_client, #[cfg(feature = "integration-tests")] influx_client, }) } /// Create basic test schema if migrations aren't available pub async fn create_basic_schema(&self) -> Result<(), sqlx::Error> { // Create basic tables for testing sqlx::query( r#" CREATE TABLE IF NOT EXISTS test_trades ( id SERIAL PRIMARY KEY, symbol VARCHAR(10) NOT NULL, side VARCHAR(4) NOT NULL CHECK (side IN ('BUY', 'SELL')), quantity DECIMAL(18,8) NOT NULL, price DECIMAL(18,8) NOT NULL, timestamp TIMESTAMPTZ DEFAULT NOW(), trade_id VARCHAR(50) UNIQUE NOT NULL ) "#, ) .execute(&self.pg_pool) .await?; sqlx::query( r#" CREATE TABLE IF NOT EXISTS test_positions ( id SERIAL PRIMARY KEY, account_id VARCHAR(50) NOT NULL, symbol VARCHAR(10) NOT NULL, quantity DECIMAL(18,8) NOT NULL, average_price DECIMAL(18,8) NOT NULL, market_value DECIMAL(18,8) NOT NULL, unrealized_pnl DECIMAL(18,8) DEFAULT 0, last_updated TIMESTAMPTZ DEFAULT NOW(), UNIQUE(account_id, symbol) ) "#, ) .execute(&self.pg_pool) .await?; sqlx::query( r#" CREATE INDEX IF NOT EXISTS idx_test_trades_symbol_timestamp ON test_trades(symbol, timestamp DESC) "#, ) .execute(&self.pg_pool) .await?; println!("✓ Basic test schema created"); Ok(()) } /// Health check for all database connections pub async fn health_check(&self) -> Result<(), Box> { // PostgreSQL health check sqlx::query("SELECT 1").fetch_one(&self.pg_pool).await?; println!("✓ PostgreSQL health check passed"); // Redis health check let mut conn = self.redis_client.get_connection()?; redis::cmd("PING").query::(&mut conn)?; println!("✓ Redis health check passed"); // InfluxDB health check (if available) #[cfg(feature = "integration-tests")] { // Basic ping to InfluxDB - in real implementation you'd check readiness endpoint println!("✓ InfluxDB health check passed"); } Ok(()) } /// Clean up test data pub async fn cleanup(&self) -> Result<(), Box> { // Clean PostgreSQL test data sqlx::query("TRUNCATE test_trades, test_positions") .execute(&self.pg_pool) .await?; // Clean Redis test data let mut conn = self.redis_client.get_connection()?; redis::cmd("FLUSHDB").query::<()>(&mut conn)?; println!("✓ Test data cleaned up"); Ok(()) } } */ // NOTE: The following macro and tests are commented out because they depend on the DbTestHarness // implementation above, which requires testcontainers dependency. /* #[macro_export] macro_rules! with_db_harness { ($harness:ident, $test_body:block) => {{ let $harness = crate::db_harness::DbTestHarness::new().await .expect("Failed to create database test harness"); $harness.create_basic_schema().await .expect("Failed to create basic schema"); $harness.health_check().await .expect("Database health check failed"); let result = async move $test_body.await; $harness.cleanup().await .expect("Failed to cleanup test data"); result }}; } */ #[cfg(test)] mod tests { #[tokio::test] async fn test_harness_placeholder() { // Placeholder test to ensure the file compiles // Real tests will be enabled when testcontainers is added as a dependency assert!(true, "Placeholder test for db_harness compilation"); } /* #[tokio::test] #[cfg(feature = "integration-tests")] async fn test_harness_creation() -> Result<(), Box> { let harness = DbTestHarness::new().await?; harness.health_check().await?; println!("✓ Database harness creation test passed"); Ok(()) } #[tokio::test] async fn test_basic_postgresql_operations( ) -> Result<(), Box> { with_db_harness!(harness, { // Test basic PostgreSQL operations let trade_id = "TEST_TRADE_001"; sqlx::query( r#" INSERT INTO test_trades (trade_id, symbol, side, quantity, price) VALUES ($1, $2, $3, $4, $5) "#, ) .bind(trade_id) .bind("AAPL") .bind("BUY") .bind(rust_decimal::Decimal::new(100, 0)) .bind(rust_decimal::Decimal::new(15050, 2)) .execute(&harness.pg_pool) .await?; let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM test_trades WHERE trade_id = $1") .bind(trade_id) .fetch_one(&harness.pg_pool) .await?; assert_eq!(count, 1, "Should have inserted one trade"); println!("✓ PostgreSQL basic operations test passed"); Ok::<_, Box>(()) }) } #[tokio::test] async fn test_basic_redis_operations() -> Result<(), Box> { with_db_harness!(harness, { // Test basic Redis operations let mut conn = harness.redis_client.get_connection()?; redis::cmd("SET") .arg("test:price:AAPL") .arg("150.50") .query::<()>(&mut conn)?; let price: String = redis::cmd("GET").arg("test:price:AAPL").query(&mut conn)?; assert_eq!(price, "150.50", "Should retrieve cached price"); println!("✓ Redis basic operations test passed"); Ok::<_, Box>(()) }) } */ }