use std::sync::{Arc, Mutex};
use type_bridge_orm::OrmError;
use type_bridge_orm::TxType;
use type_bridge_orm::session::backend::{BoxFuture, DriverBackend, QueryResult, TransactionOps};
#[derive(Debug, Clone, PartialEq)]
pub enum MockEvent {
OpenTx(TxType),
Query(TxType, String),
Commit,
Rollback,
Close,
}
pub type EventLog = Arc<Mutex<Vec<MockEvent>>>;
pub struct MockMigrationBackend {
log: EventLog,
query_count: Arc<Mutex<usize>>,
fail_on_query_index: Option<usize>,
scripted_responses: Option<Arc<Mutex<Vec<QueryResult>>>>,
commit_count: Arc<Mutex<usize>>,
fail_on_commit_index: Option<usize>,
}
impl MockMigrationBackend {
pub fn new(fail_on_query_index: Option<usize>) -> (Self, EventLog) {
let log = Arc::new(Mutex::new(Vec::new()));
let backend = Self {
log: Arc::clone(&log),
query_count: Arc::new(Mutex::new(0)),
fail_on_query_index,
scripted_responses: None,
commit_count: Arc::new(Mutex::new(0)),
fail_on_commit_index: None,
};
(backend, log)
}
pub fn with_responses(responses: Vec<QueryResult>) -> (Self, EventLog) {
let log = Arc::new(Mutex::new(Vec::new()));
let backend = Self {
log: Arc::clone(&log),
query_count: Arc::new(Mutex::new(0)),
fail_on_query_index: None,
scripted_responses: Some(Arc::new(Mutex::new(responses))),
commit_count: Arc::new(Mutex::new(0)),
fail_on_commit_index: None,
};
(backend, log)
}
pub fn with_commit_failure(fail_on_commit_index: usize) -> (Self, EventLog) {
let (mut backend, log) = Self::new(None);
backend.fail_on_commit_index = Some(fail_on_commit_index);
(backend, log)
}
}
impl DriverBackend for MockMigrationBackend {
fn open_transaction(
&self,
_database: &str,
tx_type: TxType,
) -> BoxFuture<'_, Result<Box<dyn TransactionOps>, OrmError>> {
self.log.lock().unwrap().push(MockEvent::OpenTx(tx_type));
let log = Arc::clone(&self.log);
let query_count = Arc::clone(&self.query_count);
let fail_on = self.fail_on_query_index;
let scripted = self.scripted_responses.as_ref().map(Arc::clone);
let commit_count = Arc::clone(&self.commit_count);
let fail_on_commit_index = self.fail_on_commit_index;
Box::pin(async move {
let tx: Box<dyn TransactionOps> = Box::new(MockMigrationTransaction {
tx_type,
log,
query_count,
fail_on,
scripted_responses: scripted,
commit_count,
fail_on_commit_index,
});
Ok(tx)
})
}
fn is_open(&self) -> bool {
true
}
}
struct MockMigrationTransaction {
tx_type: TxType,
log: EventLog,
query_count: Arc<Mutex<usize>>,
fail_on: Option<usize>,
scripted_responses: Option<Arc<Mutex<Vec<QueryResult>>>>,
commit_count: Arc<Mutex<usize>>,
fail_on_commit_index: Option<usize>,
}
impl TransactionOps for MockMigrationTransaction {
fn query(&mut self, typeql: &str) -> BoxFuture<'_, Result<QueryResult, OrmError>> {
let idx = {
let mut count = self.query_count.lock().unwrap();
let current = *count;
*count += 1;
current
};
let typeql_owned = typeql.to_string();
let tx_type = self.tx_type;
self.log
.lock()
.unwrap()
.push(MockEvent::Query(tx_type, typeql_owned));
let response: Result<QueryResult, OrmError> =
if let Some(scripted) = &self.scripted_responses {
let mut responses = scripted.lock().unwrap();
if responses.is_empty() {
Ok(QueryResult::Ok)
} else {
Ok(responses.remove(0))
}
} else {
let should_fail = self.fail_on == Some(idx);
if should_fail {
Err(OrmError::Transaction(
"injected query failure for testing".to_string(),
))
} else {
Ok(QueryResult::Ok)
}
};
Box::pin(async move { response })
}
fn commit(&mut self) -> BoxFuture<'_, Result<(), OrmError>> {
self.log.lock().unwrap().push(MockEvent::Commit);
let index = {
let mut count = self.commit_count.lock().unwrap();
let index = *count;
*count += 1;
index
};
let fail = self.fail_on_commit_index == Some(index);
Box::pin(async move {
if fail {
Err(OrmError::Transaction(
"injected ambiguous commit response for testing".to_string(),
))
} else {
Ok(())
}
})
}
fn rollback(&mut self) -> BoxFuture<'_, Result<(), OrmError>> {
self.log.lock().unwrap().push(MockEvent::Rollback);
Box::pin(async { Ok(()) })
}
fn close(&mut self) -> BoxFuture<'_, Result<(), OrmError>> {
self.log.lock().unwrap().push(MockEvent::Close);
Box::pin(async { Ok(()) })
}
}