#![cfg(feature = "sqlite")]
use dbnexus::{DbConfig, DbPool, DbPoolBuilder};
#[path = "../../common/mod.rs"]
mod common;
#[tokio::test]
async fn test_db_pool_new_sqlite_memory() {
let pool = DbPool::new("sqlite::memory:").await;
assert!(pool.is_ok(), "DbPool::new should succeed for sqlite::memory:");
let pool = pool.unwrap();
let status = pool.status();
assert!(status.total >= 1, "pool should have at least 1 connection after warmup, got {}", status.total);
}
#[tokio::test]
async fn test_db_pool_with_config() {
let config = DbConfig {
url: "sqlite::memory:".to_string(),
max_connections: 10,
min_connections: 1,
..Default::default()
};
let pool = DbPool::with_config(config).await;
assert!(pool.is_ok(), "with_config should succeed");
}
#[tokio::test]
async fn test_db_pool_try_from_config() {
let config = DbConfig {
url: "sqlite::memory:".to_string(),
..Default::default()
};
let pool = DbPool::try_from_config(config).await;
assert!(pool.is_ok(), "try_from_config should succeed");
}
#[tokio::test]
async fn test_db_pool_new_invalid_url_fails() {
let result = DbPool::new("invalid://nonexistent").await;
assert!(result.is_err(), "invalid URL should fail");
}
#[tokio::test]
async fn test_db_pool_builder_with_url() {
let pool = DbPoolBuilder::new()
.url("sqlite::memory:")
.build()
.await;
assert!(pool.is_ok(), "builder with url should succeed");
}
#[tokio::test]
async fn test_db_pool_builder_with_config() {
let config = DbConfig {
url: "sqlite::memory:".to_string(),
max_connections: 5,
..Default::default()
};
let pool = DbPoolBuilder::new().config(config).build().await;
assert!(pool.is_ok(), "builder with config should succeed");
let pool = pool.unwrap();
assert_eq!(pool.config().max_connections, 5);
}
#[tokio::test]
async fn test_db_pool_builder_max_min_connections() {
let pool = DbPoolBuilder::new()
.url("sqlite::memory:")
.max_connections(15)
.min_connections(2)
.build()
.await;
assert!(pool.is_ok());
let pool = pool.unwrap();
assert_eq!(pool.config().max_connections, 15);
assert_eq!(pool.config().min_connections, 2);
}
#[tokio::test]
async fn test_db_pool_builder_admin_role() {
let pool = DbPoolBuilder::new()
.url("sqlite::memory:")
.admin_role("root")
.build()
.await;
assert!(pool.is_ok());
let pool = pool.unwrap();
assert_eq!(pool.config().admin_role, "root");
}
#[tokio::test]
async fn test_db_pool_status_initial() {
let config = DbConfig {
url: "sqlite::memory:".to_string(),
min_connections: 0,
max_connections: 20,
..Default::default()
};
let pool = DbPool::with_config(config).await.unwrap();
let status = pool.status();
assert_eq!(status.total, 0);
assert_eq!(status.active, 0);
assert_eq!(status.idle, 0);
}
#[tokio::test]
async fn test_db_pool_status_invariant_total_equals_active_plus_idle() {
let pool = common::make_sqlite_memory_pool().await;
let status = pool.status();
assert_eq!(
status.total,
status.active + status.idle,
"invariant: total == active + idle"
);
}
#[tokio::test]
async fn test_db_pool_config_returns_built_config() {
let config = DbConfig {
url: "sqlite::memory:".to_string(),
max_connections: 7,
admin_role: "sa".to_string(),
..Default::default()
};
let pool = DbPool::with_config(config).await.unwrap();
assert_eq!(pool.config().max_connections, 7);
assert_eq!(pool.config().admin_role, "sa");
assert_eq!(pool.config().url, "sqlite::memory:");
}
#[tokio::test]
async fn test_db_pool_get_actual_config() {
let pool = common::make_sqlite_memory_pool().await;
let cfg1 = pool.config();
let cfg2 = pool.get_actual_config();
assert_eq!(cfg1.url, cfg2.url);
assert_eq!(cfg1.max_connections, cfg2.max_connections);
}
#[tokio::test]
async fn test_db_pool_get_session_admin() {
let pool = common::make_sqlite_memory_pool().await;
let session = pool.get_session("admin").await;
assert!(session.is_ok(), "admin role should always be allowed");
let session = session.unwrap();
assert_eq!(session.role(), "admin");
}
#[tokio::test]
async fn test_db_pool_get_session_system() {
let pool = common::make_sqlite_memory_pool().await;
let session = pool.get_session("system").await;
assert!(session.is_ok(), "system role should be allowed without permission config");
}
#[cfg(feature = "permission")]
#[tokio::test]
async fn test_db_pool_get_session_unauthorized_role_fails() {
let pool = common::make_sqlite_memory_pool().await;
let result = pool.get_session("guest").await;
assert!(result.is_err(), "non-safe role should be rejected without permission config");
match result {
Err(dbnexus::DbError::Permission(_)) => { }
Err(other) => panic!("expected Permission error, got {:?}", other),
Ok(_) => panic!("expected error, got Ok"),
}
}
#[tokio::test]
async fn test_db_pool_get_session_role_matches() {
let pool = common::make_sqlite_memory_pool().await;
let session = pool.get_session("admin").await.unwrap();
assert_eq!(session.role(), "admin");
drop(session);
}
#[tokio::test]
async fn test_db_pool_clone_usable() {
let pool = common::make_sqlite_memory_pool().await;
let cloned = pool.clone();
let session = cloned.get_session("admin").await;
assert!(session.is_ok(), "cloned pool should be usable");
}
#[tokio::test]
async fn test_db_pool_clone_config_equal() {
let pool = common::make_sqlite_memory_pool().await;
let cloned = pool.clone();
assert_eq!(pool.config().url, cloned.config().url);
assert_eq!(pool.config().max_connections, cloned.config().max_connections);
}
#[tokio::test]
async fn test_db_pool_max_connections_one() {
let config = DbConfig {
url: "sqlite::memory:".to_string(),
max_connections: 1,
min_connections: 0,
..Default::default()
};
let pool = DbPool::with_config(config).await.unwrap();
let _session = pool.get_session("admin").await.expect("first session should succeed");
}
#[tokio::test]
async fn test_db_pool_serial_get_session_reuses_connection() {
let pool = common::make_sqlite_memory_pool().await;
for i in 0..5 {
let session = pool.get_session("admin").await;
assert!(session.is_ok(), "iteration {} should succeed", i);
drop(session);
}
}
#[tokio::test]
async fn test_db_pool_concurrent_get_session_preserves_invariants() {
use std::sync::Arc;
let pool = Arc::new(common::make_sqlite_memory_pool().await);
let max = pool.config().max_connections;
let mut handles = Vec::new();
for _ in 0..max {
let pool_clone = Arc::clone(&pool);
handles.push(tokio::spawn(async move {
let _session = pool_clone.get_session("admin").await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
}));
}
for handle in handles {
handle.await.unwrap();
}
let status = pool.status();
assert!(
status.total >= status.active,
"invariant violated: total >= active"
);
}
#[tokio::test]
async fn test_db_pool_drop_no_panic() {
let pool = common::make_sqlite_memory_pool().await;
drop(pool);
}
#[tokio::test]
async fn test_db_pool_drop_with_active_session_no_panic() {
let pool = common::make_sqlite_memory_pool().await;
let _session = pool.get_session("admin").await.unwrap();
drop(pool);
drop(_session);
}