use bottle_orm::{Database, Model, Op};
use chrono::{DateTime, Utc};
use uuid::Uuid;
#[derive(Debug, Clone, Model, PartialEq)]
struct SoftUser {
#[orm(primary_key)]
id: Uuid,
name: String,
#[orm(soft_delete)]
deleted_at: Option<DateTime<Utc>>,
}
#[tokio::test]
async fn test_soft_delete_lifecycle() -> Result<(), Box<dyn std::error::Error>> {
let db = Database::builder().max_connections(1).connect("sqlite::memory:").await?;
db.migrator().register::<SoftUser>().run().await?;
let user_id = Uuid::new_v4();
let user = SoftUser { id: user_id, name: "Bob".to_string(), deleted_at: None };
db.model::<SoftUser>().insert(&user).await?;
let users: Vec<SoftUser> = db.model::<SoftUser>().scan().await?;
assert_eq!(users.len(), 1);
assert_eq!(users[0].name, "Bob");
db.model::<SoftUser>().filter(soft_user_fields::ID, Op::Eq, user_id.to_string()).delete().await?;
let users_after_delete: Vec<SoftUser> = db.model::<SoftUser>().scan().await?;
assert_eq!(users_after_delete.len(), 0);
let all_users: Vec<SoftUser> = db.model::<SoftUser>().with_deleted().scan().await?;
assert_eq!(all_users.len(), 1);
assert!(all_users[0].deleted_at.is_some());
db.model::<SoftUser>()
.filter(soft_user_fields::ID, Op::Eq, user_id.to_string())
.with_deleted() .hard_delete()
.await?;
let final_users: Vec<SoftUser> = db.model::<SoftUser>().with_deleted().scan().await?;
assert_eq!(final_users.len(), 0);
println!("Soft delete lifecycle test passed!");
Ok(())
}
#[tokio::test]
async fn test_queries_with_soft_delete() -> Result<(), Box<dyn std::error::Error>> {
let db = Database::builder().max_connections(1).connect("sqlite::memory:").await?;
db.migrator().register::<SoftUser>().run().await?;
let u1 = SoftUser { id: Uuid::new_v4(), name: "Active".to_string(), deleted_at: None };
let u2 = SoftUser {
id: Uuid::new_v4(),
name: "Deleted".to_string(),
deleted_at: Some(Utc::now()), };
db.model::<SoftUser>().insert(&u1).await?;
db.model::<SoftUser>().insert(&u2).await?;
let scan = db.model::<SoftUser>().scan::<SoftUser>().await?;
assert_eq!(scan.len(), 1);
assert_eq!(scan[0].name, "Active");
let count: i64 = db.model::<SoftUser>().count().await?;
assert_eq!(count, 1);
let first = db.model::<SoftUser>().filter(soft_user_fields::ID, Op::Eq, u2.id.to_string()).first::<SoftUser>().await;
assert!(first.is_err(), "Should not find deleted user in default scope");
let affected = db.model::<SoftUser>().update("name", "Updated").await?;
assert_eq!(affected, 1, "Should only update active user");
let u2_refetch = db
.model::<SoftUser>()
.with_deleted()
.filter(soft_user_fields::ID, Op::Eq, u2.id.to_string())
.first::<SoftUser>()
.await?;
assert_eq!(u2_refetch.name, "Deleted", "Deleted user should not be updated");
Ok(())
}
#[tokio::test]
async fn test_hard_delete() -> Result<(), Box<dyn std::error::Error>> {
let db = Database::builder().max_connections(1).connect("sqlite::memory:").await?;
db.migrator().register::<SoftUser>().run().await?;
let u1 = SoftUser { id: Uuid::new_v4(), name: "User1".to_string(), deleted_at: None };
let u2 = SoftUser { id: Uuid::new_v4(), name: "User2".to_string(), deleted_at: None };
let u3 = SoftUser { id: Uuid::new_v4(), name: "User3".to_string(), deleted_at: None };
db.model::<SoftUser>().insert(&u1).await?;
db.model::<SoftUser>().insert(&u2).await?;
db.model::<SoftUser>().insert(&u3).await?;
let count: i64 = db.model::<SoftUser>().count().await?;
assert_eq!(count, 3);
let deleted = db
.model::<SoftUser>()
.filter(soft_user_fields::ID, Op::Eq, u1.id.to_string())
.hard_delete()
.await?;
assert_eq!(deleted, 1, "Should delete exactly 1 row");
let all_users: Vec<SoftUser> = db.model::<SoftUser>().with_deleted().scan().await?;
assert_eq!(all_users.len(), 2);
assert!(all_users.iter().all(|u| u.id != u1.id), "User1 should be permanently deleted");
let deleted_all = db.model::<SoftUser>().hard_delete().await?;
assert_eq!(deleted_all, 2, "Should delete 2 remaining rows");
let final_count: i64 = db.model::<SoftUser>().with_deleted().count().await?;
assert_eq!(final_count, 0, "All users should be permanently deleted");
Ok(())
}