use sz_orm_core::active_model::ActiveModel;
use sz_orm_core::mock::{MockConnection, MockRow};
use sz_orm_core::nested_active_model::{
nested_delete, nested_save, ChildEntity, NestedActiveModel,
};
use sz_orm_core::relation_trait::{RelationDef, RelationKind};
use sz_orm_core::Model;
use sz_orm_core::Value;
fn order_relation() -> RelationDef {
RelationDef::new(
"orders",
"users",
"orders",
"id",
"user_id",
RelationKind::HasMany,
)
}
#[derive(Debug, Clone, Default)]
#[allow(dead_code)]
struct User {
id: i64,
name: String,
}
impl Model for User {
type PrimaryKey = i64;
fn table_name() -> &'static str {
"users"
}
fn pk(&self) -> Self::PrimaryKey {
self.id
}
fn set_pk(&mut self, pk: Self::PrimaryKey) {
self.id = pk;
}
fn pk_as_value(&self) -> Value {
Value::I64(self.id)
}
}
#[tokio::test]
async fn test_nested_save_basic() {
let mut mock = MockConnection::new();
mock.expect_execute("INSERT INTO users (name) VALUES (?)", 1);
mock.expect_query("SELECT LAST_INSERT_ID() as id")
.with_rows(vec![MockRow::from(vec![("id", Value::I64(1))])]);
mock.expect_execute("INSERT INTO orders (amount, user_id) VALUES (?, ?)", 1);
mock.expect_execute("INSERT INTO orders (amount, user_id) VALUES (?, ?)", 1);
let mut user = ActiveModel::from_model(User::default());
user.set("name", "Alice".into());
let order1 = ChildEntity::new("orders", vec![("amount".to_string(), Value::F64(100.0))]);
let order2 = ChildEntity::new("orders", vec![("amount".to_string(), Value::F64(200.0))]);
let nested =
NestedActiveModel::from_model(user, order_relation()).with_children(vec![order1, order2]);
let result = nested_save(&mut mock, nested).await.unwrap();
assert_eq!(result.affected_rows, 3, "应插入 3 行(1 user + 2 orders)");
assert_eq!(result.parent_id, Some(Value::I64(1)));
}
#[tokio::test]
async fn test_nested_save_fk_backfill() {
let mut mock = MockConnection::new();
mock.expect_execute("INSERT INTO users (name) VALUES (?)", 1);
mock.expect_query("SELECT LAST_INSERT_ID() as id")
.with_rows(vec![MockRow::from(vec![("id", Value::I64(42))])]);
mock.expect_execute("INSERT INTO orders (amount, user_id) VALUES (?, ?)", 1);
let mut user = ActiveModel::from_model(User::default());
user.set("name", "Bob".into());
let order = ChildEntity::new("orders", vec![("amount".to_string(), Value::F64(50.0))]);
let nested = NestedActiveModel::from_model(user, order_relation()).with_children(vec![order]);
let result = nested_save(&mut mock, nested).await.unwrap();
assert_eq!(result.parent_id, Some(Value::I64(42)));
let executed = mock.executed_sql();
assert!(
executed.iter().any(|s| s.contains("INSERT INTO orders")),
"应执行 orders INSERT: {:?}",
executed
);
}
#[tokio::test]
async fn test_nested_save_transaction_rollback() {
let mut mock = MockConnection::new();
mock.expect_execute("INSERT INTO users (name) VALUES (?)", 1);
mock.expect_query("SELECT LAST_INSERT_ID() as id")
.with_rows(vec![MockRow::from(vec![("id", Value::I64(1))])]);
mock.expect_execute("INSERT INTO orders (amount, user_id) VALUES (?, ?)", 1);
mock.expect_execute("INSERT INTO orders (amount, user_id) VALUES (?, ?)", 0);
let mut user = ActiveModel::from_model(User::default());
user.set("name", "Alice".into());
let order1 = ChildEntity::new("orders", vec![("amount".to_string(), Value::F64(100.0))]);
let order2 = ChildEntity::new("orders", vec![("amount".to_string(), Value::F64(200.0))]);
let nested =
NestedActiveModel::from_model(user, order_relation()).with_children(vec![order1, order2]);
let result = nested_save(&mut mock, nested).await;
assert!(result.is_ok(), "mock 返回 0 行不应报错");
let result = result.unwrap();
assert_eq!(result.affected_rows, 3);
}
#[tokio::test]
async fn test_nested_save_multi_level() {
let mut mock = MockConnection::new();
mock.expect_execute("INSERT INTO users (name) VALUES (?)", 1);
mock.expect_query("SELECT LAST_INSERT_ID() as id")
.with_rows(vec![MockRow::from(vec![("id", Value::I64(1))])]);
mock.expect_execute("INSERT INTO orders (amount, user_id) VALUES (?, ?)", 1);
mock.expect_query("SELECT LAST_INSERT_ID() as id")
.with_rows(vec![MockRow::from(vec![("id", Value::I64(101))])]);
mock.expect_execute("INSERT INTO order_items (qty, order_id) VALUES (?, ?)", 1);
let mut user = ActiveModel::from_model(User::default());
user.set("name", "Alice".into());
let item = ChildEntity::new("order_items", vec![("qty".to_string(), Value::I32(5))]);
let order = ChildEntity::new("orders", vec![("amount".to_string(), Value::F64(100.0))])
.with_children(vec![item])
.with_relation(RelationDef::new(
"items",
"orders",
"order_items",
"id",
"order_id",
RelationKind::HasMany,
));
let nested = NestedActiveModel::from_model(user, order_relation()).with_children(vec![order]);
let result = nested_save(&mut mock, nested).await.unwrap();
assert_eq!(
result.affected_rows, 3,
"应插入 3 行(user + order + item)"
);
}
#[tokio::test]
async fn test_nested_delete_order() {
let mut mock = MockConnection::new();
mock.expect_execute("DELETE FROM orders WHERE user_id = ?", 2);
mock.expect_execute("DELETE FROM users WHERE id = ?", 1);
let user_model = User {
id: 1,
name: String::new(),
};
let user = ActiveModel::from_model(user_model);
let order = ChildEntity::new("orders", vec![]);
let nested = NestedActiveModel::from_model(user, order_relation())
.with_children(vec![order])
.cascade_delete(true);
let rows = nested_delete(&mut mock, &nested).await.unwrap();
assert_eq!(rows, 3, "应删除 3 行(2 orders + 1 user)");
let executed = mock.executed_sql();
let delete_orders_pos = executed
.iter()
.position(|s| s.contains("DELETE FROM orders"))
.unwrap();
let delete_users_pos = executed
.iter()
.position(|s| s.contains("DELETE FROM users"))
.unwrap();
assert!(
delete_orders_pos < delete_users_pos,
"应先删 orders 再删 users: {:?}",
executed
);
}
#[tokio::test]
async fn test_nested_save_dirty_fields_only() {
let mut mock = MockConnection::new();
mock.expect_execute("INSERT INTO users (name) VALUES (?)", 1);
mock.expect_query("SELECT LAST_INSERT_ID() as id")
.with_rows(vec![MockRow::from(vec![("id", Value::I64(1))])]);
let mut user = ActiveModel::from_model(User::default());
user.set("name", "Alice".into());
let nested = NestedActiveModel::from_model(user, order_relation());
let result = nested_save(&mut mock, nested).await.unwrap();
assert_eq!(result.affected_rows, 1, "仅父实体 1 行");
let insert_sql = mock
.executed_sql()
.iter()
.find(|s| s.contains("INSERT INTO users"))
.unwrap();
assert!(
insert_sql.contains("name") && !insert_sql.contains("email"),
"仅含 Set 字段: {}",
insert_sql
);
}
#[tokio::test]
async fn test_nested_save_no_children() {
let mut mock = MockConnection::new();
mock.expect_execute("INSERT INTO users (name) VALUES (?)", 1);
mock.expect_query("SELECT LAST_INSERT_ID() as id")
.with_rows(vec![MockRow::from(vec![("id", Value::I64(1))])]);
let mut user = ActiveModel::from_model(User::default());
user.set("name", "Solo".into());
let nested = NestedActiveModel::from_model(user, order_relation());
let result = nested_save(&mut mock, nested).await.unwrap();
assert_eq!(result.affected_rows, 1, "仅父实体 1 行,无子级");
}
#[tokio::test]
async fn test_child_entity_from_active_model() {
let order_model = User {
id: 0,
name: "test".to_string(),
};
let mut active = ActiveModel::from_model(order_model);
active.set("name", "updated".into());
let child = ChildEntity::from_active(&active);
assert_eq!(child.table(), "users");
assert_eq!(child.fields().len(), 1);
assert_eq!(child.fields()[0].0, "name");
match &child.fields()[0].1 {
Value::String(s) => assert_eq!(s, "updated"),
other => panic!("期望 String,得到 {:?}", other),
}
}
#[tokio::test]
async fn test_cascade_delete_flag() {
let user = ActiveModel::from_model(User::default());
let nested = NestedActiveModel::from_model(user, order_relation()).cascade_delete(true);
assert!(nested.is_cascade_delete());
let user = ActiveModel::from_model(User::default());
let nested = NestedActiveModel::from_model(user, order_relation());
assert!(!nested.is_cascade_delete());
}