rulemorph 0.3.4

YAML-based declarative data transformation engine for CSV/JSON to JSON
Documentation
use super::*;
use crate::transform::EvalLimits;
use crate::v2_eval::eval_v2_pipe;
use crate::v2_parser::parse_v2_pipe_from_value;
use serde_json::json;

#[test]
fn test_context_new() {
    let ctx = V2EvalContext::new();
    assert!(ctx.get_pipe_value().is_none());
    assert!(ctx.resolve_local("x").is_none());
    assert!(ctx.get_item().is_none());
    assert!(ctx.get_acc().is_none());
}

#[test]
fn test_context_with_pipe_value() {
    let ctx = V2EvalContext::new().with_pipe_value(EvalValue::Value(json!(42)));
    assert!(ctx.get_pipe_value().is_some());
    assert_eq!(ctx.get_pipe_value(), Some(&EvalValue::Value(json!(42))));
}

#[test]
fn test_context_with_let_binding() {
    let ctx =
        V2EvalContext::new().with_let_binding("x".to_string(), EvalValue::Value(json!(100)));
    assert!(ctx.resolve_local("x").is_some());
    assert_eq!(ctx.resolve_local("x"), Some(&EvalValue::Value(json!(100))));
    assert!(ctx.resolve_local("y").is_none());
}

#[test]
fn test_context_with_multiple_let_bindings() {
    let ctx = V2EvalContext::new().with_let_bindings(vec![
        ("a".to_string(), EvalValue::Value(json!(1))),
        ("b".to_string(), EvalValue::Value(json!(2))),
    ]);
    assert!(ctx.resolve_local("a").is_some());
    assert!(ctx.resolve_local("b").is_some());
    assert!(ctx.resolve_local("c").is_none());
}

#[test]
fn test_context_scope_chain() {
    let ctx = V2EvalContext::new().with_let_binding("x".to_string(), EvalValue::Value(json!(1)));
    let inner_ctx = ctx
        .clone()
        .with_let_binding("y".to_string(), EvalValue::Value(json!(2)));

    assert!(inner_ctx.resolve_local("x").is_some());
    assert!(inner_ctx.resolve_local("y").is_some());

    assert!(ctx.resolve_local("x").is_some());
    assert!(ctx.resolve_local("y").is_none());
}

#[test]
fn test_context_with_item() {
    let item_value = json!({"name": "test"});
    let ctx = V2EvalContext::new().with_item(EvalItem {
        value: &item_value,
        index: 0,
    });
    assert!(ctx.has_item_scope());
    assert!(ctx.get_item().is_some());
    let item = ctx.get_item().unwrap();
    assert_eq!(item.value, &json!({"name": "test"}));
    assert_eq!(item.index, 0);
}

#[test]
fn test_context_with_acc() {
    let acc_value = json!(0);
    let ctx = V2EvalContext::new().with_acc(&acc_value);
    assert!(ctx.has_acc_scope());
    assert!(ctx.get_acc().is_some());
    assert_eq!(ctx.get_acc(), Some(&json!(0)));
}

#[test]
fn test_eval_value_is_missing() {
    assert!(EvalValue::Missing.is_missing());
    assert!(!EvalValue::Value(json!(null)).is_missing());
}

#[test]
fn test_eval_value_into_value() {
    assert_eq!(EvalValue::Missing.into_value(), None);
    assert_eq!(
        EvalValue::Value(json!("hello")).into_value(),
        Some(json!("hello"))
    );
}

#[test]
fn test_eval_value_as_value() {
    let missing = EvalValue::Missing;
    let val = EvalValue::Value(json!(42));
    assert!(missing.as_value().is_none());
    assert_eq!(val.as_value(), Some(&json!(42)));
}

#[test]
fn test_context_preserves_pipe_value_after_let() {
    let ctx = V2EvalContext::new()
        .with_pipe_value(EvalValue::Value(json!(100)))
        .with_let_binding("x".to_string(), EvalValue::Value(json!(50)));

    assert_eq!(ctx.get_pipe_value(), Some(&EvalValue::Value(json!(100))));
    assert_eq!(ctx.resolve_local("x"), Some(&EvalValue::Value(json!(50))));
}

#[test]
fn test_custom_op_counter_resets_for_public_eval_calls() {
    let rule = crate::parse_rule_file(
        r#"
version: 2
input:
  format: json
  json: {}
defs:
  id:
    input: int
    returns: int
    expr: "$"
mappings: []
"#,
    )
    .expect("rule parses");
    let pipe = parse_v2_pipe_from_value(&json!(["@input.n", "id"])).expect("pipe parses");
    let ctx = V2EvalContext::new()
        .with_limits(EvalLimits {
            max_custom_op_calls_per_record: 1,
            ..EvalLimits::default()
        })
        .with_rule(&rule);
    let out = json!({});

    for n in [1, 2] {
        let record = json!({ "n": n });
        let result =
            eval_v2_pipe(&pipe, &record, None, &out, "expr", &ctx).expect("eval stays in limit");
        assert_eq!(result, EvalValue::Value(json!(n)));
    }
}