#![cfg(feature = "sqlite")]
use dbnexus::{DbError, DbPool};
#[path = "../../common/mod.rs"]
mod common;
async fn make_pool() -> DbPool {
common::make_sqlite_memory_pool().await
}
#[tokio::test]
async fn test_session_role_returns_creation_role() {
let pool = make_pool().await;
let session = pool.get_session("admin").await.unwrap();
assert_eq!(session.role(), "admin");
}
#[cfg(feature = "permission")]
#[tokio::test]
async fn test_session_permission_ctx_returns_context() {
let pool = make_pool().await;
let session = pool.get_session("admin").await.unwrap();
let ctx = session.permission_ctx();
assert_eq!(ctx.role(), "admin");
}
#[tokio::test]
async fn test_session_is_in_transaction_initial_false() {
let pool = make_pool().await;
let session = pool.get_session("admin").await.unwrap();
assert!(!session.is_in_transaction().await);
}
#[tokio::test]
async fn test_session_begin_transaction_succeeds() {
let pool = make_pool().await;
let session = pool.get_session("admin").await.unwrap();
session.begin_transaction().await.expect("begin should succeed");
assert!(session.is_in_transaction().await);
}
#[tokio::test]
async fn test_session_commit_after_begin() {
let pool = make_pool().await;
let session = pool.get_session("admin").await.unwrap();
session.begin_transaction().await.unwrap();
session.commit().await.expect("commit should succeed");
assert!(!session.is_in_transaction().await);
}
#[tokio::test]
async fn test_session_rollback_after_begin() {
let pool = make_pool().await;
let session = pool.get_session("admin").await.unwrap();
session.begin_transaction().await.unwrap();
session.rollback().await.expect("rollback should succeed");
assert!(!session.is_in_transaction().await);
}
#[tokio::test]
async fn test_session_double_begin_fails() {
let pool = make_pool().await;
let session = pool.get_session("admin").await.unwrap();
session.begin_transaction().await.unwrap();
let result = session.begin_transaction().await;
assert!(result.is_err(), "double begin should fail");
let err = result.unwrap_err();
assert!(
matches!(err, DbError::Transaction(ref msg) if msg.contains("Already in transaction")),
"expected Transaction 'Already in transaction' error, got {:?}",
err
);
}
#[tokio::test]
async fn test_session_commit_without_transaction_fails() {
let pool = make_pool().await;
let session = pool.get_session("admin").await.unwrap();
let result = session.commit().await;
assert!(result.is_err(), "commit without transaction should fail");
let err = result.unwrap_err();
assert!(
matches!(err, DbError::Transaction(ref msg) if msg.contains("No active transaction")),
"expected Transaction 'No active transaction' error, got {:?}",
err
);
}
#[tokio::test]
async fn test_session_rollback_without_transaction_fails() {
let pool = make_pool().await;
let session = pool.get_session("admin").await.unwrap();
let result = session.rollback().await;
assert!(result.is_err(), "rollback without transaction should fail");
let err = result.unwrap_err();
assert!(
matches!(err, DbError::Transaction(ref msg) if msg.contains("Not in transaction")),
"expected Transaction 'Not in transaction' error, got {:?}",
err
);
}
#[tokio::test]
async fn test_session_execute_raw_ddl_admin_success() {
let pool = make_pool().await;
let session = pool.get_session("admin").await.unwrap();
let table = common::generate_test_table_name("ddl_test");
let sql = format!("CREATE TABLE {} (id INTEGER PRIMARY KEY, name TEXT)", table);
let result = session.execute_raw_ddl(&sql).await;
assert!(
result.is_ok(),
"admin execute_raw_ddl should succeed: {:?}",
result.err()
);
}
#[tokio::test]
async fn test_session_execute_raw_ddl_non_admin_fails() {
let pool = make_pool().await;
let session = pool.get_session("system").await.unwrap();
let table = common::generate_test_table_name("ddl_fail");
let sql = format!("CREATE TABLE {} (id INTEGER PRIMARY KEY)", table);
let result = session.execute_raw_ddl(&sql).await;
assert!(result.is_err(), "non-admin execute_raw_ddl should fail");
let err = result.unwrap_err();
assert!(
matches!(err, DbError::Permission(ref msg) if msg.contains("admin role")),
"expected Permission 'admin role' error, got {:?}",
err
);
}
#[tokio::test]
async fn test_session_execute_raw_ddl_drop_table() {
let pool = make_pool().await;
let session = pool.get_session("admin").await.unwrap();
let table = common::generate_test_table_name("drop_test");
let create_sql = format!("CREATE TABLE {} (id INTEGER PRIMARY KEY)", table);
session.execute_raw_ddl(&create_sql).await.unwrap();
let drop_sql = format!("DROP TABLE {}", table);
let result = session.execute_raw_ddl(&drop_sql).await;
assert!(result.is_err(), "DROP TABLE should be rejected by DdlGuard");
let err = result.unwrap_err();
assert!(
matches!(err, DbError::Permission(ref msg) if msg.contains("DropTable")),
"expected DdlGuard rejection for DropTable, got {:?}",
err
);
}
#[cfg(feature = "sql-parser")]
#[tokio::test]
async fn test_session_execute_raw_rejects_ddl_under_sql_parser() {
let pool = make_pool().await;
let session = pool.get_session("admin").await.unwrap();
let table = common::generate_test_table_name("raw_ddl_reject");
let sql = format!("CREATE TABLE {} (id INTEGER PRIMARY KEY)", table);
let result = session.execute_raw(&sql).await;
assert!(
result.is_err(),
"execute_raw with DDL should be rejected under sql-parser"
);
let err = result.unwrap_err();
assert!(
matches!(err, DbError::Permission(ref msg) if msg.contains("DDL")),
"expected Permission DDL error, got {:?}",
err
);
}
#[cfg(not(feature = "sql-parser"))]
#[tokio::test]
async fn test_session_execute_raw_requires_sql_parser_feature() {
let pool = make_pool().await;
let session = pool.get_session("admin").await.unwrap();
let result = session.execute_raw("SELECT 1").await;
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(err, DbError::Permission(ref msg) if msg.contains("sql-parser")),
"expected 'requires sql-parser feature' error, got {:?}",
err
);
}
#[cfg(feature = "sql-parser")]
#[tokio::test]
async fn test_session_execute_raw_select_admin_success() {
let pool = make_pool().await;
let session = pool.get_session("admin").await.unwrap();
let table = common::generate_test_table_name("select_test");
let create_sql = format!("CREATE TABLE {} (id INTEGER PRIMARY KEY, val TEXT)", table);
session.execute_raw_ddl(&create_sql).await.unwrap();
let insert_sql = format!("INSERT INTO {} (val) VALUES ('hello')", table);
session.execute_raw(&insert_sql).await.unwrap();
let select_sql = format!("SELECT val FROM {} WHERE val = 'hello'", table);
let result = session.execute_raw(&select_sql).await;
assert!(
result.is_ok(),
"admin execute_raw SELECT should succeed: {:?}",
result.err()
);
}
#[cfg(feature = "sql-parser")]
#[tokio::test]
async fn test_session_batch_execute_multiple_inserts() {
let pool = make_pool().await;
let session = pool.get_session("admin").await.unwrap();
let table = common::generate_test_table_name("batch_ins");
let create_sql = format!("CREATE TABLE {} (id INTEGER PRIMARY KEY, val TEXT)", table);
session.execute_raw_ddl(&create_sql).await.unwrap();
let insert1 = format!("INSERT INTO {} (val) VALUES ('a')", table);
let insert2 = format!("INSERT INTO {} (val) VALUES ('b')", table);
let results = session.batch_execute(vec![insert1.as_str(), insert2.as_str()]).await;
assert!(results.is_ok(), "batch_execute should succeed: {:?}", results.err());
let results = results.unwrap();
assert_eq!(results.len(), 2, "should return 2 results");
for (i, r) in results.iter().enumerate() {
assert!(r.is_ok(), "result {} should be ok: {:?}", i, r.as_ref().err());
}
}
#[cfg(feature = "sql-parser")]
#[tokio::test]
async fn test_session_batch_execute_in_transaction_success() {
let pool = make_pool().await;
let session = pool.get_session("admin").await.unwrap();
let table = common::generate_test_table_name("tx_batch_ok");
let create_sql = format!("CREATE TABLE {} (id INTEGER PRIMARY KEY, val TEXT)", table);
session.execute_raw_ddl(&create_sql).await.unwrap();
let insert1 = format!("INSERT INTO {} (val) VALUES ('a')", table);
let insert2 = format!("INSERT INTO {} (val) VALUES ('b')", table);
let results = session
.batch_execute_in_transaction(vec![insert1.as_str(), insert2.as_str()])
.await;
assert!(
results.is_ok(),
"batch in transaction should succeed: {:?}",
results.err()
);
assert_eq!(results.unwrap().len(), 2);
assert!(!session.is_in_transaction().await);
}
#[cfg(feature = "sql-parser")]
#[tokio::test]
async fn test_session_batch_execute_in_transaction_atomicity() {
let pool = make_pool().await;
let session = pool.get_session("admin").await.unwrap();
let table = common::generate_test_table_name("tx_batch_fail");
let create_sql = format!("CREATE TABLE {} (id INTEGER PRIMARY KEY, val TEXT)", table);
session.execute_raw_ddl(&create_sql).await.unwrap();
let insert_ok = format!("INSERT INTO {} (val) VALUES ('ok')", table);
let bad_sql = "THIS IS NOT VALID SQL";
let result = session
.batch_execute_in_transaction(vec![insert_ok.as_str(), bad_sql])
.await;
assert!(result.is_err(), "batch with invalid SQL should fail");
assert!(!session.is_in_transaction().await);
}
#[tokio::test]
async fn test_session_check_table_permission_admin_bypass() {
let pool = make_pool().await;
let session = pool.get_session("admin").await.unwrap();
let result = session.check_table_permission("any_table", "SELECT").await;
assert!(
result.is_ok(),
"admin should bypass permission check: {:?}",
result.err()
);
}
#[tokio::test]
async fn test_session_should_use_master_initial_false() {
let pool = make_pool().await;
let session = pool.get_session("admin").await.unwrap();
let _ = session.should_use_master().await;
}
#[tokio::test]
async fn test_session_should_use_master_true_in_transaction() {
let pool = make_pool().await;
let session = pool.get_session("admin").await.unwrap();
session.begin_transaction().await.unwrap();
assert!(
session.should_use_master().await,
"should use master when in transaction"
);
}
#[tokio::test]
async fn test_session_mark_write_affects_should_use_master() {
let pool = make_pool().await;
let session = pool.get_session("admin").await.unwrap();
session.mark_write().await;
assert!(session.should_use_master().await, "should use master after mark_write");
}
#[tokio::test]
async fn test_session_drop_releases_connection() {
let pool = make_pool().await;
let status_before = pool.status();
{
let _session = pool.get_session("admin").await.unwrap();
} let status_after = pool.status();
assert!(
status_after.active <= status_before.active,
"active should not increase after session drop"
);
}
#[tokio::test]
async fn test_session_serial_acquire_release() {
let pool = make_pool().await;
for i in 0..3 {
let session = pool.get_session("admin").await;
assert!(session.is_ok(), "iteration {} should succeed", i);
drop(session);
}
}