db_testkit/
macros.rs

1use std::future::Future;
2
3#[allow(unused)]
4use crate::{
5    backend::{Connection, DatabaseBackend, DatabasePool},
6    backends::PostgresBackend,
7    env::get_postgres_url,
8    pool::PoolConfig,
9    template::DatabaseTemplate,
10};
11
12#[cfg(feature = "mysql")]
13use crate::{backends::mysql::MySqlBackend, env::get_mysql_url};
14
15#[cfg(feature = "sqlx-postgres")]
16use crate::{backends::sqlx::SqlxPostgresBackend, env::get_sqlx_postgres_url};
17
18#[cfg(feature = "sqlx-sqlite")]
19use crate::{backends::sqlite::SqliteBackend, env::get_sqlite_url};
20
21/// A test database instance that provides access to a connection pool and test-specific data.
22///
23/// This struct is generic over the database backend type and provides methods for setting up
24/// and interacting with a test database instance.
25///
26/// # Type Parameters
27///
28/// * `'a` - The lifetime of the database pool reference
29/// * `B` - The database backend type that implements [`DatabaseBackend`]
30pub struct TestDatabase<'a, B: DatabaseBackend> {
31    /// The connection pool for this test database
32    pub test_pool: &'a B::Pool,
33    /// A unique identifier for the test user, useful for test data isolation
34    pub test_user: String,
35}
36
37impl<'a, B: DatabaseBackend> TestDatabase<'a, B> {
38    /// Sets up the test database by executing the provided setup function.
39    ///
40    /// This method acquires a connection from the pool, executes the setup function with that
41    /// connection, and returns the result.
42    ///
43    /// # Type Parameters
44    ///
45    /// * `F` - The setup function type
46    /// * `Fut` - The future type returned by the setup function
47    /// * `T` - The result type of the setup function
48    ///
49    /// # Arguments
50    ///
51    /// * `f` - A function that takes a database connection and returns a future
52    ///
53    /// # Returns
54    ///
55    /// Returns a `Result` containing the value returned by the setup function, or an error
56    /// if the setup failed.
57    pub async fn setup<F, Fut, T>(&self, f: F) -> Result<T, Box<dyn std::error::Error>>
58    where
59        F: FnOnce(B::Connection) -> Fut,
60        Fut: Future<Output = Result<T, Box<dyn std::error::Error>>>,
61    {
62        let conn = self.test_pool.acquire().await?;
63        let result = f(conn).await?;
64        Ok(result)
65    }
66}
67
68/// Creates a new PostgreSQL test database and executes the provided test function.
69///
70/// This macro handles the creation of a temporary database, executes the test function,
71/// and ensures proper cleanup after the test completes.
72///
73/// # Arguments
74///
75/// * `$f` - A function that takes a [`TestDatabase`] and returns a future
76///
77/// # Example
78///
79/// ```rust
80/// #[tokio::test]
81/// async fn test_users() {
82///     with_test_db(|db| async move {
83///         db.setup(|mut conn| async move {
84///             conn.execute("CREATE TABLE users (id SERIAL PRIMARY KEY)").await?;
85///             Ok(())
86///         }).await?;
87///         Ok(())
88///     }).await;
89/// }
90/// ```
91#[macro_export]
92macro_rules! with_test_db {
93    ($f:expr) => {{
94        let backend = PostgresBackend::new(&get_postgres_url().unwrap())
95            .await
96            .unwrap();
97        let template = DatabaseTemplate::new(backend, PoolConfig::default(), 1)
98            .await
99            .unwrap();
100
101        let db = template.get_immutable_database().await.unwrap();
102        let test_db = TestDatabase {
103            test_pool: db.get_pool(),
104            test_user: format!("test_user_{}", uuid::Uuid::new_v4()),
105        };
106
107        $f(test_db).await
108    }};
109}
110
111/// Creates a new MySQL test database and executes the provided test function.
112///
113/// Similar to [`with_test_db`], but uses MySQL as the backend.
114///
115/// # Arguments
116///
117/// * `$f` - A function that takes a [`TestDatabase`] and returns a future
118#[cfg(feature = "mysql")]
119#[macro_export]
120macro_rules! with_mysql_test_db {
121    ($f:expr) => {{
122        let backend = MySqlBackend::new(&get_mysql_url().unwrap()).await.unwrap();
123        let template = DatabaseTemplate::new(backend, PoolConfig::default(), 1)
124            .await
125            .unwrap();
126
127        let db = template.get_immutable_database().await.unwrap();
128        let test_db = TestDatabase {
129            test_pool: db.get_pool(),
130            test_user: format!("test_user_{}", uuid::Uuid::new_v4()),
131        };
132
133        $f(test_db).await
134    }};
135}
136
137/// Creates a new SQLx PostgreSQL test database and executes the provided test function.
138///
139/// Similar to [`with_test_db`], but uses SQLx's PostgreSQL implementation as the backend.
140///
141/// # Arguments
142///
143/// * `$f` - A function that takes a [`TestDatabase`] and returns a future
144#[cfg(feature = "sqlx-postgres")]
145#[macro_export]
146macro_rules! with_sqlx_test_db {
147    ($f:expr) => {{
148        let backend = SqlxPostgresBackend::new(&get_sqlx_postgres_url().unwrap())
149            .await
150            .unwrap();
151        let template = DatabaseTemplate::new(backend, PoolConfig::default(), 1)
152            .await
153            .unwrap();
154
155        let db = template.get_immutable_database().await.unwrap();
156        let test_db = TestDatabase {
157            test_pool: db.get_pool(),
158            test_user: format!("test_user_{}", uuid::Uuid::new_v4()),
159        };
160
161        $f(test_db).await
162    }};
163}
164
165/// Creates a new SQLite test database and executes the provided test function.
166///
167/// Similar to [`with_test_db`], but uses SQLite as the backend.
168///
169/// # Arguments
170///
171/// * `$f` - A function that takes a [`TestDatabase`] and returns a future
172#[cfg(feature = "sqlx-sqlite")]
173#[macro_export]
174macro_rules! with_sqlite_test_db {
175    ($f:expr) => {{
176        let backend = SqliteBackend::new(&get_sqlite_url().unwrap())
177            .await
178            .unwrap();
179        let template = DatabaseTemplate::new(backend, PoolConfig::default(), 1)
180            .await
181            .unwrap();
182
183        let db = template.get_immutable_database().await.unwrap();
184        let test_db = TestDatabase {
185            test_pool: db.get_pool(),
186            test_user: format!("test_user_{}", uuid::Uuid::new_v4()),
187        };
188
189        $f(test_db).await
190    }};
191}