#![allow(clippy::unwrap_used, clippy::expect_used)]
use figment::{Figment, providers::Serialized};
use modkit_db::{config::*, manager::DbManager};
use std::collections::HashMap;
use std::time::Duration;
use tempfile::TempDir;
#[tokio::test]
#[cfg(feature = "sqlite")]
async fn test_pool_cfg_options_applied() {
let figment = Figment::new().merge(Serialized::defaults(serde_json::json!({
"modules": {
"test_module": {
"database": {
"engine": "sqlite",
"dsn": "sqlite::memory:",
"pool": {
"max_conns": 20,
"min_conns": 2,
"acquire_timeout": "45s",
"idle_timeout": "300s",
"max_lifetime": "1800s",
"test_before_acquire": true
}
}
}
}
})));
let temp_dir = TempDir::new().unwrap();
let manager = DbManager::from_figment(figment, temp_dir.path().to_path_buf()).unwrap();
let result = manager.get("test_module").await;
match result {
Ok(_handle) => {
}
Err(err) => {
panic!("Expected successful connection with custom pool settings, got: {err:?}");
}
}
}
#[tokio::test]
#[cfg(feature = "sqlite")]
async fn test_module_pool_overrides_server_pool() {
let global_config = GlobalDatabaseConfig {
servers: {
let mut servers = HashMap::new();
servers.insert(
"sqlite_server".to_owned(),
DbConnConfig {
pool: Some(PoolCfg {
max_conns: Some(10),
min_conns: Some(1),
acquire_timeout: Some(Duration::from_secs(30)),
idle_timeout: Some(Duration::from_mins(10)),
max_lifetime: Some(Duration::from_hours(1)),
test_before_acquire: Some(false),
}),
..Default::default()
},
);
servers
},
auto_provision: Some(true),
};
let figment = Figment::new().merge(Serialized::defaults(serde_json::json!({
"database": global_config,
"modules": {
"test_module": {
"database": {
"server": "sqlite_server",
"engine": "sqlite",
"dsn": "sqlite::memory:",
"pool": {
"max_conns": 25, "acquire_timeout": "60s" }
}
}
}
})));
let temp_dir = TempDir::new().unwrap();
let manager = DbManager::from_figment(figment, temp_dir.path().to_path_buf()).unwrap();
let result = manager.get("test_module").await;
match result {
Ok(_handle) => {
}
Err(err) => {
panic!("Expected successful connection with overridden pool settings, got: {err:?}");
}
}
}
#[tokio::test]
#[cfg(feature = "sqlite")]
async fn test_pool_config_inheritance() {
let global_config = GlobalDatabaseConfig {
servers: {
let mut servers = HashMap::new();
servers.insert(
"sqlite_server".to_owned(),
DbConnConfig {
pool: Some(PoolCfg {
max_conns: Some(15),
min_conns: Some(3),
acquire_timeout: Some(Duration::from_secs(25)),
idle_timeout: Some(Duration::from_secs(500)),
max_lifetime: Some(Duration::from_secs(2000)),
test_before_acquire: Some(true),
}),
..Default::default()
},
);
servers
},
auto_provision: Some(true),
};
let figment = Figment::new().merge(Serialized::defaults(serde_json::json!({
"database": global_config,
"modules": {
"test_module": {
"database": {
"server": "sqlite_server",
"engine": "sqlite",
"dsn": "sqlite::memory:"
}
}
}
})));
let temp_dir = TempDir::new().unwrap();
let manager = DbManager::from_figment(figment, temp_dir.path().to_path_buf()).unwrap();
let result = manager.get("test_module").await;
match result {
Ok(_handle) => {
}
Err(err) => {
panic!("Expected successful connection with inherited pool settings, got: {err:?}");
}
}
}
#[tokio::test]
#[cfg(feature = "sqlite")]
async fn test_default_pool_configuration() {
let figment = Figment::new().merge(Serialized::defaults(serde_json::json!({
"modules": {
"test_module": {
"database": {
"dsn": "sqlite::memory:"
}
}
}
})));
let temp_dir = TempDir::new().unwrap();
let manager = DbManager::from_figment(figment, temp_dir.path().to_path_buf()).unwrap();
let result = manager.get("test_module").await;
match result {
Ok(_handle) => {
}
Err(err) => {
panic!("Expected successful connection with default pool settings, got: {err:?}");
}
}
}
#[test]
fn test_pool_cfg_helper_methods() {
let pool_cfg = PoolCfg {
max_conns: Some(50),
min_conns: Some(5),
acquire_timeout: Some(Duration::from_secs(45)),
idle_timeout: Some(Duration::from_mins(5)),
max_lifetime: Some(Duration::from_mins(30)),
test_before_acquire: Some(true),
};
#[cfg(feature = "sqlite")]
{
let sqlite_opts = pool_cfg.apply_sqlite(sqlx::sqlite::SqlitePoolOptions::new());
assert_eq!(
std::mem::size_of_val(&sqlite_opts),
std::mem::size_of::<sqlx::sqlite::SqlitePoolOptions>()
);
}
#[cfg(feature = "pg")]
{
let pg_opts = pool_cfg.apply_pg(sqlx::postgres::PgPoolOptions::new());
assert_eq!(
std::mem::size_of_val(&pg_opts),
std::mem::size_of::<sqlx::postgres::PgPoolOptions>()
);
}
#[cfg(feature = "mysql")]
{
let mysql_opts = pool_cfg.apply_mysql(sqlx::mysql::MySqlPoolOptions::new());
assert_eq!(
std::mem::size_of_val(&mysql_opts),
std::mem::size_of::<sqlx::mysql::MySqlPoolOptions>()
);
}
}
#[tokio::test]
#[cfg(feature = "sqlite")]
async fn test_partial_pool_configuration() {
let figment = Figment::new().merge(Serialized::defaults(serde_json::json!({
"modules": {
"test_module": {
"database": {
"dsn": "sqlite::memory:",
"pool": {
"max_conns": 8,
"acquire_timeout": "20s"
}
}
}
}
})));
let temp_dir = TempDir::new().unwrap();
let manager = DbManager::from_figment(figment, temp_dir.path().to_path_buf()).unwrap();
let result = manager.get("test_module").await;
match result {
Ok(_handle) => {
}
Err(err) => {
panic!("Expected successful connection with partial pool config, got: {err:?}");
}
}
}
#[test]
fn test_humantime_parsing() {
let test_cases = vec![
r#"{"acquire_timeout": "30s"}"#,
r#"{"acquire_timeout": "5m"}"#,
r#"{"acquire_timeout": "1h"}"#,
r#"{"acquire_timeout": "500ms"}"#,
r#"{"idle_timeout": "10min"}"#,
r#"{"max_lifetime": "2hours"}"#,
];
for case in test_cases {
let result: Result<PoolCfg, _> = serde_json::from_str(case);
assert!(result.is_ok(), "Failed to parse: {case}");
}
let invalid_case = r#"{"acquire_timeout": "invalid_duration"}"#;
let result: Result<PoolCfg, _> = serde_json::from_str(invalid_case);
assert!(result.is_err());
}
#[test]
fn test_pool_cfg_serde_roundtrip() {
let original = PoolCfg {
max_conns: Some(42),
min_conns: Some(7),
acquire_timeout: Some(Duration::from_secs(35)),
idle_timeout: Some(Duration::from_mins(7)),
max_lifetime: Some(Duration::from_mins(25)),
test_before_acquire: Some(false),
};
let serialized = serde_json::to_string(&original).unwrap();
let deserialized: PoolCfg = serde_json::from_str(&serialized).unwrap();
assert_eq!(original, deserialized);
}
#[test]
fn test_pool_cfg_defaults() {
let default_cfg = PoolCfg::default();
assert_eq!(default_cfg.max_conns, None);
assert_eq!(default_cfg.min_conns, None);
assert_eq!(default_cfg.acquire_timeout, None);
assert_eq!(default_cfg.idle_timeout, None);
assert_eq!(default_cfg.max_lifetime, None);
assert_eq!(default_cfg.test_before_acquire, None);
let empty_json = "{}";
let parsed: PoolCfg = serde_json::from_str(empty_json).unwrap();
assert_eq!(parsed, default_cfg);
}