use dbnexus::db_entity;
use dbnexus::foundation::DatabaseType;
use dbnexus::{Migration, MigrationExecutor, TableChange};
use sea_orm::entity::prelude::*;
use sea_orm::{ActiveModelTrait, DbBackend, EntityTrait};
#[db_entity(table_name = "articles", primary_key = "id", soft_delete = true)]
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "articles")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i64,
pub title: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
async fn setup_with_seed() -> 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_articles_table".to_string());
migration.add_table_change(TableChange::CreateTable(table));
executor
.apply_migration(&migration)
.await
.expect("Migration should succeed");
let seed = vec![
Model {
id: 1,
title: "Article 1".to_string(),
deleted_at: None,
},
Model {
id: 2,
title: "Article 2".to_string(),
deleted_at: None,
},
Model {
id: 3,
title: "Article 3".to_string(),
deleted_at: None,
},
];
for m in seed {
let am: ActiveModel = m.into();
let _: Model = am
.insert(session.connection().expect("conn"))
.await
.expect("insert should succeed");
}
pool
}
#[tokio::test]
async fn test_find_all_excludes_soft_deleted() {
let pool = setup_with_seed().await;
let session = pool.get_session("admin").await.expect("Failed to get session");
let all = Model::find_all(&session).await.expect("find_all should succeed");
assert_eq!(all.len(), 3, "initially should have 3 records");
let affected = Model::delete(&session, 2).await.expect("delete should succeed");
assert_eq!(affected, 1, "should soft-delete 1 record");
let all = Model::find_all(&session).await.expect("find_all should succeed");
assert_eq!(all.len(), 2, "find_all should exclude soft-deleted record");
assert!(
all.iter().all(|m| m.id != 2),
"soft-deleted record should not appear in find_all"
);
let with_deleted = Model::find_with_deleted(&session)
.await
.expect("find_with_deleted should succeed");
assert_eq!(
with_deleted.len(),
3,
"find_with_deleted should include soft-deleted records"
);
let only_deleted = Model::find_only_deleted(&session)
.await
.expect("find_only_deleted should succeed");
assert_eq!(only_deleted.len(), 1, "should have 1 soft-deleted record");
assert_eq!(only_deleted[0].id, 2, "soft-deleted record should be id=2");
assert!(
only_deleted[0].deleted_at.is_some(),
"soft-deleted record should have deleted_at set"
);
}
#[tokio::test]
async fn test_find_by_id_excludes_soft_deleted() {
let pool = setup_with_seed().await;
let session = pool.get_session("admin").await.expect("Failed to get session");
let found = Model::find_by_id(&session, 2).await.expect("find_by_id should succeed");
assert!(found.is_some(), "record 2 should exist before soft delete");
Model::delete(&session, 2).await.expect("delete should succeed");
let found = Model::find_by_id(&session, 2).await.expect("find_by_id should succeed");
assert!(found.is_none(), "find_by_id should return None for soft-deleted record");
let found = Model::find_by_id(&session, 1).await.expect("find_by_id should succeed");
assert!(found.is_some(), "record 1 should still exist");
}
#[tokio::test]
async fn test_delete_soft_force_delete_physical() {
let pool = setup_with_seed().await;
let session = pool.get_session("admin").await.expect("Failed to get session");
let affected = Model::delete(&session, 1).await.expect("soft delete should succeed");
assert_eq!(affected, 1, "should soft-delete 1 record");
let with_deleted = Model::find_with_deleted(&session)
.await
.expect("find_with_deleted should succeed");
assert_eq!(with_deleted.len(), 3, "record should still exist in DB");
let deleted_record = with_deleted.iter().find(|m| m.id == 1).expect("record 1 should exist");
assert!(
deleted_record.deleted_at.is_some(),
"soft-deleted record should have deleted_at set"
);
let affected = Model::force_delete(&session, 1)
.await
.expect("force_delete should succeed");
assert_eq!(affected, 1, "should physically delete 1 record");
let with_deleted = Model::find_with_deleted(&session)
.await
.expect("find_with_deleted should succeed");
assert_eq!(with_deleted.len(), 2, "record should be physically removed from DB");
assert!(
with_deleted.iter().all(|m| m.id != 1),
"force-deleted record should not exist"
);
}
#[tokio::test]
async fn test_count_excludes_soft_deleted() {
let pool = setup_with_seed().await;
let session = pool.get_session("admin").await.expect("Failed to get session");
let count = Model::count(&session).await.expect("count should succeed");
assert_eq!(count, 3, "initial count should be 3");
Model::delete(&session, 1).await.expect("delete should succeed");
Model::delete(&session, 2).await.expect("delete should succeed");
let count = Model::count(&session).await.expect("count should succeed");
assert_eq!(
count, 1,
"count should exclude soft-deleted records (only id=3 remains)"
);
}
#[tokio::test]
async fn test_delete_many_soft_delete_batch() {
let pool = setup_with_seed().await;
let session = pool.get_session("admin").await.expect("Failed to get session");
use sea_orm::ColumnTrait;
let cond = sea_orm::Condition::any().add(Column::Id.eq(1)).add(Column::Id.eq(3));
let affected = Model::delete_many(&session, cond)
.await
.expect("delete_many should succeed");
assert_eq!(affected, 2, "should soft-delete 2 records");
let all = Model::find_all(&session).await.expect("find_all should succeed");
assert_eq!(all.len(), 1, "only 1 record should remain visible");
assert_eq!(all[0].id, 2, "remaining record should be id=2");
let only_deleted = Model::find_only_deleted(&session)
.await
.expect("find_only_deleted should succeed");
assert_eq!(only_deleted.len(), 2, "should have 2 soft-deleted records");
}
#[tokio::test]
async fn test_auto_injected_deleted_at_field() {
let pool = setup_with_seed().await;
let session = pool.get_session("admin").await.expect("Failed to get session");
Model::delete(&session, 1).await.expect("delete should succeed");
let with_deleted = Model::find_with_deleted(&session)
.await
.expect("find_with_deleted should succeed");
let deleted = with_deleted.iter().find(|m| m.id == 1).expect("record 1 should exist");
assert!(
deleted.deleted_at.is_some(),
"auto-injected deleted_at should be set after soft delete"
);
}