use super::{NestedSave, NestedSaveBuilder, SavedRelation};
use crate::internal::ConnectionTrait;
use crate::model::Model;
use crate::{Database, GlobalProfiler, TideConfig};
#[derive(tideorm::Model, PartialEq)]
#[tideorm(table = "nested_test_parents")]
struct NestedTestParent {
#[tideorm(primary_key, auto_increment)]
id: i64,
name: String,
}
#[derive(tideorm::Model, PartialEq)]
#[tideorm(table = "nested_test_children")]
struct NestedTestChild {
#[tideorm(primary_key, auto_increment)]
id: i64,
parent_id: i64,
name: String,
}
#[derive(tideorm::Model, PartialEq)]
#[tideorm(table = "nested_test_profiles")]
struct NestedTestProfile {
#[tideorm(primary_key, auto_increment)]
id: i64,
user_id: i64,
bio: String,
}
async fn setup_nested_test_db() -> Database {
Database::reset_global();
TideConfig::reset();
GlobalProfiler::disable();
GlobalProfiler::reset();
let db = Database::connect("sqlite::memory:")
.await
.expect("failed to connect to SQLite for nested model tests");
Database::set_global(db.clone()).expect("failed to register nested test database");
db.__internal_connection()
.unwrap()
.execute_unprepared(
r#"
CREATE TABLE nested_test_parents (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL
);
CREATE TABLE nested_test_children (
id INTEGER PRIMARY KEY AUTOINCREMENT,
parent_id INTEGER NOT NULL,
name TEXT NOT NULL
);
CREATE TABLE nested_test_profiles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
bio TEXT NOT NULL
);
"#,
)
.await
.expect("failed to create nested test schema");
db
}
fn nested_children(names: &[&str]) -> Vec<NestedTestChild> {
names
.iter()
.map(|name| NestedTestChild {
id: 0,
parent_id: 0,
name: (*name).to_string(),
})
.collect()
}
fn nested_profile() -> NestedTestProfile {
NestedTestProfile {
id: 0,
user_id: 0,
bio: "profile".to_string(),
}
}
#[tokio::test]
async fn save_with_many_persists_children_with_parent_fk() {
let _db = setup_nested_test_db().await;
let parent = NestedTestParent {
id: 0,
name: "parent".to_string(),
};
let (saved_parent, saved_children) = parent
.save_with_many(nested_children(&["alpha", "beta"]), "parent_id")
.await
.expect("save_with_many should succeed");
assert!(saved_parent.id > 0);
assert_eq!(saved_children.len(), 2);
assert!(
saved_children
.iter()
.all(|child| child.parent_id == saved_parent.id)
);
assert!(saved_children.iter().all(|child| child.id > 0));
let fetched = NestedTestChild::query()
.where_eq("parent_id", saved_parent.id)
.order_by("id", crate::query::Order::Asc)
.get()
.await
.expect("should fetch nested children");
assert_eq!(fetched, saved_children);
}
#[tokio::test]
async fn nested_save_builder_persists_related_models_with_parent_fk() {
let _db = setup_nested_test_db().await;
let parent = NestedTestParent {
id: 0,
name: "parent".to_string(),
};
let (saved_parent, saved_related) = NestedSaveBuilder::new(parent)
.with_many(nested_children(&["alpha", "beta"]), "parent_id")
.save()
.await
.expect("nested builder save should succeed");
assert!(saved_parent.id > 0);
assert_eq!(saved_related.len(), 1);
let returned_children = saved_related[0]
.clone()
.into_many::<NestedTestChild>()
.expect("saved relation should deserialize into typed child models");
assert_eq!(returned_children.len(), 2);
assert!(
returned_children
.iter()
.all(|child| child.parent_id == saved_parent.id)
);
let fetched = NestedTestChild::query()
.where_eq("parent_id", saved_parent.id)
.order_by("id", crate::query::Order::Asc)
.get()
.await
.expect("should fetch nested children saved by builder");
assert_eq!(fetched.len(), 2);
assert_eq!(fetched[0].name, "alpha");
assert_eq!(fetched[1].name, "beta");
assert!(
fetched
.iter()
.all(|child| child.parent_id == saved_parent.id)
);
}
#[tokio::test]
async fn nested_save_builder_can_be_spawned() {
let _db = setup_nested_test_db().await;
let builder = NestedSaveBuilder::new(NestedTestParent {
id: 0,
name: "parent".to_string(),
})
.with_one(nested_profile(), "user_id")
.with_many(nested_children(&["alpha", "beta"]), "parent_id");
let join = tokio::spawn(async move { builder.save().await });
let (saved_parent, saved_related) = join
.await
.expect("spawned nested save task should join successfully")
.expect("spawned nested save should succeed");
assert!(saved_parent.id > 0);
assert_eq!(saved_related.len(), 2);
let saved_profile = saved_related[0]
.clone()
.into_one::<NestedTestProfile>()
.expect("first saved relation should deserialize into profile");
assert_eq!(saved_profile.user_id, saved_parent.id);
let saved_children = saved_related[1]
.clone()
.into_many::<NestedTestChild>()
.expect("second saved relation should deserialize into children");
assert_eq!(saved_children.len(), 2);
assert!(
saved_children
.iter()
.all(|child| child.parent_id == saved_parent.id)
);
let profile = NestedTestProfile::query()
.where_eq("user_id", saved_parent.id)
.first()
.await
.expect("profile query should succeed")
.expect("spawned builder should persist the one relation");
assert_eq!(profile.bio, "profile");
let fetched = NestedTestChild::query()
.where_eq("parent_id", saved_parent.id)
.order_by("id", crate::query::Order::Asc)
.get()
.await
.expect("should fetch nested children saved by spawned builder");
assert_eq!(fetched.len(), 2);
assert!(
fetched
.iter()
.all(|child| child.parent_id == saved_parent.id)
);
}
#[test]
fn saved_relation_rejects_wrong_shape_conversions() {
let one = SavedRelation::test_one(serde_json::json!({"id": 1, "user_id": 1, "bio": "x"}));
let many = SavedRelation::test_many(vec![
serde_json::json!({"id": 1, "parent_id": 1, "name": "x"}),
]);
assert!(one.into_many::<NestedTestChild>().is_err());
assert!(many.into_one::<NestedTestProfile>().is_err());
}
#[tokio::test]
async fn delete_with_many_uses_bulk_delete_for_related_models() {
let _db = setup_nested_test_db().await;
let parent = NestedTestParent {
id: 0,
name: "parent".to_string(),
};
let (saved_parent, saved_children) = parent
.save_with_many(nested_children(&["alpha", "beta", "gamma"]), "parent_id")
.await
.expect("save_with_many should seed nested children");
GlobalProfiler::reset();
GlobalProfiler::enable();
let deleted = saved_parent
.delete_with_many(saved_children)
.await
.expect("delete_with_many should succeed");
GlobalProfiler::disable();
assert_eq!(deleted, 4);
assert_eq!(GlobalProfiler::stats().total_queries, 4);
assert_eq!(
NestedTestChild::query()
.get()
.await
.expect("child query should succeed")
.len(),
0
);
assert_eq!(
NestedTestParent::query()
.get()
.await
.expect("parent query should succeed")
.len(),
0
);
}
#[tokio::test]
async fn update_with_many_uses_bulk_upsert_for_existing_related_models() {
let _db = setup_nested_test_db().await;
let parent = NestedTestParent {
id: 0,
name: "parent".to_string(),
};
let (saved_parent, saved_children) = parent
.save_with_many(nested_children(&["alpha", "beta", "gamma"]), "parent_id")
.await
.expect("save_with_many should seed nested children");
let updated_parent = NestedTestParent {
name: "parent-updated".to_string(),
..saved_parent
};
let updated_children: Vec<_> = saved_children
.into_iter()
.enumerate()
.map(|(index, child)| NestedTestChild {
name: format!("updated-{index}"),
..child
})
.collect();
GlobalProfiler::reset();
GlobalProfiler::enable();
let (parent_after_update, children_after_update) = updated_parent
.update_with_many(updated_children.clone())
.await
.expect("update_with_many should succeed");
GlobalProfiler::disable();
assert_eq!(parent_after_update.name, "parent-updated");
assert_eq!(children_after_update.len(), 3);
assert_eq!(GlobalProfiler::stats().total_queries, 5);
for (expected, actual) in updated_children.iter().zip(children_after_update.iter()) {
assert_eq!(expected.id, actual.id);
assert_eq!(expected.parent_id, actual.parent_id);
assert_eq!(expected.name, actual.name);
}
}