extern crate floz_orm as floz;
use floz_orm::Executor;
fn test_url() -> Option<String> {
std::env::var("DATABASE_URL").ok()
}
use floz_orm::prelude::*;
use floz_orm::types::*;
#[model("floz_test_employees")]
pub struct Employee {
#[col(key, auto)]
pub id: i32,
#[col(max = 100)]
pub name: Varchar,
pub email: Text,
pub age: Option<i16>,
pub active: bool,
}
#[model("floz_test_append_log")]
pub struct AppendLog {
pub event: Text,
pub timestamp: TimestampTz,
}
#[tokio::test]
async fn crud_full_lifecycle() {
let Some(url) = test_url() else { return };
let db = floz_orm::Db::connect(&url).await.unwrap();
db.execute_raw(
"CREATE TABLE IF NOT EXISTS floz_test_employees (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email TEXT NOT NULL,
age SMALLINT,
active BOOLEAN NOT NULL DEFAULT true
)",
vec![],
).await.unwrap();
db.execute_raw("DELETE FROM floz_test_employees", vec![]).await.unwrap();
let emp = Employee {
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
age: Some(30),
active: true,
..Employee::default()
};
let created = emp.create(&db).await.unwrap();
assert!(created.id > 0, "auto-increment should assign a positive ID");
assert_eq!(created.name, "Alice");
assert_eq!(created.age, Some(30));
let alice_id = created.id;
let fetched = Employee::get(alice_id, &db).await.unwrap();
assert_eq!(fetched.name, "Alice");
assert_eq!(fetched.email, "alice@example.com");
let found = Employee::find(alice_id, &db).await.unwrap();
assert!(found.is_some());
assert_eq!(found.unwrap().name, "Alice");
let not_found = Employee::find(99999, &db).await.unwrap();
assert!(not_found.is_none());
let err = Employee::get(99999, &db).await;
assert!(matches!(err, Err(floz_orm::FlozError::NotFound)));
let mut alice = Employee::get(alice_id, &db).await.unwrap();
assert!(!alice.is_dirty());
alice.set_name("Alice Updated".to_string());
alice.set_age(Some(31));
assert!(alice.is_dirty());
alice.save(&db).await.unwrap();
assert!(!alice.is_dirty());
let refreshed = Employee::get(alice_id, &db).await.unwrap();
assert_eq!(refreshed.name, "Alice Updated");
assert_eq!(refreshed.age, Some(31));
let mut clean = Employee::get(alice_id, &db).await.unwrap();
clean.save(&db).await.unwrap();
let emp2 = Employee {
name: "Bob".to_string(),
email: "bob@example.com".to_string(),
age: None,
active: false,
..Employee::default()
};
emp2.create(&db).await.unwrap();
let all = Employee::all(&db).await.unwrap();
assert_eq!(all.len(), 2);
let active_emps = Employee::filter(
EmployeeTable::active.eq(true),
&db,
).await.unwrap();
assert_eq!(active_emps.len(), 1);
assert_eq!(active_emps[0].name, "Alice Updated");
let age_filter = Employee::filter(
EmployeeTable::age.is_null(),
&db,
).await.unwrap();
assert_eq!(age_filter.len(), 1);
assert_eq!(age_filter[0].name, "Bob");
let bob = Employee::filter(
EmployeeTable::name.eq("Bob"),
&db,
).await.unwrap().into_iter().next().unwrap();
bob.delete(&db).await.unwrap();
let remaining = Employee::all(&db).await.unwrap();
assert_eq!(remaining.len(), 1);
db.execute_raw("DROP TABLE IF EXISTS floz_test_employees", vec![]).await.unwrap();
}
#[tokio::test]
async fn crud_with_transaction() {
let Some(url) = test_url() else { return };
let db = floz_orm::Db::connect(&url).await.unwrap();
db.execute_raw(
"CREATE TABLE IF NOT EXISTS floz_test_employees (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email TEXT NOT NULL,
age SMALLINT,
active BOOLEAN NOT NULL DEFAULT true
)",
vec![],
).await.unwrap();
db.execute_raw("DELETE FROM floz_test_employees", vec![]).await.unwrap();
let tx = db.begin().await.unwrap();
let emp = Employee {
name: "TxUser".to_string(),
email: "tx@example.com".to_string(),
active: true,
..Employee::default()
};
let created = emp.create(&tx).await.unwrap();
assert!(created.id > 0);
tx.commit().await.unwrap();
let found = Employee::filter(
EmployeeTable::name.eq("TxUser"),
&db,
).await.unwrap();
assert_eq!(found.len(), 1);
let tx2 = db.begin().await.unwrap();
let emp2 = Employee {
name: "RollbackUser".to_string(),
email: "rb@example.com".to_string(),
active: true,
..Employee::default()
};
emp2.create(&tx2).await.unwrap();
tx2.rollback().await.unwrap();
let found2 = Employee::filter(
EmployeeTable::name.eq("RollbackUser"),
&db,
).await.unwrap();
assert_eq!(found2.len(), 0);
db.execute_raw("DROP TABLE IF EXISTS floz_test_employees", vec![]).await.unwrap();
}
#[tokio::test]
async fn crud_append_only_no_pk() {
let Some(url) = test_url() else { return };
let db = floz_orm::Db::connect(&url).await.unwrap();
db.execute_raw(
"CREATE TABLE IF NOT EXISTS floz_test_append_log (
event TEXT NOT NULL,
timestamp TIMESTAMPTZ NOT NULL
)",
vec![],
).await.unwrap();
db.execute_raw("DELETE FROM floz_test_append_log", vec![]).await.unwrap();
let log = AppendLog {
event: "user_login".to_string(),
timestamp: chrono::Utc::now(),
..AppendLog::default()
};
let created = log.create(&db).await.unwrap();
assert_eq!(created.event, "user_login");
let all = AppendLog::all(&db).await.unwrap();
assert_eq!(all.len(), 1);
let logs = AppendLog::filter(
AppendLogTable::event.eq("user_login"),
&db,
).await.unwrap();
assert_eq!(logs.len(), 1);
db.execute_raw("DROP TABLE IF EXISTS floz_test_append_log", vec![]).await.unwrap();
}
#[tokio::test]
async fn save_only_updates_dirty_fields() {
let Some(url) = test_url() else { return };
let db = floz_orm::Db::connect(&url).await.unwrap();
db.execute_raw(
"CREATE TABLE IF NOT EXISTS floz_test_employees (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email TEXT NOT NULL,
age SMALLINT,
active BOOLEAN NOT NULL DEFAULT true
)",
vec![],
).await.unwrap();
db.execute_raw("DELETE FROM floz_test_employees", vec![]).await.unwrap();
let emp = Employee {
name: "DirtyTest".to_string(),
email: "dirty@example.com".to_string(),
age: Some(25),
active: true,
..Employee::default()
};
let created = emp.create(&db).await.unwrap();
let mut e = Employee::get(created.id, &db).await.unwrap();
e.set_name("DirtyTest Updated".to_string());
e.save(&db).await.unwrap();
let refreshed = Employee::get(created.id, &db).await.unwrap();
assert_eq!(refreshed.name, "DirtyTest Updated");
assert_eq!(refreshed.email, "dirty@example.com"); assert_eq!(refreshed.age, Some(25)); assert!(refreshed.active);
db.execute_raw("DROP TABLE IF EXISTS floz_test_employees", vec![]).await.unwrap();
}