//! Live validation smoke tests that exercise the database validators against //! real MySQL and Postgres instances provisioned with `testcontainers`. //! //! These are ignored by default because they require Docker. Run them with: //! `cargo test --test live_db_validation -- --ignored`. use std::time::{Duration, Instant}; use anyhow::{anyhow, Result}; use kingfisher::validation::{validate_mysql, validate_postgres}; use testcontainers::{clients::Cli, core::WaitFor, GenericImage}; use tokio::{net::TcpStream, time::sleep}; const HOST_ALIAS: &str = "kingfisherlocal"; const STARTUP_TIMEOUT: Duration = Duration::from_secs(60); const STARTUP_POLL_INTERVAL: Duration = Duration::from_millis(250); async fn wait_for_port(host: &str, port: u16) -> Result<()> { let deadline = Instant::now() + STARTUP_TIMEOUT; loop { match TcpStream::connect((host, port)).await { Ok(stream) => { drop(stream); return Ok(()); } Err(err) => { if Instant::now() >= deadline { return Err(anyhow!( "timed out after {:?} waiting for {host}:{port}: {err}", STARTUP_TIMEOUT, )); } sleep(STARTUP_POLL_INTERVAL).await; } } } } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[ignore] async fn validates_mysql_secret_against_testcontainer() -> Result<()> { let docker = Cli::default(); let image = GenericImage::new("mysql", "8.4") .with_env_var("MYSQL_ROOT_PASSWORD", "secret") .with_env_var("MYSQL_DATABASE", "app") .with_env_var("MYSQL_ROOT_HOST", "%") .with_wait_for(WaitFor::message_on_stdout("MySQL init process done. Ready for start up.")); let container = docker.run(image); let port = container.get_host_port_ipv4(3306); wait_for_port(HOST_ALIAS, port).await?; let uri = format!("mysql://root:secret@{HOST_ALIAS}:{port}/app"); let (is_valid, metadata) = validate_mysql(&uri, false).await?; assert!(is_valid, "expected MySQL validation to succeed, got {metadata:?}"); assert!( metadata.iter().any(|entry| entry.contains("user=root")), "expected user metadata in {metadata:?}" ); assert!( metadata.iter().any(|entry| entry.contains("database=app")), "expected database metadata in {metadata:?}" ); drop(container); drop(docker); Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[ignore] async fn validates_postgres_secret_against_testcontainer() -> Result<()> { let docker = Cli::default(); let image = GenericImage::new("postgres", "15") .with_env_var("POSTGRES_PASSWORD", "secret") .with_wait_for(WaitFor::message_on_stdout( "database system is ready to accept connections", )); let container = docker.run(image); let port = container.get_host_port_ipv4(5432); wait_for_port(HOST_ALIAS, port).await?; let uri = format!("postgres://postgres:secret@{HOST_ALIAS}:{port}/postgres"); let (is_valid, metadata) = validate_postgres(&uri, false).await?; assert!(is_valid, "expected Postgres validation to succeed"); assert!(metadata.is_empty(), "expected no metadata but found {metadata:?}"); drop(container); drop(docker); Ok(()) }