use dbnexus::Condition;
use dbnexus::DbPool;
use dbnexus::access::permission::{PermissionAction, PermissionConfig, RolePolicy, TablePermission};
use sea_orm::ActiveValue;
fn detect_db_type_from_url(url: &str) -> &'static str {
if url.starts_with("postgres") || url.starts_with("postgresql") {
"postgres"
} else if url.starts_with("mysql") {
"mysql"
} else {
"sqlite"
}
}
fn get_database_url() -> Option<String> {
if let Ok(url) = std::env::var("DATABASE_URL") {
return Some(url);
}
if cfg!(feature = "sqlite") {
return Some("sqlite::memory:".to_string());
}
if cfg!(feature = "postgres") {
let password = std::env::var("TEST_DB_PASSWORD").unwrap_or_else(|_| "dbnexus_password".to_string());
return Some(format!("postgres://dbnexus:{}@localhost:15433/dbnexus_test", password));
}
if cfg!(feature = "mysql") {
let password = std::env::var("TEST_DB_PASSWORD").unwrap_or_else(|_| "dbnexus_password".to_string());
return Some(format!("mysql://dbnexus:{}@localhost:13308/dbnexus_test", password));
}
None
}
#[tokio::test]
async fn test_entity_types_are_accessible() {
let condition = Condition::all();
let set_value = ActiveValue::Set("test".to_string());
let set_number = ActiveValue::Set(42);
let set_option = ActiveValue::Set(Some(42));
assert!(!format!("{condition:?}").is_empty());
assert!(matches!(set_value, ActiveValue::Set(_)));
assert!(matches!(set_number, ActiveValue::Set(42)));
assert!(matches!(set_option, ActiveValue::Set(Some(42))));
}
#[tokio::test]
async fn test_condition_operations() {
let condition = Condition::all()
.add(Condition::any())
.add(Condition::not(Condition::all()));
let combined = Condition::all().add(Condition::all()).add(Condition::any());
assert!(!format!("{condition:?}").is_empty());
assert!(!format!("{combined:?}").is_empty());
}
#[tokio::test]
async fn test_set_type() {
let string = ActiveValue::Set("test".to_string());
let number = ActiveValue::Set(42);
let option = ActiveValue::Set(Some(42));
let none_value: ActiveValue<Option<i32>> = ActiveValue::Set(None);
assert!(matches!(string, ActiveValue::Set(_)));
assert!(matches!(number, ActiveValue::Set(42)));
assert!(matches!(option, ActiveValue::Set(Some(42))));
assert!(matches!(none_value, ActiveValue::Set(None)));
}
#[tokio::test]
async fn test_entity_with_session() {
let Some(url) = get_database_url() else {
return;
};
let pool = DbPool::new(&url).await.unwrap();
let session = pool.get_session("admin").await.unwrap();
assert_eq!(session.role(), "admin");
assert!(!session.is_in_transaction().await);
session.begin_transaction().await.unwrap();
assert!(session.is_in_transaction().await);
session.commit().await.unwrap();
assert!(!session.is_in_transaction().await);
}
#[tokio::test]
async fn test_entity_types_compatibility() {
let condition = Condition::all();
let set_value = ActiveValue::Set("value".to_string());
assert!(!format!("{condition:?}").is_empty());
assert!(matches!(set_value, ActiveValue::Set(_)));
}
#[tokio::test]
async fn test_entity_module_reexports() {
let condition = Condition::all();
assert!(!format!("{condition:?}").is_empty());
}
#[tokio::test]
async fn test_condition_building() {
let simple = Condition::all();
let nested = Condition::all().add(Condition::any().add(Condition::all()));
let not_condition = Condition::not(Condition::all());
let combined = Condition::all()
.add(Condition::any())
.add(Condition::not(Condition::all()));
assert!(!format!("{simple:?}").is_empty());
assert!(!format!("{nested:?}").is_empty());
assert!(!format!("{not_condition:?}").is_empty());
assert!(!format!("{combined:?}").is_empty());
}
#[tokio::test]
async fn test_set_with_various_types() {
let string = ActiveValue::Set("string".to_string());
let i32_value = ActiveValue::Set(42);
let i64_value = ActiveValue::Set(100_i64);
let f64_value = ActiveValue::Set(1.5);
let bool_value = ActiveValue::Set(true);
let option_value: ActiveValue<Option<i32>> = ActiveValue::Set(None);
assert!(matches!(string, ActiveValue::Set(_)));
assert!(matches!(i32_value, ActiveValue::Set(42)));
assert!(matches!(i64_value, ActiveValue::Set(100)));
assert!(matches!(f64_value, ActiveValue::Set(_)));
assert!(matches!(bool_value, ActiveValue::Set(true)));
assert!(matches!(option_value, ActiveValue::Set(None)));
}
#[tokio::test]
async fn test_entity_integration_with_sql_operations() {
let Some(url) = get_database_url() else {
return;
};
let pool = DbPool::new(&url).await.unwrap();
let session = pool.get_session("admin").await.unwrap();
assert_eq!(session.role(), "admin");
assert!(!session.is_in_transaction().await);
session.begin_transaction().await.unwrap();
assert!(session.is_in_transaction().await);
session.rollback().await.unwrap();
assert!(!session.is_in_transaction().await);
}
const CRUD_TEST_TABLE: &str = "crud_entity_test";
fn get_test_config_with_all_permissions() -> (dbnexus::DbConfig, tempfile::TempDir) {
let dir = tempfile::tempdir().expect("Failed to create temp dir");
let url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
let db_path = dir.path().join("test.db");
format!("sqlite://{}?mode=rwc", db_path.display())
});
let perm_content = r#"
roles:
admin:
tables:
- name: "*"
operations:
- SELECT
- INSERT
- UPDATE
- DELETE
"#;
let perm_file = dir.path().join("perms.yaml");
std::fs::write(&perm_file, perm_content).expect("Failed to write permissions");
let config = dbnexus::DbConfig {
url,
max_connections: 5,
admin_role: "admin".to_string(),
permissions_path: Some(perm_file.to_string_lossy().to_string()),
..Default::default()
};
(config, dir)
}
async fn setup_crud_test_table(pool: &DbPool) {
let session = pool.get_session("admin").await.unwrap();
let _ = session
.execute_raw_ddl(&format!("DROP TABLE IF EXISTS {}", CRUD_TEST_TABLE))
.await;
let db_type = detect_db_type_from_url(pool.config().url.as_str());
let create_sql = match db_type {
"mysql" => {
eprintln!("Using MySQL CREATE TABLE syntax");
format!(
"CREATE TABLE IF NOT EXISTS {} (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), email VARCHAR(255), age INT)",
CRUD_TEST_TABLE
)
}
"postgres" => {
eprintln!("Using PostgreSQL CREATE TABLE syntax");
format!(
"CREATE TABLE IF NOT EXISTS {} (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name TEXT, email TEXT, age INTEGER)",
CRUD_TEST_TABLE
)
}
_ => {
eprintln!("Using SQLite CREATE TABLE syntax");
format!(
"CREATE TABLE IF NOT EXISTS {} (id INTEGER PRIMARY KEY, name TEXT, email TEXT, age INTEGER)",
CRUD_TEST_TABLE
)
}
};
eprintln!("CREATE SQL: {}", create_sql);
session.execute_raw_ddl(&create_sql).await.unwrap();
}
async fn cleanup_crud_test_table(pool: &DbPool) {
let session = pool.get_session("admin").await.unwrap();
let _ = session
.execute_raw_ddl(&format!("DROP TABLE IF EXISTS {}", CRUD_TEST_TABLE))
.await;
}
#[tokio::test]
async fn test_entity_crud_full_operations() {
let (config, _perm_dir) = get_test_config_with_all_permissions();
let pool = DbPool::with_config(config).await.unwrap();
setup_crud_test_table(&pool).await;
let session = pool.get_session("admin").await.unwrap();
let perm_config = PermissionConfig {
roles: [(
"admin".to_string(),
RolePolicy {
tables: vec![TablePermission {
name: "*".to_string(),
operations: vec![
PermissionAction::Select,
PermissionAction::Insert,
PermissionAction::Update,
PermissionAction::Delete,
],
}],
},
)]
.into_iter()
.collect(),
};
session
.permission_ctx()
.load_policy(&perm_config)
.await
.expect("Failed to load permissions");
let create = session
.execute_raw(&format!(
"INSERT INTO {} (name, email, age) VALUES ('CRUD Test', 'crud@test.com', 25)",
CRUD_TEST_TABLE
))
.await;
assert!(create.is_ok(), "INSERT failed: {:?}", create.err());
let read = session
.execute_raw(&format!("SELECT * FROM {} WHERE name = 'CRUD Test'", CRUD_TEST_TABLE))
.await;
assert!(read.is_ok(), "SELECT failed: {:?}", read.err());
let update = session
.execute_raw(&format!(
"UPDATE {} SET age = 30 WHERE name = 'CRUD Test'",
CRUD_TEST_TABLE
))
.await;
assert!(update.is_ok(), "UPDATE failed: {:?}", update.err());
let delete = session
.execute_raw(&format!("DELETE FROM {} WHERE name = 'CRUD Test'", CRUD_TEST_TABLE))
.await;
assert!(delete.is_ok(), "DELETE failed: {:?}", delete.err());
cleanup_crud_test_table(&pool).await;
}
#[tokio::test]
async fn test_entity_transaction_with_permissions() {
let (config, _perm_dir) = get_test_config_with_all_permissions();
let pool = DbPool::with_config(config).await.unwrap();
setup_crud_test_table(&pool).await;
let session = pool.get_session("admin").await.unwrap();
let perm_config = PermissionConfig {
roles: [(
"admin".to_string(),
RolePolicy {
tables: vec![TablePermission {
name: "*".to_string(),
operations: vec![
PermissionAction::Select,
PermissionAction::Insert,
PermissionAction::Update,
PermissionAction::Delete,
],
}],
},
)]
.into_iter()
.collect(),
};
session
.permission_ctx()
.load_policy(&perm_config)
.await
.expect("Failed to load permissions");
session.begin_transaction().await.unwrap();
session
.execute_raw(&format!(
"INSERT INTO {} (name, email) VALUES ('TX Commit', 'tx@test.com')",
CRUD_TEST_TABLE
))
.await
.expect("INSERT in transaction failed");
session.commit().await.unwrap();
let after_commit = session
.execute_raw(&format!("SELECT COUNT(*) FROM {}", CRUD_TEST_TABLE))
.await;
assert!(after_commit.is_ok(), "SELECT COUNT failed: {:?}", after_commit.err());
session.begin_transaction().await.unwrap();
session
.execute_raw(&format!(
"INSERT INTO {} (name, email) VALUES ('TX Rollback', 'rollback@test.com')",
CRUD_TEST_TABLE
))
.await
.expect("INSERT before rollback failed");
session.rollback().await.unwrap();
cleanup_crud_test_table(&pool).await;
}