testkit-core 0.1.1

Core utilities for testkit
Documentation
use async_trait::async_trait;
use std::fmt::Debug;
use std::marker::PhantomData;

use crate::testdb::{
    DatabaseBackend, DatabasePool, TestDatabaseConnection,
    transaction::{DBTransactionManager, DatabaseTransaction},
};

use crate::TestContext;

/// A helper trait to simplify type constraints
pub trait TransactionStarter<DB: DatabaseBackend> {
    /// The transaction type
    type Transaction: DatabaseTransaction<Error = DB::Error> + Send + Sync + 'static;
    /// The connection type
    type Connection: TestDatabaseConnection + Send + Sync + 'static;

    /// Begin a transaction
    fn begin_transaction_type() -> Self::Transaction;
}

// Implement TransactionStarter with DB-specific transaction types
impl<DB> TransactionStarter<DB> for TestContext<DB>
where
    DB: DatabaseBackend + Send + Sync + Debug + 'static,
{
    // Implementation details are hidden using a type from the backend
    type Transaction = MockTransactionFor<DB>;
    type Connection = MockConnectionFor<DB>;

    fn begin_transaction_type() -> Self::Transaction {
        // This is just for type inference, not actually used
        panic!("This method should never be called")
    }
}

// These are placeholder types to satisfy the compiler
// In a real implementation, you would use actual transaction types from your backend
pub struct MockTransactionFor<DB: DatabaseBackend>(PhantomData<DB>);
pub struct MockConnectionFor<DB: DatabaseBackend>(PhantomData<DB>);

// Implement the necessary traits for these types
#[async_trait]
impl<DB: DatabaseBackend> DatabaseTransaction for MockTransactionFor<DB> {
    type Error = DB::Error;

    async fn commit(&mut self) -> Result<(), Self::Error> {
        // Mock implementation
        Ok(())
    }

    async fn rollback(&mut self) -> Result<(), Self::Error> {
        // Mock implementation
        Ok(())
    }
}

impl<DB: DatabaseBackend> TestDatabaseConnection for MockConnectionFor<DB> {
    fn connection_string(&self) -> String {
        "mock".to_string()
    }
}

// We need an associated type to represent the transaction type
#[async_trait]
impl<DB, T, Conn> DBTransactionManager<T, Conn> for TestContext<DB>
where
    DB: DatabaseBackend + Send + Sync + Debug + 'static,
    T: DatabaseTransaction<Error = DB::Error> + Send + Sync + 'static,
    Conn: TestDatabaseConnection + Send + Sync + 'static,
    DB::Pool: DatabasePool<Connection = Conn, Error = DB::Error>,
{
    type Error = DB::Error;
    type Tx = T;

    /// Begin a new transaction
    async fn begin_transaction(&mut self) -> Result<Self::Tx, Self::Error> {
        // This needs to be implemented for your specific transaction type
        // For example with a PostgreSQL backend, you might do:
        // let conn = self.db.acquire_connection().await?;
        // let tx = conn.begin().await?;
        // Ok(tx)

        // As a placeholder, we'll return an error
        Err(From::from(
            "Transaction implementation is database-specific and must be provided for each backend"
                .to_string(),
        ))
    }

    /// Commit a transaction
    async fn commit_transaction(tx: &mut Self::Tx) -> Result<(), Self::Error> {
        tx.commit().await
    }

    /// Rollback a transaction
    async fn rollback_transaction(tx: &mut Self::Tx) -> Result<(), Self::Error> {
        tx.rollback().await
    }
}