sqlx-query-dsl 0.1.1

A query DSL extension for SQLx
Documentation
use sqlx::{MySql, QueryBuilder};
use serde_json::json;
use crate::params::{FilterExpr, Op, FilterCond, QueryParams};
use crate::whitelist::FieldWhitelist;
use crate::builder::build_filter;

#[test]
fn test_whitelist_check() {
    let allowed = &["id", "name", "email"];
    let whitelist = FieldWhitelist::new(allowed);

    assert!(whitelist.check("id").is_ok());
    assert!(whitelist.check("name").is_ok());
    // 测试不在白名单中的字段
    assert!(whitelist.check("password").is_err());
    assert!(whitelist.check("drop table").is_err());
}

#[test]
fn test_builder_simple_eq() {
    let allowed = &["name"];
    let whitelist = FieldWhitelist::new(allowed);
    let cond = FilterExpr::Cond(FilterCond {
        field: Some("name".to_string()),
        op: Op::Eq,
        value: Some(json!("Alice")),
    });

    let mut qb = QueryBuilder::<MySql>::new("");
    build_filter(&mut qb, &cond, &whitelist).expect("build failed");
    
    assert_eq!(qb.sql(), "name = ?");
}

#[test]
fn test_builder_nested_and_or() {
    let allowed = &["age", "role", "active"];
    let whitelist = FieldWhitelist::new(allowed);
    
    // 构造逻辑: (age > 18 OR role = 'admin') AND active = true
    let expr = FilterExpr::And {
        and: vec![
            FilterExpr::Or {
                or: vec![
                    FilterExpr::Cond(FilterCond {
                        field: Some("age".to_string()),
                        op: Op::Gt,
                        value: Some(json!(18)),
                    }),
                    FilterExpr::Cond(FilterCond {
                        field: Some("role".to_string()),
                        op: Op::Eq,
                        value: Some(json!("admin")),
                    }),
                ]
            },
            FilterExpr::Cond(FilterCond {
                field: Some("active".to_string()),
                op: Op::Eq,
                value: Some(json!(true)),
            }),
        ]
    };

    let mut qb = QueryBuilder::<MySql>::new("");
    build_filter(&mut qb, &expr, &whitelist).expect("build failed");

    // 验证生成的 SQL 结构
    assert_eq!(qb.sql(), "((age > ? OR role = ?) AND active = ?)");
}

#[test]
fn test_builder_in_operator() {
    let allowed = &["status"];
    let whitelist = FieldWhitelist::new(allowed);
    let cond = FilterExpr::Cond(FilterCond {
        field: Some("status".to_string()),
        op: Op::In,
        value: Some(json!([1, 2, 3])),
    });

    let mut qb = QueryBuilder::<MySql>::new("");
    build_filter(&mut qb, &cond, &whitelist).expect("build failed");

    assert_eq!(qb.sql(), "status IN (?, ?, ?)");
}

#[test]
fn test_builder_illegal_field() {
    let allowed = &["name"];
    let whitelist = FieldWhitelist::new(allowed);
    let cond = FilterExpr::Cond(FilterCond {
        field: Some("password".to_string()), // 非法字段
        op: Op::Eq,
        value: Some(json!("123456")),
    });

    let mut qb = QueryBuilder::<MySql>::new("");
    let result = build_filter(&mut qb, &cond, &whitelist);
    
    assert!(result.is_err());
    assert_eq!(result.unwrap_err(), "Illegal field: password");
}

#[test]
fn test_deserialize_query_params() {
    // 模拟前端传入的 JSON
    let json_str = r#"{
        "page": 1,
        "page_size": 20,
        "sort": [{"field": "created_at", "desc": true}],
        "filter": {
            "field": "name",
            "op": "like",
            "value": "test"
        }
    }"#;

    let params: QueryParams = serde_json::from_str(json_str).expect("deserialize failed");
    
    assert_eq!(params.page, 1);
    assert_eq!(params.page_size, 20);
    assert_eq!(params.sort.len(), 1);
    assert_eq!(params.sort[0].field, "created_at");
    assert!(params.sort[0].desc);
    
    // 验证 filter 是否正确解析为 Cond
    if let Some(FilterExpr::Cond(c)) = params.filter {
        assert_eq!(c.field.unwrap(), "name");
        assert!(matches!(c.op, Op::Like));
        assert_eq!(c.value.unwrap(), json!("test"));
    } else {
        panic!("Filter should be a condition");
    }
}