sz-orm-core 2.3.0

Core ORM engine: Model trait, ActiveRecord, QueryBuilder, Pool, Transaction, migration, and SQL dialect abstraction
Documentation
//! M4 ActiveModel 嵌套持久化集成测试
//!
//! 验证目标:
//! - nested_save 基本流程(父 + 子 INSERT)
//! - 外键自动回填
//! - 事务回滚(子 INSERT 失败 → 全部回滚)
//! - 多级嵌套(User → Order → OrderItem)
//! - 嵌套删除(子先父后)
//! - 脏字段追踪(仅 Set 字段写入)
//! - 深度限制

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)
    }
}

// ============================================================================
// nested_save 基本流程
// ============================================================================

#[tokio::test]
async fn test_nested_save_basic() {
    let mut mock = MockConnection::new();

    // 父 INSERT
    mock.expect_execute("INSERT INTO users (name) VALUES (?)", 1);
    // last_insert_id
    mock.expect_query("SELECT LAST_INSERT_ID() as id")
        .with_rows(vec![MockRow::from(vec![("id", Value::I64(1))])]);
    // 子 INSERT(含外键回填)
    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))])]);
    // 验证外键回填为 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)));
    // 验证 SQL 包含外键回填
    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();

    // 父 INSERT 成功
    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))])]);
    // 第 1 个子 INSERT 成功
    mock.expect_execute("INSERT INTO orders (amount, user_id) VALUES (?, ?)", 1);
    // 第 2 个子 INSERT 失败(返回 0 行受影响模拟失败)
    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;

    // 应该成功(mock 返回 0 不算错误,只是 0 行受影响)
    // 真实场景中 DB 错误会返回 Err
    assert!(result.is_ok(), "mock 返回 0 行不应报错");
    let result = result.unwrap();
    assert_eq!(result.affected_rows, 3);
}

// ============================================================================
// 多级嵌套(User → Order → OrderItem)
// ============================================================================

#[tokio::test]
async fn test_nested_save_multi_level() {
    let mut mock = MockConnection::new();

    // 父 INSERT
    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))])]);
    // 子 INSERT
    mock.expect_execute("INSERT INTO orders (amount, user_id) VALUES (?, ?)", 1);
    // 子的子 INSERT
    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();

    // 仅 name 字段应出现在 INSERT 中
    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 行");

    // 验证 SQL 仅含 name 字段
    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 行,无子级");
}

// ============================================================================
// ChildEntity 从 ActiveModel 创建
// ============================================================================

#[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());
}