#[cfg(feature = "testcontainers")]
use rstest::*;
#[cfg(feature = "testcontainers")]
use std::sync::Arc;
#[cfg(feature = "testcontainers")]
use testcontainers::{ContainerAsync, GenericImage, ImageExt, core::WaitFor, runners::AsyncRunner};
#[cfg(feature = "testcontainers")]
use crate::resource::{TeardownGuard, TestResource};
#[cfg(feature = "testcontainers")]
#[fixture]
pub async fn validator_test_db() -> (ContainerAsync<GenericImage>, Arc<sqlx::PgPool>, u16, String) {
let postgres = GenericImage::new("postgres", "17-alpine")
.with_wait_for(WaitFor::message_on_stderr(
"database system is ready to accept connections",
))
.with_wait_for(WaitFor::seconds(2)) .with_env_var("POSTGRES_HOST_AUTH_METHOD", "trust")
.with_env_var("POSTGRES_USER", "postgres")
.with_env_var("POSTGRES_DB", "test_db")
.start()
.await
.expect("Failed to start PostgreSQL container for validator tests");
let port = postgres
.get_host_port_ipv4(5432)
.await
.expect("Failed to get PostgreSQL port");
let database_url = format!("postgres://postgres@localhost:{}/test_db", port);
let mut retry_count = 0;
let max_retries = 3;
let pool = loop {
match sqlx::postgres::PgPoolOptions::new()
.max_connections(10) .min_connections(1)
.acquire_timeout(std::time::Duration::from_secs(20)) .idle_timeout(Some(std::time::Duration::from_secs(30)))
.max_lifetime(Some(std::time::Duration::from_secs(300)))
.connect(&database_url)
.await
{
Ok(pool) => break pool,
Err(e) if retry_count < max_retries => {
retry_count += 1;
eprintln!(
"Failed to connect to PostgreSQL (attempt {}/{}): {}",
retry_count, max_retries, e
);
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
Err(e) => panic!(
"Failed to connect to PostgreSQL after {} retries: {}",
max_retries, e
),
}
};
(postgres, Arc::new(pool), port, database_url)
}
#[cfg(feature = "testcontainers")]
pub struct ValidatorDbGuard;
#[cfg(feature = "testcontainers")]
impl TestResource for ValidatorDbGuard {
fn setup() -> Self {
Self
}
fn teardown(&mut self) {
}
}
#[cfg(feature = "testcontainers")]
#[fixture]
pub fn validator_db_guard() -> TeardownGuard<ValidatorDbGuard> {
TeardownGuard::new()
}
#[cfg(all(test, feature = "testcontainers"))]
mod tests {
use super::*;
use rstest::*;
#[rstest]
#[tokio::test]
async fn test_validator_test_db_connects(
#[future] validator_test_db: (ContainerAsync<GenericImage>, Arc<sqlx::PgPool>, u16, String),
) {
let (_container, pool, _port, _url) = validator_test_db.await;
let row: (i32,) = sqlx::query_as("SELECT 1")
.fetch_one(pool.as_ref())
.await
.expect("Failed to execute SELECT 1 on validator test db");
assert_eq!(row.0, 1);
}
#[rstest]
#[tokio::test]
async fn test_validator_test_db_url_format(
#[future] validator_test_db: (ContainerAsync<GenericImage>, Arc<sqlx::PgPool>, u16, String),
) {
let (_container, _pool, _port, url) = validator_test_db.await;
assert!(
url.starts_with("postgres://"),
"Expected database_url to start with 'postgres://', got: {}",
url
);
}
#[rstest]
fn test_validator_db_guard_lifecycle() {
let guard = validator_db_guard();
let _: &TeardownGuard<ValidatorDbGuard> = &guard;
}
}