elif-orm 0.7.1

Production-ready ORM with migrations, database services, connection pooling, and query builder
Documentation
use super::*;
use std::collections::HashMap;

#[test]
fn test_batch_config_default() {
    let config = BatchConfig::default();
    assert_eq!(config.max_batch_size, 1000);
    assert_eq!(config.max_depth, 10);
    assert!(config.parallel_execution);
    assert!(config.deduplicate_queries);
}

#[test]
fn test_batch_loader_creation() {
    let loader = BatchLoader::new();
    assert_eq!(loader.config.max_batch_size, 1000);

    let custom_config = BatchConfig {
        max_batch_size: 500,
        max_depth: 5,
        parallel_execution: false,
        deduplicate_queries: false,
    };
    let custom_loader = BatchLoader::with_config(custom_config);
    assert_eq!(custom_loader.config.max_batch_size, 500);
    assert_eq!(custom_loader.config.max_depth, 5);
    assert!(!custom_loader.config.parallel_execution);
    assert!(!custom_loader.config.deduplicate_queries);
}

#[test]
fn test_row_to_json_conversion_logic() {
    let _loader = BatchLoader::new();

    // Test the core conversion logic using a simplified approach
    // Since we can't easily mock sqlx::postgres::PgRow, we'll test the logic principles

    // Test JSON value handling for different types
    let test_cases = vec![
        ("String", serde_json::Value::String("test".to_string())),
        (
            "Number",
            serde_json::Value::Number(serde_json::Number::from(42)),
        ),
        ("Bool", serde_json::Value::Bool(true)),
        ("Null", serde_json::Value::Null),
    ];

    for (type_name, value) in test_cases {
        // Validate that our conversion handles these types properly
        match value {
            serde_json::Value::String(s) => assert_eq!(s, "test"),
            serde_json::Value::Number(n) => assert_eq!(n.as_i64(), Some(42)),
            serde_json::Value::Bool(b) => assert!(b),
            serde_json::Value::Null => assert!(value.is_null()),
            _ => panic!("Unexpected type: {}", type_name),
        }
    }
}

#[test]
fn test_batch_result_structure() {
    let mut records: HashMap<String, HashMap<Value, JsonValue>> = HashMap::new();

    // Test User records
    let mut user_records = HashMap::new();
    user_records.insert(
        Value::Number(serde_json::Number::from(1)),
        serde_json::json!({
            "id": 1,
            "name": "John Doe",
            "email": "john@example.com"
        }),
    );
    records.insert("users".to_string(), user_records);

    let result = BatchLoadResult {
        records,
        query_count: 1,
        record_count: 1,
    };

    assert_eq!(result.query_count, 1);
    assert_eq!(result.record_count, 1);
    assert!(result.records.contains_key("users"));

    let user_data = result.records.get("users").unwrap();
    let user_record = user_data
        .get(&Value::Number(serde_json::Number::from(1)))
        .unwrap();
    assert_eq!(user_record["name"], "John Doe");
    assert_eq!(user_record["email"], "john@example.com");
}

#[test]
fn test_group_by_parent_id_logic() {
    let loader = BatchLoader::new();

    // Test the grouping logic with mock data
    let results = vec![
        serde_json::json!({
            "id": 1,
            "user_id": 10,
            "title": "Post 1"
        }),
        serde_json::json!({
            "id": 2,
            "user_id": 10,
            "title": "Post 2"
        }),
        serde_json::json!({
            "id": 3,
            "user_id": 20,
            "title": "Post 3"
        }),
    ];

    let parent_ids = vec![
        Value::Number(serde_json::Number::from(10)),
        Value::Number(serde_json::Number::from(20)),
        Value::Number(serde_json::Number::from(30)), // No posts for this user
    ];

    let grouped = loader.group_by_parent_id(results, "user_id", &parent_ids);
    assert!(grouped.is_ok());

    let grouped = grouped.unwrap();

    // User 10 should have 2 posts
    let user_10_posts = grouped
        .get(&Value::Number(serde_json::Number::from(10)))
        .unwrap();
    assert_eq!(user_10_posts.len(), 2);

    // User 20 should have 1 post
    let user_20_posts = grouped
        .get(&Value::Number(serde_json::Number::from(20)))
        .unwrap();
    assert_eq!(user_20_posts.len(), 1);

    // User 30 should have 0 posts (empty array)
    let user_30_posts = grouped
        .get(&Value::Number(serde_json::Number::from(30)))
        .unwrap();
    assert_eq!(user_30_posts.len(), 0);
}

