#[cfg(test)]
mod test {
use async_trait::async_trait;
use futures_core::future::BoxFuture;
use futures_core::Stream;
use rbatis::executor::Executor;
use rbatis::intercept::{Intercept, ResultType};
use rbatis::{Action, Error, RBatis};
use rbdc::db::{ConnectOptions, Connection, Driver, ExecResult, Row};
use rbdc::rt::block_on;
use rbs::Value;
use std::pin::Pin;
use std::sync::Arc;
#[derive(Debug, Clone)]
struct MockDriver {}
impl Driver for MockDriver {
fn name(&self) -> &str {
"test"
}
fn connect(&self, _url: &str) -> BoxFuture<'_, Result<Box<dyn Connection>, Error>> {
Box::pin(async { Ok(Box::new(MockConnection {}) as Box<dyn Connection>) })
}
fn connect_opt<'a>(
&'a self,
_option: &'a dyn ConnectOptions,
) -> BoxFuture<'a, Result<Box<dyn Connection>, Error>> {
Box::pin(async { Ok(Box::new(MockConnection {}) as Box<dyn Connection>) })
}
fn default_option(&self) -> Box<dyn ConnectOptions> {
Box::new(MockConnectOptions {})
}
}
#[derive(Debug, Clone)]
struct FailingDriver {}
impl Driver for FailingDriver {
fn name(&self) -> &str {
"failing"
}
fn connect(&self, _url: &str) -> BoxFuture<'_, Result<Box<dyn Connection>, Error>> {
Box::pin(async { Err(Error::from("connection refused")) })
}
fn connect_opt<'a>(
&'a self,
_option: &'a dyn ConnectOptions,
) -> BoxFuture<'a, Result<Box<dyn Connection>, Error>> {
Box::pin(async { Err(Error::from("connection refused")) })
}
fn default_option(&self) -> Box<dyn ConnectOptions> {
Box::new(FailingConnectOptions {})
}
}
#[derive(Clone, Debug)]
struct MockConnection {}
impl Connection for MockConnection {
fn exec_rows(
&mut self,
_sql: &str,
_params: Vec<Value>,
) -> BoxFuture<
'_,
Result<Pin<Box<dyn Stream<Item = Result<Box<dyn Row>, Error>> + Send + '_>>, Error>,
> {
Box::pin(async { Err(Error::from("mock")) })
}
fn exec(
&mut self,
_sql: &str,
_params: Vec<Value>,
) -> BoxFuture<'_, Result<ExecResult, Error>> {
Box::pin(async {
Ok(ExecResult {
rows_affected: 1,
last_insert_id: Value::Null,
})
})
}
fn close(&mut self) -> BoxFuture<'_, Result<(), Error>> {
Box::pin(async { Ok(()) })
}
fn ping(&mut self) -> BoxFuture<'_, Result<(), Error>> {
Box::pin(async { Ok(()) })
}
}
#[derive(Clone, Debug)]
struct MockConnectOptions {}
impl ConnectOptions for MockConnectOptions {
fn connect(&self) -> BoxFuture<'_, Result<Box<dyn Connection>, Error>> {
Box::pin(async { Ok(Box::new(MockConnection {}) as Box<dyn Connection>) })
}
fn set_uri(&mut self, _uri: &str) -> Result<(), Error> {
Ok(())
}
}
#[derive(Clone, Debug)]
struct FailingConnectOptions {}
impl ConnectOptions for FailingConnectOptions {
fn connect(&self) -> BoxFuture<'_, Result<Box<dyn Connection>, Error>> {
Box::pin(async { Err(Error::from("connect failed")) })
}
fn set_uri(&mut self, _uri: &str) -> Result<(), Error> {
Ok(())
}
}
#[test]
fn test_init_empty_url_error_message() {
let rb = RBatis::new();
let result = rb.init(MockDriver {}, "");
assert!(result.is_err());
let msg = result.err().unwrap().to_string();
assert!(
msg.contains("url is empty"),
"Expected url is empty error, got: {}",
msg
);
}
#[test]
fn test_get_pool_not_initialized_error_message() {
let rb = RBatis::default(); let result = rb.get_pool();
assert!(result.is_err());
let msg = result.err().unwrap().to_string();
assert!(
msg.contains("pool not inited"),
"Expected pool not inited error, got: {}",
msg
);
}
#[tokio::test]
async fn test_acquire_not_initialized_error_message() {
let rb = RBatis::default();
let result = rb.acquire().await;
assert!(result.is_err());
let msg = result.err().unwrap().to_string();
assert!(
msg.contains("pool not inited"),
"Expected pool not inited error, got: {}",
msg
);
}
#[tokio::test]
async fn test_try_acquire_not_initialized_error_message() {
let rb = RBatis::default();
let result = rb.try_acquire().await;
assert!(result.is_err());
let msg = result.err().unwrap().to_string();
assert!(
msg.contains("pool not inited"),
"Expected pool not inited error, got: {}",
msg
);
}
#[tokio::test]
async fn test_exec_not_initialized_error_message() {
let rb = RBatis::default();
let result = rb.exec("select 1", vec![]).await;
assert!(result.is_err());
let msg = result.err().unwrap().to_string();
assert!(
msg.contains("pool not inited"),
"Expected pool not inited error, got: {}",
msg
);
}
#[tokio::test]
async fn test_query_not_initialized_error_message() {
let rb = RBatis::default();
let result = rb.query("select 1", vec![]).await;
assert!(result.is_err());
let msg = result.err().unwrap().to_string();
assert!(
msg.contains("pool not inited"),
"Expected pool not inited error, got: {}",
msg
);
}
#[tokio::test]
async fn test_exec_decode_not_initialized_error_message() {
let rb = RBatis::default();
let result: Result<i32, _> = rb.exec_decode("select 1", vec![]).await;
assert!(result.is_err());
let msg = result.err().unwrap().to_string();
assert!(
msg.contains("pool not inited"),
"Expected pool not inited error, got: {}",
msg
);
}
#[tokio::test]
async fn test_query_decode_deprecated_not_initialized() {
let rb = RBatis::default();
#[allow(deprecated)]
let result: Result<i32, _> = rb.query_decode("select 1", vec![]).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_acquire_begin_not_initialized() {
let rb = RBatis::default();
let result = rb.acquire_begin().await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_try_acquire_begin_not_initialized() {
let rb = RBatis::default();
let result = rb.try_acquire_begin().await;
assert!(result.is_err());
}
#[test]
fn test_driver_type_not_initialized() {
let rb = RBatis::default();
let result = rb.driver_type();
assert!(result.is_err());
}
#[test]
fn test_init_pool_twice_returns_error() {
use rbatis::DefaultPool;
use rbdc::pool::{ConnectionManager, Pool};
let rb = RBatis::new();
let opts = MockConnectOptions {};
let manager1 = ConnectionManager {
driver: Arc::new(Box::new(MockDriver {})),
option: Arc::new(Box::new(opts)),
};
let pool1 = DefaultPool::new(manager1).unwrap();
rb.init_pool(pool1).unwrap();
let opts2 = MockConnectOptions {};
let manager2 = ConnectionManager {
driver: Arc::new(Box::new(MockDriver {})),
option: Arc::new(Box::new(opts2)),
};
let pool2 = DefaultPool::new(manager2).unwrap();
let result = rb.init_pool(pool2);
assert!(result.is_err());
let msg = result.err().unwrap().to_string();
assert!(msg.contains("pool already initialized"));
}
#[tokio::test]
async fn test_rbatis_query_success() {
let rb = RBatis::new();
rb.init(MockDriver {}, "test").unwrap();
let result = rb.query("select 1", vec![]).await;
let _ = result; }
#[tokio::test]
async fn test_rbatis_exec_success() {
let rb = RBatis::new();
rb.init(MockDriver {}, "test").unwrap();
let result = rb.exec("insert into t values (1)", vec![]).await.unwrap();
assert_eq!(result.rows_affected, 1); }
#[derive(Debug)]
struct ErrorIntercept;
#[async_trait]
impl Intercept for ErrorIntercept {
async fn before(
&self,
_task_id: i64,
_rb: &dyn Executor,
_sql: &mut String,
_args: &mut Vec<Value>,
_result: ResultType<&mut Result<ExecResult, Error>, &mut Result<Value, Error>>,
) -> Result<Action, Error> {
Err(Error::from("intercept before error"))
}
}
#[tokio::test]
async fn test_intercept_before_error_propagates() {
let mut rb = RBatis::new();
rb.set_intercepts(vec![Arc::new(ErrorIntercept)]);
rb.init(MockDriver {}, "test").unwrap();
let result = rb.exec("select 1", vec![]).await;
assert!(result.is_err());
let msg = result.err().unwrap().to_string();
assert!(msg.contains("intercept before error"));
}
#[derive(Debug)]
struct AfterErrorIntercept {
pub call_count: Arc<std::sync::atomic::AtomicUsize>,
}
#[async_trait]
impl Intercept for AfterErrorIntercept {
async fn before(
&self,
_task_id: i64,
_rb: &dyn Executor,
_sql: &mut String,
_args: &mut Vec<Value>,
_result: ResultType<&mut Result<ExecResult, Error>, &mut Result<Value, Error>>,
) -> Result<Action, Error> {
Ok(Action::Next)
}
async fn after(
&self,
_task_id: i64,
_rb: &dyn Executor,
_sql: &mut String,
_args: &mut Vec<Value>,
_result: ResultType<&mut Result<ExecResult, Error>, &mut Result<Value, Error>>,
) -> Result<Action, Error> {
self.call_count
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
Err(Error::from("intercept after error"))
}
}
#[tokio::test]
async fn test_intercept_after_error_propagates_on_exec() {
let counter = Arc::new(std::sync::atomic::AtomicUsize::new(0));
let mut rb = RBatis::new();
rb.set_intercepts(vec![Arc::new(AfterErrorIntercept {
call_count: counter.clone(),
})]);
rb.init(MockDriver {}, "test").unwrap();
let result = rb.exec("select 1", vec![]).await;
assert!(result.is_err());
let msg = result.err().unwrap().to_string();
assert!(msg.contains("intercept after error"));
assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
}
#[test]
fn test_executor_id_for_rbatis() {
let rb = RBatis::new();
assert_eq!(Executor::id(&rb), 0);
}
#[test]
fn test_executor_name_for_rbatis() {
let rb = RBatis::new();
let name = Executor::name(&rb);
assert!(name.contains("RBatis"));
}
#[test]
fn test_sync_non_map_value_error() {
let f = async move {
let rb = RBatis::new();
rb.init(MockDriver {}, "test").unwrap();
let conn = rb.acquire().await.unwrap();
let table_data = rbs::Value::I32(42); let result = RBatis::sync(&conn, &rb, &table_data, "test_table").await;
assert!(result.is_err());
assert!(result
.err()
.unwrap()
.to_string()
.contains("not is an struct or map"));
};
block_on(f);
}
#[test]
fn test_rbatis_ref_driver_type_delegates() {
let rb = RBatis::new();
rb.init(MockDriver {}, "test").unwrap();
assert_eq!(rb.driver_type().unwrap(), "test");
}
#[test]
fn test_error_type_alias() {
fn accepts_error(_: rbatis::Error) {}
fn accepts_result(r: rbatis::Result<()>) {
let _ = r;
}
accepts_error(Error::from("test"));
accepts_result(Ok(()));
accepts_result(Err(Error::from("test")));
}
#[test]
fn test_action_debug() {
let next_fmt = format!("{:?}", Action::Next);
assert!(next_fmt.contains("Next"));
let return_fmt = format!("{:?}", Action::Return);
assert!(return_fmt.contains("Return"));
}
#[test]
fn test_result_type_debug_exec() {
let rt: ResultType<i32, String> = ResultType::Exec(42i32);
let fmt = format!("{:?}", rt);
assert!(fmt.contains("Exec"));
}
#[test]
fn test_result_type_debug_query() {
let rt: ResultType<i32, String> = ResultType::Query("hello".to_string());
let fmt = format!("{:?}", rt);
assert!(fmt.contains("Query"));
}
#[tokio::test]
async fn test_init_option_failing_connect() {
let rb = RBatis::new();
use rbatis::DefaultPool;
let result = rb.init_option::<FailingDriver, FailingConnectOptions, DefaultPool>(
FailingDriver {},
FailingConnectOptions {},
);
assert!(result.is_ok());
let acq_result = rb.acquire().await;
assert!(acq_result.is_err());
}
#[tokio::test]
async fn test_link_empty_url() {
let rb = RBatis::new();
let result = rb.link(MockDriver {}, "").await;
assert!(result.is_err());
assert!(result.err().unwrap().to_string().contains("url is empty"));
}
#[tokio::test]
async fn test_link_with_valid_url() {
let rb = RBatis::new();
let result = rb.link(MockDriver {}, "valid-url").await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_executor_trait_dispatch_exec() {
let rb = RBatis::new();
rb.init(MockDriver {}, "test").unwrap();
let result = Executor::exec(&rb, "insert into t values (1)", vec![]).await;
assert!(result.is_ok());
assert_eq!(result.unwrap().rows_affected, 1);
}
#[tokio::test]
async fn test_executor_trait_dispatch_query() {
let rb = RBatis::new();
rb.init(MockDriver {}, "test").unwrap();
let result = Executor::query(&rb, "select 1", vec![]).await;
let _ = result; }
}