use std::future::Future;
use tideorm::prelude::*;
use tideorm::profiling::GlobalProfiler;
use tideorm::{Database, TideConfig};
#[path = "support/sqlite_test_config.rs"]
mod test_config;
use test_config::{should_run_sqlite_tests, sqlite_database_url};
#[derive(Model, PartialEq)]
#[tideorm(table = "test_users")]
pub struct TestUser {
#[tideorm(primary_key, auto_increment)]
pub id: i64,
pub email: String,
pub name: String,
pub age: i32,
pub active: bool,
}
#[tideorm::model(table = "test_posts")]
pub struct TestPost {
#[tideorm(primary_key, auto_increment)]
pub id: i64,
pub user_id: i64,
pub title: String,
pub content: String,
pub published: bool,
}
#[tideorm::model(table = "test_products")]
pub struct TestProduct {
#[tideorm(primary_key, auto_increment)]
pub id: i64,
pub name: String,
pub category: String,
pub price: i64,
#[tideorm(nullable)]
pub metadata: Option<serde_json::Value>,
}
#[tideorm::model(table = "test_soft_deletes", soft_delete)]
pub struct TestSoftDelete {
#[tideorm(primary_key, auto_increment)]
pub id: i64,
pub name: String,
pub deleted_at: Option<chrono::DateTime<chrono::Utc>>,
}
async fn assert_profiled_operation<T, Fut>(label: &str, future: Fut) -> T
where
Fut: Future<Output = tideorm::Result<T>>,
{
GlobalProfiler::enable();
GlobalProfiler::reset();
GlobalProfiler::set_slow_threshold(0);
let result = future
.await
.unwrap_or_else(|err| panic!("{} failed during profiler test: {}", label, err));
let profiler_stats = GlobalProfiler::stats();
assert!(
profiler_stats.total_queries >= 1,
"expected {} to increment total_queries, got {:?}",
label,
profiler_stats
);
assert!(
profiler_stats.slow_queries >= 1,
"expected {} to increment slow_queries when threshold is zero, got {:?}",
label,
profiler_stats
);
GlobalProfiler::disable();
GlobalProfiler::reset();
GlobalProfiler::set_slow_threshold(100);
result
}
#[tokio::test]
async fn sqlite_integration_tests() {
if !should_run_sqlite_tests() {
println!("⏭️ Skipping SQLite tests (SKIP_SQLITE_TESTS is set)");
return;
}
println!("🪶 Starting SQLite Integration Tests...\n");
let db_url = if sqlite_database_url().contains("mode=memory") {
sqlite_database_url().to_string()
} else {
"sqlite::memory:".to_string()
};
let connect_result = TideConfig::init()
.database_type(DatabaseType::SQLite)
.database(&db_url)
.max_connections(1) .connect()
.await;
if let Err(e) = connect_result {
println!("⚠️ SQLite connection failed: {}", e);
println!(" This is expected if SQLite feature is not enabled");
return;
}
let db_type = tideorm::require_db().unwrap().backend();
assert_eq!(db_type, DatabaseType::SQLite, "Expected SQLite database");
println!(" Connected to SQLite\n");
let _ = Database::execute("DROP TABLE IF EXISTS test_soft_deletes").await;
let _ = Database::execute("DROP TABLE IF EXISTS test_posts").await;
let _ = Database::execute("DROP TABLE IF EXISTS test_products").await;
let _ = Database::execute("DROP TABLE IF EXISTS test_users").await;
Database::execute(
r#"
CREATE TABLE test_users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL,
name TEXT NOT NULL,
age INTEGER NOT NULL,
active INTEGER NOT NULL DEFAULT 1
)
"#,
)
.await
.expect("Failed to create test_users table");
Database::execute(
r#"
CREATE TABLE test_posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
content TEXT NOT NULL,
published INTEGER NOT NULL DEFAULT 0
)
"#,
)
.await
.expect("Failed to create test_posts table");
Database::execute(
r#"
CREATE TABLE test_products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
category TEXT NOT NULL,
price INTEGER NOT NULL,
metadata TEXT
)
"#,
)
.await
.expect("Failed to create test_products table");
Database::execute(
r#"
CREATE TABLE test_soft_deletes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
deleted_at TEXT
)
"#,
)
.await
.expect("Failed to create test_soft_deletes table");
println!(" Database setup complete\n");
println!("📡 Testing: Database Connection");
{
let db = tideorm::require_db().unwrap();
assert!(db.ping().await.is_ok(), "Database ping failed");
println!(" ✓ Ping successful");
let result = Database::execute("SELECT 1").await;
assert!(result.is_ok(), "Raw SQL execution failed");
println!(" ✓ Raw SQL execution works");
assert_eq!(db.backend(), DatabaseType::SQLite);
println!(" ✓ Database type is SQLite");
}
println!();
println!("📝 Testing: CRUD Operations");
{
let user = TestUser {
id: 0,
email: "test@example.com".to_string(),
name: "Test User".to_string(),
age: 25,
active: true,
};
let saved_user = user.save().await.expect("Failed to save user");
let _ = assert_profiled_operation("TestUser::query().get()", TestUser::query().get()).await;
assert!(saved_user.id > 0, "User ID should be auto-generated");
println!(" ✓ Create works (id: {})", saved_user.id);
let found_user = TestUser::find(saved_user.id).await.expect("Find failed");
assert!(found_user.is_some(), "User should be found");
assert_eq!(found_user.unwrap().email, "test@example.com");
println!(" ✓ Find by ID works");
}
{
let user = TestUser {
id: 0,
email: "update@example.com".to_string(),
name: "Original Name".to_string(),
age: 30,
active: true,
};
let mut saved_user = user.save().await.expect("Failed to save");
saved_user.name = "Updated Name".to_string();
saved_user.age = 31;
let updated = saved_user.update().await.expect("Update failed");
assert_eq!(updated.name, "Updated Name");
assert_eq!(updated.age, 31);
println!(" ✓ Update works");
}
{
let user = TestUser {
id: 0,
email: "delete@example.com".to_string(),
name: "To Delete".to_string(),
age: 40,
active: true,
};
let saved_user = user.save().await.expect("Failed to save");
let user_id = saved_user.id;
saved_user.delete().await.expect("Delete failed");
let found = TestUser::find(user_id).await.expect("Find failed");
assert!(found.is_none(), "User should be deleted");
println!(" ✓ Delete works");
}
println!();
println!("🔍 Testing: Query Builder");
let _ = Database::execute("DELETE FROM test_users").await;
for i in 1..=10 {
TestUser {
id: 0,
email: format!("user{}@example.com", i),
name: format!("User {}", i),
age: 20 + i,
active: i % 2 == 0,
}
.save()
.await
.expect("Failed to seed user");
}
{
let active_users = TestUser::query()
.where_eq("active", true)
.get()
.await
.expect("Query failed");
assert_eq!(active_users.len(), 5, "Should have 5 active users");
println!(" ✓ where_eq works");
let young_users = TestUser::query()
.where_lt("age", 25)
.get()
.await
.expect("Query failed");
assert_eq!(young_users.len(), 4);
println!(" ✓ where_lt works");
let range_users = TestUser::query()
.where_between("age", 23, 27)
.get()
.await
.expect("Query failed");
assert_eq!(range_users.len(), 5);
println!(" ✓ where_between works");
}
{
let ordered = TestUser::query()
.order_by("age", Order::Desc)
.limit(3)
.get()
.await
.expect("Query failed");
assert_eq!(ordered.len(), 3);
assert_eq!(ordered[0].age, 30);
println!(" ✓ order_by works");
}
{
let page1 = TestUser::query()
.order_by("id", Order::Asc)
.page(1, 3)
.get()
.await
.expect("Query failed");
assert_eq!(page1.len(), 3);
let page2 = TestUser::query()
.order_by("id", Order::Asc)
.page(2, 3)
.get()
.await
.expect("Query failed");
assert_eq!(page2.len(), 3);
assert_ne!(page1[0].id, page2[0].id);
println!(" ✓ Pagination works");
}
{
let count = TestUser::query().count().await.expect("Count failed");
assert_eq!(count, 10);
let active_count = TestUser::query()
.where_eq("active", true)
.count()
.await
.expect("Count failed");
assert_eq!(active_count, 5);
println!(" ✓ count works");
}
{
let exists = TestUser::query()
.where_eq("email", "user1@example.com")
.exists()
.await
.expect("Exists failed");
assert!(exists);
let not_exists = TestUser::query()
.where_eq("email", "nonexistent@example.com")
.exists()
.await
.expect("Exists failed");
assert!(!not_exists);
println!(" ✓ exists works");
}
{
let like_users = TestUser::query()
.where_like("email", "%@example.com")
.get()
.await
.expect("Query failed");
assert_eq!(like_users.len(), 10);
println!(" ✓ where_like works");
}
{
let in_users = TestUser::query()
.where_in("age", vec![21, 23, 25])
.get()
.await
.expect("Query failed");
assert_eq!(in_users.len(), 3);
println!(" ✓ where_in works");
}
println!();
println!("📊 Testing: Aggregations");
{
let sum = TestUser::query().sum("age").await.expect("Sum failed");
assert_eq!(sum as i64, 255);
println!(" ✓ sum works");
let avg = TestUser::query().avg("age").await.expect("Avg failed");
assert!((avg - 25.5).abs() < 0.01);
println!(" ✓ avg works");
let min = TestUser::query().min("age").await.expect("Min failed");
assert_eq!(min as i64, 21);
println!(" ✓ min works");
let max = TestUser::query().max("age").await.expect("Max failed");
assert_eq!(max as i64, 30);
println!(" ✓ max works");
}
println!();
println!("⏱️ Testing: Global Profiler Integration");
{
let first_user = TestUser::query()
.order_by("id", Order::Asc)
.first()
.await
.expect("Query failed")
.expect("Expected at least one seeded user");
let _ =
assert_profiled_operation("TestUser::query().count()", TestUser::query().count()).await;
let _ = assert_profiled_operation(
"TestUser::query().count_distinct(\"active\")",
TestUser::query().count_distinct("active"),
)
.await;
let _ = assert_profiled_operation(
"TestUser::query().sum(\"age\")",
TestUser::query().sum("age"),
)
.await;
let _ = assert_profiled_operation(
"Database::raw::<TestUser>()",
Database::raw::<TestUser>(
"SELECT id, email, name, age, active FROM test_users ORDER BY id LIMIT 2",
),
)
.await;
let _ = assert_profiled_operation(
"Database::raw_with_params::<TestUser>()",
Database::raw_with_params::<TestUser>(
"SELECT id, email, name, age, active FROM test_users WHERE age > ?",
vec![25.into()],
),
)
.await;
let _ = assert_profiled_operation(
"Database::execute()",
Database::execute("UPDATE test_users SET active = active"),
)
.await;
let _ = assert_profiled_operation(
"Database::execute_with_params()",
Database::execute_with_params(
"UPDATE test_users SET name = ? WHERE id = ?",
vec![first_user.name.clone().into(), first_user.id.into()],
),
)
.await;
let updated_user = assert_profiled_operation("TestUser::update()", async move {
let mut user = first_user;
user.name = format!("{} (profiled)", user.name);
user.update().await
})
.await;
assert!(updated_user.name.ends_with("(profiled)"));
let temp_user = assert_profiled_operation(
"TestUser::save()",
TestUser {
id: 0,
email: "profile-save@example.com".to_string(),
name: "Profile Save".to_string(),
age: 33,
active: true,
}
.save(),
)
.await;
let _ = assert_profiled_operation("TestUser::delete()", temp_user.delete()).await;
let destroy_target = TestUser {
id: 0,
email: "profile-destroy@example.com".to_string(),
name: "Profile Destroy".to_string(),
age: 44,
active: false,
}
.save()
.await
.expect("Failed to create destroy target");
let _ =
assert_profiled_operation("TestUser::destroy()", TestUser::destroy(destroy_target.id))
.await;
println!(" ✓ profiler records raw SQL, parameterized SQL, aggregates, and CRUD paths");
}
println!();
println!("🗑️ Testing: Bulk Delete");
{
let deleted = TestUser::query()
.where_eq("active", false)
.delete()
.await
.expect("Bulk delete failed");
assert_eq!(deleted, 5);
let remaining = TestUser::query().count().await.expect("Count failed");
assert_eq!(remaining, 5);
println!(" ✓ Bulk delete works");
}
println!();
println!("🗄️ Testing: Soft Deletes");
{
let _ = Database::execute("DELETE FROM test_soft_deletes").await;
for i in 1..=5 {
TestSoftDelete {
id: 0,
name: format!("Item {}", i),
deleted_at: None,
}
.save()
.await
.expect("Failed to create");
}
let item = TestSoftDelete::query()
.where_eq("name", "Item 1")
.first()
.await
.expect("Query failed")
.expect("Item not found");
item.soft_delete().await.expect("Soft delete failed");
let item2 = TestSoftDelete::query()
.where_eq("name", "Item 2")
.first()
.await
.expect("Query failed")
.expect("Item not found");
item2.soft_delete().await.expect("Soft delete failed");
let active = TestSoftDelete::query().get().await.expect("Query failed");
assert_eq!(active.len(), 3);
println!(" ✓ Default excludes soft deleted");
let all = TestSoftDelete::query()
.with_trashed()
.get()
.await
.expect("Query failed");
assert_eq!(all.len(), 5);
println!(" ✓ with_trashed includes all");
let trashed = TestSoftDelete::query()
.only_trashed()
.get()
.await
.expect("Query failed");
assert_eq!(trashed.len(), 2);
println!(" ✓ only_trashed works");
}
println!();
println!(" Testing: JSON Operations (JSON1 Extension)");
{
let _ = Database::execute("DELETE FROM test_products").await;
let product = TestProduct {
id: 0,
name: "Laptop".to_string(),
category: "Electronics".to_string(),
price: 999,
metadata: Some(serde_json::json!({
"brand": "TechCorp",
"features": ["fast", "lightweight"]
})),
};
product.save().await.expect("Failed to save product");
let product2 = TestProduct {
id: 0,
name: "Phone".to_string(),
category: "Electronics".to_string(),
price: 699,
metadata: Some(serde_json::json!({
"brand": "MobileCo",
"features": ["compact", "durable"]
})),
};
product2.save().await.expect("Failed to save product");
let products = TestProduct::query()
.where_eq("category", "Electronics")
.get()
.await
.expect("Query failed");
assert_eq!(products.len(), 2);
assert!(products[0].metadata.is_some());
println!(" ✓ JSON storage works");
println!(" ℹ JSON query operators require JSON1 extension");
}
println!();
println!("🎯 Testing: First Methods");
{
let _ = Database::execute("DELETE FROM test_users").await;
TestUser {
id: 0,
email: "first@example.com".to_string(),
name: "First".to_string(),
age: 25,
active: true,
}
.save()
.await
.unwrap();
let first = TestUser::query().first().await.expect("First failed");
assert!(first.is_some());
println!(" ✓ first works");
let first_or_fail = TestUser::query()
.where_eq("email", "first@example.com")
.first_or_fail()
.await;
assert!(first_or_fail.is_ok());
println!(" ✓ first_or_fail works for existing");
let not_found = TestUser::query()
.where_eq("email", "nonexistent@example.com")
.first_or_fail()
.await;
assert!(not_found.is_err());
println!(" ✓ first_or_fail returns error for missing");
}
println!();
println!("🧹 Cleanup");
let _ = Database::execute("DROP TABLE IF EXISTS test_soft_deletes").await;
let _ = Database::execute("DROP TABLE IF EXISTS test_posts").await;
let _ = Database::execute("DROP TABLE IF EXISTS test_products").await;
let _ = Database::execute("DROP TABLE IF EXISTS test_users").await;
println!(" ✓ Tables dropped");
println!("\n All SQLite integration tests passed!");
}