kit_rs/database/
testing.rs

1//! Testing utilities for database operations
2//!
3//! Provides `TestDatabase` for setting up isolated test environments with
4//! in-memory SQLite databases and automatic migration support.
5//!
6//! # Example
7//!
8//! ```rust,ignore
9//! use kit::test_database;
10//!
11//! #[tokio::test]
12//! async fn test_create_user() {
13//!     let db = test_database!();
14//!
15//!     // Your test code here - actions using DB::connection()
16//!     // will automatically use this test database
17//! }
18//! ```
19
20use sea_orm::DatabaseConnection;
21use sea_orm_migration::MigratorTrait;
22
23use super::config::DatabaseConfig;
24use super::connection::DbConnection;
25use crate::container::testing::{TestContainer, TestContainerGuard};
26use crate::error::FrameworkError;
27
28/// Test database wrapper that provides isolated database environments
29///
30/// Each `TestDatabase` creates a fresh in-memory SQLite database with
31/// migrations applied. The database is automatically registered in the
32/// test container, so any code using `DB::connection()` or `#[inject] db: Database`
33/// will receive this test database.
34///
35/// When the `TestDatabase` is dropped, the test container is cleared,
36/// ensuring complete isolation between tests.
37///
38/// # Example
39///
40/// ```rust,ignore
41/// use kit::testing::TestDatabase;
42/// use crate::migrations::Migrator;
43///
44/// #[tokio::test]
45/// async fn test_user_creation() {
46///     let db = TestDatabase::fresh::<Migrator>().await.unwrap();
47///
48///     // Actions using DB::connection() automatically get this test database
49///     let action = CreateUserAction::new();
50///     let user = action.execute("test@example.com").await.unwrap();
51///
52///     // Query directly using db.conn()
53///     let found = users::Entity::find_by_id(user.id)
54///         .one(db.conn())
55///         .await
56///         .unwrap();
57///     assert!(found.is_some());
58/// }
59/// ```
60pub struct TestDatabase {
61    conn: DbConnection,
62    _guard: TestContainerGuard,
63}
64
65impl TestDatabase {
66    /// Create a fresh test database with migrations applied
67    ///
68    /// This creates an in-memory SQLite database, runs all migrations,
69    /// and registers the connection in the test container.
70    ///
71    /// # Type Parameters
72    ///
73    /// * `M` - The migrator type implementing `MigratorTrait`. Typically
74    ///   this is `crate::migrations::Migrator` from your application.
75    ///
76    /// # Errors
77    ///
78    /// Returns an error if:
79    /// - Database connection fails
80    /// - Migration execution fails
81    ///
82    /// # Example
83    ///
84    /// ```rust,ignore
85    /// use kit::testing::TestDatabase;
86    /// use crate::migrations::Migrator;
87    ///
88    /// #[tokio::test]
89    /// async fn test_example() {
90    ///     let db = TestDatabase::fresh::<Migrator>().await.unwrap();
91    ///     // ...
92    /// }
93    /// ```
94    pub async fn fresh<M: MigratorTrait>() -> Result<Self, FrameworkError> {
95        // 1. Create test container guard for isolation
96        let guard = TestContainer::fake();
97
98        // 2. Create in-memory SQLite database
99        let config = DatabaseConfig::builder()
100            .url("sqlite::memory:")
101            .max_connections(1)
102            .min_connections(1)
103            .logging(false)
104            .build();
105
106        let conn = DbConnection::connect(&config).await?;
107
108        // 3. Run migrations
109        M::up(conn.inner(), None)
110            .await
111            .map_err(|e| FrameworkError::database(format!("Migration failed: {}", e)))?;
112
113        // 4. Register in TestContainer - this is the key integration!
114        // Any code calling DB::connection() or App::resolve::<DbConnection>()
115        // will now get this test database
116        TestContainer::singleton(conn.clone());
117
118        Ok(Self { conn, _guard: guard })
119    }
120
121    /// Get a reference to the underlying database connection
122    ///
123    /// Use this when you need to execute queries directly in your tests.
124    ///
125    /// # Example
126    ///
127    /// ```rust,ignore
128    /// let db = test_database!();
129    /// let users = users::Entity::find().all(db.conn()).await?;
130    /// ```
131    pub fn conn(&self) -> &DatabaseConnection {
132        self.conn.inner()
133    }
134
135    /// Get the `DbConnection` wrapper
136    ///
137    /// Use this when you need the full `DbConnection` type.
138    pub fn db(&self) -> &DbConnection {
139        &self.conn
140    }
141}
142
143/// Create a test database with default migrator
144///
145/// This macro creates a `TestDatabase` using `crate::migrations::Migrator` as the
146/// default migrator. This follows the Kit convention where migrations are defined
147/// in `src/migrations/mod.rs`.
148///
149/// # Example
150///
151/// ```rust,ignore
152/// use kit::test_database;
153///
154/// #[tokio::test]
155/// async fn test_user_creation() {
156///     let db = test_database!();
157///
158///     let action = CreateUserAction::new();
159///     let user = action.execute("test@example.com").await.unwrap();
160///     assert!(user.id > 0);
161/// }
162/// ```
163///
164/// # With Custom Migrator
165///
166/// ```rust,ignore
167/// let db = test_database!(my_crate::CustomMigrator);
168/// ```
169#[macro_export]
170macro_rules! test_database {
171    () => {
172        $crate::testing::TestDatabase::fresh::<crate::migrations::Migrator>()
173            .await
174            .expect("Failed to set up test database")
175    };
176    ($migrator:ty) => {
177        $crate::testing::TestDatabase::fresh::<$migrator>()
178            .await
179            .expect("Failed to set up test database")
180    };
181}