use dbnexus::db_entity;
use dbnexus::foundation::DatabaseType;
use dbnexus::{Migration, MigrationExecutor, TableChange};
use sea_orm::entity::prelude::*;
use sea_orm::{ActiveModelTrait, DbBackend, EntityTrait};
use validator::Validate;
#[db_entity(table_name = "members", primary_key = "id", validate, timestamps = true)]
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Validate)]
#[sea_orm(table_name = "members")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i64,
#[validate(length(min = 2, message = "name too short"))]
pub name: String,
#[validate(email(message = "invalid email"))]
pub email: String,
pub created_at: Option<time::OffsetDateTime>,
pub updated_at: Option<time::OffsetDateTime>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
async fn setup() -> dbnexus::DbPool {
let pool = dbnexus::DbPool::new("sqlite::memory:")
.await
.expect("Failed to create pool");
let session = pool.get_session("admin").await.expect("Failed to get session");
let table = Model::schema(DbBackend::Sqlite);
let conn = session.connection().expect("Failed to get connection");
let mut executor = MigrationExecutor::new(conn.clone(), DatabaseType::Sqlite);
let mut migration = Migration::new(1, "create_members_table".to_string());
migration.add_table_change(TableChange::CreateTable(table));
executor
.apply_migration(&migration)
.await
.expect("Migration should succeed");
pool
}
#[tokio::test]
async fn test_validate_email_invalid() {
let pool = setup().await;
let session = pool.get_session("admin").await.expect("Failed to get session");
let conn = session.connection().expect("conn");
let am: ActiveModel = Model {
id: 1,
name: "Alice".to_string(),
email: "not-an-email".to_string(),
created_at: None,
updated_at: None,
}
.into();
let result = am.insert(conn).await;
assert!(result.is_err(), "insert with invalid email should fail validation");
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(
err_msg.contains("email") || err_msg.contains("invalid"),
"error should mention email validation, got: {}",
err_msg
);
let count = Entity::find().count(conn).await.expect("count should succeed");
assert_eq!(count, 0, "no records should be inserted after validation failure");
}
#[tokio::test]
async fn test_validate_email_valid() {
let pool = setup().await;
let session = pool.get_session("admin").await.expect("Failed to get session");
let conn = session.connection().expect("conn");
let am: ActiveModel = Model {
id: 1,
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
created_at: None,
updated_at: None,
}
.into();
let model: Model = am.insert(conn).await.expect("insert with valid email should succeed");
assert_eq!(model.id, 1);
let count = Entity::find().count(conn).await.expect("count should succeed");
assert_eq!(count, 1, "1 record should be inserted");
}
#[tokio::test]
async fn test_validate_length_too_short() {
let pool = setup().await;
let session = pool.get_session("admin").await.expect("Failed to get session");
let conn = session.connection().expect("conn");
let am: ActiveModel = Model {
id: 1,
name: "A".to_string(), email: "valid@example.com".to_string(),
created_at: None,
updated_at: None,
}
.into();
let result = am.insert(conn).await;
assert!(result.is_err(), "insert with too-short name should fail validation");
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(
err_msg.contains("name") || err_msg.contains("short"),
"error should mention name length validation, got: {}",
err_msg
);
}
#[tokio::test]
async fn test_validation_short_circuits_timestamps() {
let pool = setup().await;
let session = pool.get_session("admin").await.expect("Failed to get session");
let conn = session.connection().expect("conn");
let am: ActiveModel = Model {
id: 1,
name: "X".to_string(), email: "invalid".to_string(), created_at: None,
updated_at: None,
}
.into();
let result = am.insert(conn).await;
assert!(result.is_err(), "validation should fail");
let all = Entity::find().all(conn).await.expect("query should succeed");
assert!(all.is_empty(), "no records should exist after validation short-circuit");
}
#[tokio::test]
async fn test_validate_multiple_fields_fail() {
let pool = setup().await;
let session = pool.get_session("admin").await.expect("Failed to get session");
let conn = session.connection().expect("conn");
let am: ActiveModel = Model {
id: 1,
name: "".to_string(), email: "no-at-sign".to_string(), created_at: None,
updated_at: None,
}
.into();
let result = am.insert(conn).await;
assert!(result.is_err(), "multiple validation failures should fail");
let err_msg = result.unwrap_err().to_string();
assert!(!err_msg.is_empty(), "error message should contain validation details");
}
#[tokio::test]
async fn test_validate_and_timestamps_combined() {
let pool = setup().await;
let session = pool.get_session("admin").await.expect("Failed to get session");
let conn = session.connection().expect("conn");
let am: ActiveModel = Model {
id: 42,
name: "ValidName".to_string(),
email: "valid@example.com".to_string(),
created_at: None,
updated_at: None,
}
.into();
let model: Model = am
.insert(conn)
.await
.expect("insert should succeed (validate + timestamps)");
assert!(
model.created_at.is_some(),
"created_at should be set after validate+timestamps"
);
assert!(
model.updated_at.is_some(),
"updated_at should be set after validate+timestamps"
);
}