use std::sync::Arc;
use std::time::Duration;
use tokio::sync::Mutex;
use sz_orm_core::IsolationLevel;
use sz_orm_core::TxError;
use sz_orm_core::{TransactOptions, Transaction, TransactionState};
use crate::common::{InMemoryDb, MockConnection};
fn make_tx() -> Transaction {
let db = Arc::new(Mutex::new(InMemoryDb::new()));
let conn = MockConnection::new(db);
Transaction::new(Box::new(conn), TransactOptions::default())
}
#[tokio::test]
async fn test_new_transaction_is_active_contract() {
let tx = make_tx();
assert_eq!(tx.state(), TransactionState::Active);
assert!(tx.is_active());
}
#[tokio::test]
async fn test_commit_transitions_state_contract() -> Result<(), TxError> {
let mut tx = make_tx();
tx.commit().await?;
assert_eq!(tx.state(), TransactionState::Committed);
assert!(!tx.is_active());
Ok(())
}
#[tokio::test]
async fn test_rollback_transitions_state_contract() -> Result<(), TxError> {
let mut tx = make_tx();
tx.rollback().await?;
assert_eq!(tx.state(), TransactionState::RolledBack);
assert!(!tx.is_active());
Ok(())
}
#[tokio::test]
async fn test_double_commit_returns_not_active_contract() -> Result<(), TxError> {
let mut tx = make_tx();
tx.commit().await?;
let err = tx.commit().await.unwrap_err();
match err {
TxError::NotActive(state) => {
assert_eq!(state, TransactionState::Committed);
}
other => panic!("期望 NotActive(Committed),实际: {:?}", other),
}
Ok(())
}
#[tokio::test]
async fn test_rollback_after_commit_returns_not_active_contract() -> Result<(), TxError> {
let mut tx = make_tx();
tx.commit().await?;
let err = tx.rollback().await.unwrap_err();
match err {
TxError::NotActive(state) => {
assert_eq!(state, TransactionState::Committed);
}
other => panic!("期望 NotActive(Committed),实际: {:?}", other),
}
Ok(())
}
#[tokio::test]
async fn test_execute_after_commit_returns_not_active_contract() -> Result<(), TxError> {
let mut tx = make_tx();
tx.commit().await?;
let err = tx.execute("SELECT 1").await.unwrap_err();
assert!(matches!(err, TxError::NotActive(_)));
Ok(())
}
#[tokio::test]
async fn test_query_after_commit_returns_not_active_contract() -> Result<(), TxError> {
let mut tx = make_tx();
tx.commit().await?;
let err = tx.query("SELECT 1").await.unwrap_err();
assert!(matches!(err, TxError::NotActive(_)));
Ok(())
}
#[tokio::test]
async fn test_savepoint_after_commit_returns_not_active_contract() -> Result<(), TxError> {
let mut tx = make_tx();
tx.commit().await?;
let err = tx.savepoint().await.unwrap_err();
match err {
TxError::NotActive(state) => {
assert_eq!(state, TransactionState::Committed);
}
TxError::SavepointError(_) => {
panic!("v0.2.0 契约违反:commit 后 savepoint 应返回 NotActive,而非 SavepointError");
}
other => panic!("期望 NotActive(Committed),实际: {:?}", other),
}
Ok(())
}
#[tokio::test]
async fn test_savepoint_after_rollback_returns_not_active_contract() -> Result<(), TxError> {
let mut tx = make_tx();
tx.rollback().await?;
let err = tx.savepoint().await.unwrap_err();
assert!(matches!(err, TxError::NotActive(_)));
Ok(())
}
#[tokio::test]
async fn test_savepoint_name_format_contract() -> Result<(), TxError> {
let mut tx = make_tx();
let sp1 = tx.savepoint().await?;
assert_eq!(sp1, "sp_1");
let sp2 = tx.savepoint().await?;
assert_eq!(sp2, "sp_2");
let sp3 = tx.savepoint().await?;
assert_eq!(sp3, "sp_3");
Ok(())
}
#[tokio::test]
async fn test_savepoint_name_monotonic_increment_contract() -> Result<(), TxError> {
let db = Arc::new(Mutex::new(InMemoryDb::new()));
let conn = MockConnection::new(db);
let opts = TransactOptions::default().with_max_nesting_depth(20);
let mut tx = Transaction::new(Box::new(conn), opts);
let mut names = Vec::new();
for _ in 0..10 {
names.push(tx.savepoint().await?);
}
for (i, name) in names.iter().enumerate() {
assert_eq!(name, &format!("sp_{}", i + 1));
}
Ok(())
}
#[tokio::test]
async fn test_rollback_to_savepoint_contract() -> Result<(), TxError> {
let mut tx = make_tx();
let sp = tx.savepoint().await?;
assert_eq!(tx.state(), TransactionState::Active);
tx.rollback_to_savepoint(&sp).await?;
assert_eq!(tx.state(), TransactionState::Active);
tx.release_savepoint(&sp).await?;
assert!(tx.is_active());
Ok(())
}
#[tokio::test]
async fn test_rollback_to_invalid_savepoint_name_contract() {
let mut tx = make_tx();
let err = tx.rollback_to_savepoint("1bad").await.unwrap_err();
assert!(matches!(err, TxError::InvalidSavepointName(_)));
let err = tx.rollback_to_savepoint("bad-name!").await.unwrap_err();
assert!(matches!(err, TxError::InvalidSavepointName(_)));
let err = tx.rollback_to_savepoint("").await.unwrap_err();
assert!(matches!(err, TxError::InvalidSavepointName(_)));
}
#[tokio::test]
async fn test_release_savepoint_invalid_name_contract() {
let mut tx = make_tx();
let err = tx.release_savepoint("1bad").await.unwrap_err();
assert!(matches!(err, TxError::InvalidSavepointName(_)));
}
#[tokio::test]
async fn test_take_connection_in_active_returns_not_active_contract() {
let mut tx = make_tx();
let result = tx.take_connection().await;
match result {
Err(TxError::NotActive(state)) => {
assert_eq!(state, TransactionState::Active);
}
Err(other) => panic!("期望 NotActive(Active),实际: {:?}", other),
Ok(_) => panic!("Active 状态 take_connection 必须返回 Err,实际返回 Ok"),
}
}
#[tokio::test]
async fn test_take_connection_after_commit_contract() -> Result<(), TxError> {
let mut tx = make_tx();
tx.commit().await?;
let conn = tx.take_connection().await?;
assert!(conn.is_connected());
Ok(())
}
#[tokio::test]
async fn test_take_connection_after_rollback_contract() -> Result<(), TxError> {
let mut tx = make_tx();
tx.rollback().await?;
let conn = tx.take_connection().await?;
assert!(conn.is_connected());
Ok(())
}
#[tokio::test]
async fn test_take_connection_twice_returns_connection_taken_contract() -> Result<(), TxError> {
let mut tx = make_tx();
tx.commit().await?;
let _first = tx.take_connection().await?;
let result = tx.take_connection().await;
match result {
Err(TxError::ConnectionTaken) => { }
Err(other) => panic!("期望 ConnectionTaken,实际: {:?}", other),
Ok(_) => panic!("重复 take_connection 必须返回 Err,实际返回 Ok"),
}
Ok(())
}
#[test]
fn test_transact_options_default_contract() {
let opts = TransactOptions::default();
assert!(opts.isolation_level.is_none());
assert!(!opts.read_only);
assert!(opts.timeout.is_none());
}
#[test]
fn test_transact_options_with_isolation_contract() {
let opts = TransactOptions::default().with_isolation(IsolationLevel::Serializable);
assert_eq!(opts.isolation_level, Some(IsolationLevel::Serializable));
}
#[test]
fn test_transact_options_read_only_contract() {
let opts = TransactOptions::default().read_only();
assert!(opts.read_only);
}
#[test]
fn test_transact_options_with_timeout_contract() {
let opts = TransactOptions::default().with_timeout(Duration::from_secs(30));
assert_eq!(opts.timeout, Some(Duration::from_secs(30)));
}
#[test]
fn test_transact_options_chaining_contract() {
let opts = TransactOptions::default()
.with_isolation(IsolationLevel::RepeatableRead)
.read_only()
.with_timeout(Duration::from_secs(10));
assert_eq!(opts.isolation_level, Some(IsolationLevel::RepeatableRead));
assert!(opts.read_only);
assert_eq!(opts.timeout, Some(Duration::from_secs(10)));
}
#[tokio::test]
async fn test_options_and_state_dont_panic_contract() -> Result<(), TxError> {
let mut tx = make_tx();
assert_eq!(tx.state(), TransactionState::Active);
assert!(tx.options().isolation_level.is_none());
tx.commit().await?;
assert_eq!(tx.state(), TransactionState::Committed);
assert!(!tx.is_active());
assert!(tx.options().isolation_level.is_none());
Ok(())
}