#[tokio::test]
async fn test_cache_stats() {
    let loader = BatchLoader::new();

    // Initially cache should be empty
    let stats = loader.cache_stats().await;
    assert_eq!(stats.cached_queries, 0);
    assert_eq!(stats.total_cached_records, 0);

    // Test cache clearing
    loader.clear_cache().await;
    let stats_after_clear = loader.cache_stats().await;
    assert_eq!(stats_after_clear.cached_queries, 0);
}

#[test]
fn test_postgresql_type_conversion_patterns() {
    // Test type conversion patterns we implement in row_to_json

    // String conversion
    let string_val = Some("test".to_string());
    let json_val = string_val.map_or(JsonValue::Null, JsonValue::String);
    assert_eq!(json_val, JsonValue::String("test".to_string()));

    // i64 conversion
    let int_val = Some(42i64);
    let json_val = int_val.map_or(JsonValue::Null, |v| {
        JsonValue::Number(serde_json::Number::from(v))
    });
    assert_eq!(json_val, JsonValue::Number(serde_json::Number::from(42)));

    // i32 conversion
    let int32_val = Some(24i32);
    let json_val = int32_val.map_or(JsonValue::Null, |v| {
        JsonValue::Number(serde_json::Number::from(v))
    });
    assert_eq!(json_val, JsonValue::Number(serde_json::Number::from(24)));

    // f64 conversion
    let float_val = Some(3.14f64);
    let json_val = float_val.map_or(JsonValue::Null, |v| {
        JsonValue::Number(serde_json::Number::from_f64(v).unwrap_or(serde_json::Number::from(0)))
    });
    assert!(json_val.is_number());

    // bool conversion
    let bool_val = Some(true);
    let json_val = bool_val.map_or(JsonValue::Null, JsonValue::Bool);
    assert_eq!(json_val, JsonValue::Bool(true));

    // UUID conversion
    let uuid_val = Some(uuid::Uuid::new_v4());
    let json_val = uuid_val.map_or(JsonValue::Null, |v| JsonValue::String(v.to_string()));
    assert!(json_val.is_string());

    // DateTime conversion
    let datetime_val = Some(chrono::Utc::now());
    let json_val = datetime_val.map_or(JsonValue::Null, |v| JsonValue::String(v.to_rfc3339()));
    assert!(json_val.is_string());

    // Null handling
    let null_val: Option<String> = None;
    let json_val = null_val.map_or(JsonValue::Null, JsonValue::String);
    assert_eq!(json_val, JsonValue::Null);
}

#[test]
fn test_error_handling_patterns() {
    // Test error conversion from database errors to ORM errors
    let db_error = "Connection failed";
    let orm_error = OrmError::Database(format!("Batch query failed: {}", db_error));

    match orm_error {
        OrmError::Database(msg) => {
            assert!(msg.contains("Batch query failed"));
            assert!(msg.contains("Connection failed"));
        }
        _ => panic!("Expected Database error"),
    }

    // Test row conversion error handling
    let conversion_error = "Invalid column type";
    let orm_error = OrmError::Database(format!(
        "Failed to convert row to JSON: {}",
        conversion_error
    ));

    match orm_error {
        OrmError::Database(msg) => {
            assert!(msg.contains("Failed to convert row to JSON"));
            assert!(msg.contains("Invalid column type"));
        }
        _ => panic!("Expected Database error"),
    }
}