#![allow(clippy::unwrap_used, clippy::expect_used)]
use figment::{Figment, providers::Serialized};
use modkit_db::{DbError, manager::DbManager};
use tempfile::TempDir;
#[tokio::test]
#[cfg(feature = "sqlite")]
async fn test_sqlite_relative_path_resolution() {
let temp_dir = TempDir::new().unwrap();
let db_filename = format!("test_{}.db", std::process::id());
let figment = Figment::new().merge(Serialized::defaults(serde_json::json!({
"modules": {
"test_module": {
"database": {
"engine": "sqlite",
"file": db_filename
}
}
}
})));
let manager = DbManager::from_figment(figment, temp_dir.path().to_path_buf()).unwrap();
let result = manager.get("test_module").await;
match result {
Ok(_handle) => {
let expected_path = temp_dir.path().join("test_module").join(&db_filename);
assert!(
expected_path.exists(),
"Database file should be created at {expected_path:?}"
);
}
Err(err) => {
panic!("Expected successful SQLite connection, got: {err:?}");
}
}
}
#[tokio::test]
#[cfg(feature = "sqlite")]
async fn test_sqlite_absolute_path() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("absolute_test.db");
let figment = Figment::new().merge(Serialized::defaults(serde_json::json!({
"modules": {
"test_module": {
"database": {
"engine": "sqlite",
"path": db_path
}
}
}
})));
let manager = DbManager::from_figment(figment, temp_dir.path().to_path_buf()).unwrap();
let result = manager.get("test_module").await;
match result {
Ok(_handle) => {
assert!(
db_path.exists(),
"Database file should be created at {db_path:?}"
);
}
Err(err) => {
panic!("Expected successful SQLite connection, got: {err:?}");
}
}
}
#[tokio::test]
#[cfg(feature = "sqlite")]
async fn test_pragma_precedence() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir
.path()
.join(format!("pragma_test_{}.db", std::process::id()));
let figment = Figment::new().merge(Serialized::defaults(serde_json::json!({
"modules": {
"test_module": {
"database": {
"dsn": format!("sqlite://{}?synchronous=OFF&journal_mode=DELETE", db_path.display()),
"params": {
"synchronous": "FULL", "busy_timeout": "5000" }
}
}
}
})));
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) => {
let error_msg = err.to_string();
assert!(
!error_msg.contains("PRAGMA"),
"PRAGMA error suggests precedence failed: {error_msg}"
);
}
}
}
#[tokio::test]
#[cfg(feature = "sqlite")]
async fn test_invalid_pragma_values() {
let temp_dir = TempDir::new().unwrap();
let db_filename = format!("invalid_pragma_{}.db", std::process::id());
let figment = Figment::new().merge(Serialized::defaults(serde_json::json!({
"modules": {
"test_module": {
"database": {
"engine": "sqlite",
"file": db_filename,
"params": {
"synchronous": "INVALID_VALUE"
}
}
}
}
})));
let manager = DbManager::from_figment(figment, temp_dir.path().to_path_buf()).unwrap();
let result = manager.get("test_module").await;
assert!(result.is_err());
if let Err(DbError::InvalidSqlitePragma { key, message }) = result {
assert_eq!(key, "synchronous");
assert!(message.contains("must be OFF/NORMAL/FULL/EXTRA"));
} else {
panic!("Expected InvalidSqlitePragma error, got: {result:?}");
}
}
#[tokio::test]
#[cfg(feature = "sqlite")]
async fn test_unknown_pragma_parameters() {
let temp_dir = TempDir::new().unwrap();
let db_filename = format!("unknown_pragma_{}.db", std::process::id());
let figment = Figment::new().merge(Serialized::defaults(serde_json::json!({
"modules": {
"test_module": {
"database": {
"engine": "sqlite",
"file": db_filename,
"params": {
"unknown_pragma": "some_value"
}
}
}
}
})));
let manager = DbManager::from_figment(figment, temp_dir.path().to_path_buf()).unwrap();
let result = manager.get("test_module").await;
assert!(result.is_err());
if let Err(DbError::UnknownSqlitePragma(key)) = result {
assert_eq!(key, "unknown_pragma");
} else {
panic!("Expected UnknownSqlitePragma error, got: {result:?}");
}
}
#[tokio::test]
#[cfg(feature = "sqlite")]
async fn test_auto_provision_creates_directories() {
let temp_dir = TempDir::new().unwrap();
let nested_path = temp_dir.path().join("nested").join("directories");
let figment = Figment::new().merge(Serialized::defaults(serde_json::json!({
"database": {
"auto_provision": true
},
"modules": {
"test_module": {
"database": {
"engine": "sqlite",
"path": nested_path.join("test.db")
}
}
}
})));
let manager = DbManager::from_figment(figment, temp_dir.path().to_path_buf()).unwrap();
let result = manager.get("test_module").await;
match result {
Ok(_handle) => {
assert!(
nested_path.exists(),
"Nested directories should be auto-created: {nested_path:?}"
);
assert!(
nested_path.join("test.db").exists(),
"Database file should exist"
);
}
Err(err) => {
panic!("Expected successful connection with auto-provision, got: {err:?}");
}
}
}
#[tokio::test]
#[cfg(feature = "sqlite")]
async fn test_auto_provision_disabled() {
let figment = Figment::new().merge(Serialized::defaults(serde_json::json!({
"database": {
"auto_provision": false
},
"modules": {
"test_module": {
"database": {
"engine": "sqlite",
"file": "nested/directories/test.db" }
}
}
})));
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;
assert!(result.is_err());
if let Err(err) = result {
let error_msg = err.to_string();
assert!(
error_msg.contains("No such file")
|| error_msg.contains("cannot find")
|| error_msg.contains("directory")
|| error_msg.contains("system cannot find the path")
|| error_msg.contains("Directory does not exist and auto_provision is disabled"),
"Expected I/O error, got: {error_msg}"
);
}
}
#[tokio::test]
#[cfg(feature = "sqlite")]
async fn test_sqlite_memory_database() {
let figment = Figment::new().merge(Serialized::defaults(serde_json::json!({
"modules": {
"test_module": {
"database": {
"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(Some(db)) => {
assert_eq!(db.db_engine(), "sqlite");
}
Ok(None) => {
panic!("Expected database handle for in-memory SQLite");
}
Err(err) => {
panic!("Expected successful in-memory SQLite connection, got: {err:?}");
}
}
}
#[tokio::test]
#[cfg(feature = "sqlite")]
async fn test_sqlite_shared_memory_database() {
let temp_dir = TempDir::new().unwrap();
let memdb_path = temp_dir
.path()
.join(format!("memdb_shared_{}", std::process::id()));
let figment = Figment::new().merge(Serialized::defaults(serde_json::json!({
"modules": {
"test_module": {
"database": {
"engine": "sqlite",
"dsn": format!(
"sqlite://{}?mode=memory&cache=shared",
memdb_path.display()
)
}
}
}
})));
let manager = DbManager::from_figment(figment, temp_dir.path().to_path_buf()).unwrap();
{
let result = manager.get("test_module").await;
match result {
Ok(Some(_handle)) => {
}
Ok(None) => {
panic!("Expected database handle for shared memory SQLite");
}
Err(err) => {
panic!("Expected successful shared memory SQLite connection, got: {err:?}");
}
}
}
}
#[tokio::test]
#[cfg(feature = "sqlite")]
async fn test_wal_pragma_validation() {
for wal_value in &["true", "false", "1", "0"] {
let temp_dir = TempDir::new().unwrap();
let db_filename = format!("wal_test_{}_{}.db", wal_value, std::process::id());
let figment = Figment::new().merge(Serialized::defaults(serde_json::json!({
"modules": {
"test_module": {
"database": {
"engine": "sqlite",
"file": db_filename,
"params": {
"wal": wal_value
}
}
}
}
})));
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 WAL value '{wal_value}', error type: {}",
std::any::type_name_of_val(&err)
);
}
}
}
let temp_dir = TempDir::new().unwrap();
let db_filename = format!("wal_invalid_{}.db", std::process::id());
let figment = Figment::new().merge(Serialized::defaults(serde_json::json!({
"modules": {
"test_module": {
"database": {
"engine": "sqlite",
"file": db_filename,
"params": {
"wal": "invalid"
}
}
}
}
})));
let manager = DbManager::from_figment(figment, temp_dir.path().to_path_buf()).unwrap();
let result = manager.get("test_module").await;
assert!(result.is_err());
if let Err(DbError::InvalidSqlitePragma { key, message }) = result {
assert_eq!(key, "wal");
assert!(message.contains("true/false/1/0"));
} else {
panic!("Expected InvalidSqlitePragma error, got: {result:?}");
}
}
#[tokio::test]
#[cfg(feature = "sqlite")]
async fn test_busy_timeout_pragma_validation() {
for timeout_value in &["0", "1000", "5000"] {
let temp_dir = TempDir::new().unwrap();
let db_filename = format!("timeout_test_{}_{}.db", timeout_value, std::process::id());
let figment = Figment::new().merge(Serialized::defaults(serde_json::json!({
"modules": {
"test_module": {
"database": {
"engine": "sqlite",
"file": db_filename,
"params": {
"busy_timeout": timeout_value
}
}
}
}
})));
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 timeout '{timeout_value}', got: error type: {}",
std::any::type_name_of_val(&err)
);
}
}
}
let temp_dir = TempDir::new().unwrap();
let db_filename = format!("timeout_negative_{}.db", std::process::id());
let figment = Figment::new().merge(Serialized::defaults(serde_json::json!({
"modules": {
"test_module": {
"database": {
"engine": "sqlite",
"file": db_filename,
"params": {
"busy_timeout": "-1000"
}
}
}
}
})));
let manager = DbManager::from_figment(figment, temp_dir.path().to_path_buf()).unwrap();
let result = manager.get("test_module").await;
assert!(result.is_err());
if let Err(DbError::InvalidSqlitePragma { key, message }) = result {
assert_eq!(key, "busy_timeout");
assert!(message.contains("non-negative"));
} else {
panic!("Expected InvalidSqlitePragma error, got: {result:?}");
}
let temp_dir = TempDir::new().unwrap();
let db_filename = format!("timeout_invalid_{}.db", std::process::id());
let figment = Figment::new().merge(Serialized::defaults(serde_json::json!({
"modules": {
"test_module": {
"database": {
"engine": "sqlite",
"file": db_filename,
"params": {
"busy_timeout": "not_a_number"
}
}
}
}
})));
let manager = DbManager::from_figment(figment, temp_dir.path().to_path_buf()).unwrap();
let result = manager.get("test_module").await;
assert!(result.is_err());
if let Err(DbError::InvalidSqlitePragma { key, message }) = result {
assert_eq!(key, "busy_timeout");
assert!(message.contains("integer"));
} else {
panic!("Expected InvalidSqlitePragma error, got: {result:?}");
}
}