1use sqlx::PgPool;
2use testcontainers::{
3 GenericImage, ImageExt,
4 core::{ContainerAsync, WaitFor},
5 runners::AsyncRunner,
6};
7
8pub struct TestDb {
10 pub pool: PgPool,
11 database_url: String,
12 _container: ContainerAsync<GenericImage>,
13}
14
15impl TestDb {
16 pub async fn new() -> Result<Self, Box<dyn std::error::Error>> {
18 let image = GenericImage::new("postgres", "16")
19 .with_wait_for(WaitFor::message_on_stderr(
20 "database system is ready to accept connections",
21 ))
22 .with_env_var("POSTGRES_USER", "pgdrift")
23 .with_env_var("POSTGRES_PASSWORD", "pgdrift_test")
24 .with_env_var("POSTGRES_DB", "pgdrift_test");
25
26 let container = image.start().await?;
27 let port = container.get_host_port_ipv4(5432).await?;
28
29 let database_url = format!(
30 "postgres://pgdrift:pgdrift_test@127.0.0.1:{}/pgdrift_test",
31 port
32 );
33
34 let mut attempts = 0;
36 let pool = loop {
37 match PgPool::connect(&database_url).await {
38 Ok(p) => break p,
39 Err(_) if attempts < 30 => {
40 attempts += 1;
41 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
42 }
43 Err(e) => return Err(Box::new(e)),
44 }
45 };
46
47 Ok(TestDb {
48 pool,
49 database_url,
50 _container: container,
51 })
52 }
53
54 pub fn database_url(&self) -> &str {
56 &self.database_url
57 }
58
59 pub async fn cleanup(&self) -> Result<(), sqlx::Error> {
61 crate::fixtures::cleanup(&self.pool).await
62 }
63}