#![allow(clippy::doc_markdown)]
use std::sync::Arc;
use testcontainers_modules::{
postgres::Postgres,
testcontainers::{runners::AsyncRunner, ContainerAsync, ImageExt},
};
use tokio::sync::OnceCell;
const SCHEMA_SQL: &str = include_str!("../fixtures/schema.sql");
const SEED_SQL: &str = include_str!("../fixtures/seed_data.sql");
static CONTAINER: OnceCell<Arc<TestContainer>> = OnceCell::const_new();
pub struct TestContainer {
#[allow(dead_code)]
container: ContainerAsync<Postgres>,
pub port: u16,
pub user: String,
pub password: String,
pub database: String,
}
impl TestContainer {
#[allow(dead_code)] pub fn connection_string(&self) -> String {
format!(
"postgres://{}:{}@127.0.0.1:{}/{}",
self.user, self.password, self.port, self.database
)
}
}
pub async fn get_test_container() -> Arc<TestContainer> {
CONTAINER
.get_or_init(|| async {
let container = start_postgres_container().await;
Arc::new(container)
})
.await
.clone()
}
async fn start_postgres_container() -> TestContainer {
let user = "testuser";
let password = "testpassword";
let database = "testdb";
let container = Postgres::default()
.with_user(user)
.with_password(password)
.with_db_name(database)
.with_env_var("POSTGRES_HOST_AUTH_METHOD", "scram-sha-256")
.with_env_var("POSTGRES_INITDB_ARGS", "--auth-host=scram-sha-256")
.start()
.await
.expect("Failed to start PostgreSQL container");
let port = container
.get_host_port_ipv4(5432)
.await
.expect("Failed to get container port");
let conn_string = format!(
"host=127.0.0.1 port={} user={} password={} dbname={}",
port, user, password, database
);
let (client, connection) = tokio_postgres::connect(&conn_string, tokio_postgres::NoTls)
.await
.expect("Failed to connect to container for setup");
tokio::spawn(async move {
if let Err(e) = connection.await {
eprintln!("Connection error during setup: {}", e);
}
});
client
.batch_execute(SCHEMA_SQL)
.await
.expect("Failed to create schema");
client
.batch_execute(SEED_SQL)
.await
.expect("Failed to seed data");
TestContainer {
container,
port,
user: user.to_string(),
password: password.to_string(),
database: database.to_string(),
}
}
#[allow(dead_code)] pub async fn connect_test_client() -> fraiseql_wire::error::Result<fraiseql_wire::FraiseClient> {
let container = get_test_container().await;
let conn_string = container.connection_string();
fraiseql_wire::FraiseClient::connect(&conn_string).await
}