use mockall::mock;
use reinhardt_db::backends::{
Result,
backend::DatabaseBackend as BackendTrait,
connection::DatabaseConnection as BackendsConnection,
types::{DatabaseType, QueryResult, QueryValue, Row, TransactionExecutor},
};
use reinhardt_db::orm::{DatabaseBackend, DatabaseConnection};
use rstest::*;
use std::sync::Arc;
mock! {
pub DatabaseBackend {}
#[async_trait::async_trait]
impl BackendTrait for DatabaseBackend {
fn database_type(&self) -> DatabaseType;
fn placeholder(&self, index: usize) -> String;
fn supports_returning(&self) -> bool;
fn supports_on_conflict(&self) -> bool;
async fn execute(&self, sql: &str, params: Vec<QueryValue>) -> Result<QueryResult>;
async fn fetch_one(&self, sql: &str, params: Vec<QueryValue>) -> Result<Row>;
async fn fetch_all(&self, sql: &str, params: Vec<QueryValue>) -> Result<Vec<Row>>;
async fn fetch_optional(&self, sql: &str, params: Vec<QueryValue>) -> Result<Option<Row>>;
async fn begin(&self) -> Result<Box<dyn TransactionExecutor>>;
fn as_any(&self) -> &dyn std::any::Any;
}
}
unsafe impl Send for MockDatabaseBackend {}
unsafe impl Sync for MockDatabaseBackend {}
#[fixture]
pub fn mock_database() -> MockDatabaseBackend {
let mut mock = MockDatabaseBackend::new();
mock.expect_database_type()
.return_const(DatabaseType::Postgres);
mock.expect_placeholder()
.returning(|idx| format!("${}", idx));
mock.expect_supports_returning().return_const(true);
mock.expect_supports_on_conflict().return_const(true);
mock
}
#[fixture]
pub fn mock_connection() -> DatabaseConnection {
let mut mock = MockDatabaseBackend::new();
mock.expect_database_type()
.return_const(DatabaseType::Postgres);
mock.expect_placeholder()
.returning(|idx| format!("${}", idx));
mock.expect_supports_returning().return_const(true);
mock.expect_supports_on_conflict().return_const(true);
mock.expect_execute()
.returning(|_, _| Ok(QueryResult { rows_affected: 0 }));
mock.expect_fetch_all().returning(|_, _| Ok(Vec::new()));
mock.expect_fetch_one().returning(|_, _| {
let mut row = Row::new();
row.data.insert("count".to_string(), QueryValue::Int(0));
Ok(row)
});
mock.expect_fetch_optional().returning(|_, _| Ok(None));
let backends_conn = BackendsConnection::new(Arc::new(mock));
DatabaseConnection::new(DatabaseBackend::Postgres, backends_conn)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mock_database_default_expectations() {
let mock = mock_database();
assert_eq!(mock.database_type(), DatabaseType::Postgres);
assert_eq!(mock.placeholder(1), "$1");
assert!(mock.supports_returning());
assert!(mock.supports_on_conflict());
}
#[test]
fn test_mock_database_custom_expectations() {
let mut mock = MockDatabaseBackend::new();
mock.expect_database_type()
.return_const(DatabaseType::Mysql);
mock.expect_placeholder().returning(|_| "?".to_string());
assert_eq!(mock.database_type(), DatabaseType::Mysql);
assert_eq!(mock.placeholder(1), "?");
}
#[tokio::test]
async fn test_mock_execute_with_verification() {
let mut mock = MockDatabaseBackend::new();
mock.expect_execute()
.withf(|sql, params| sql.contains("INSERT INTO users") && params.len() == 2)
.times(1)
.returning(|_, _| Ok(QueryResult { rows_affected: 1 }));
let result = mock
.execute(
"INSERT INTO users (name, email) VALUES ($1, $2)",
vec![
QueryValue::String("Alice".to_string()),
QueryValue::String("alice@example.com".to_string()),
],
)
.await;
assert!(result.is_ok());
assert_eq!(result.unwrap().rows_affected, 1);
}
#[rstest]
fn test_mock_connection_fixture(mock_connection: DatabaseConnection) {
assert!(matches!(
mock_connection.backend(),
DatabaseBackend::Postgres
));
}
}