use exprimo::Evaluator;
use serde_json::Value;
use std::collections::HashMap;
#[test]
fn test_division_by_zero_returns_infinity() {
let context = HashMap::new();
let evaluator = Evaluator::new(context, HashMap::new());
let result = evaluator.evaluate("5 / 0");
assert!(result.is_ok(), "Division by zero should not error");
let result = evaluator.evaluate("-5 / 0");
assert!(
result.is_ok(),
"Division by zero (negative) should not error"
);
let result = evaluator.evaluate("0 / 0");
assert!(result.is_ok(), "0/0 should not error (results in NaN)");
}
#[test]
fn test_nan_from_invalid_string_conversion() {
let mut context = HashMap::new();
context.insert("str".to_string(), Value::String("not a number".to_string()));
let evaluator = Evaluator::new(context, HashMap::new());
let result = evaluator.evaluate("str * 2");
assert!(result.is_ok(), "Invalid string conversion should not error");
let result = evaluator.evaluate("str * 2 ? true : false").unwrap();
assert_eq!(result, Value::Bool(false), "NaN should be falsy");
}
#[test]
fn test_nan_comparison() {
let context = HashMap::new();
let evaluator = Evaluator::new(context, HashMap::new());
let result = evaluator.evaluate("NaN == NaN");
if result.is_ok() {
let val = result.unwrap();
if let Value::Bool(b) = val {
}
}
let result = evaluator.evaluate("('abc' * 1) == ('abc' * 1)");
if result.is_ok() {
}
}
#[test]
fn test_infinity_support() {
let context = HashMap::new();
let evaluator = Evaluator::new(context, HashMap::new());
let result = evaluator.evaluate("Infinity");
assert!(result.is_ok(), "Infinity identifier should work");
let result = evaluator.evaluate("Infinity ? true : false").unwrap();
assert_eq!(result, Value::Bool(true), "Infinity should be truthy");
let result = evaluator.evaluate("Infinity > 1000000");
assert!(result.is_ok(), "Infinity comparison should work");
}
#[test]
fn test_empty_array_truthiness() {
let mut context = HashMap::new();
context.insert("emptyArr".to_string(), Value::Array(vec![]));
let evaluator = Evaluator::new(context, HashMap::new());
let result = evaluator.evaluate("emptyArr ? true : false").unwrap();
assert_eq!(result, Value::Bool(true), "Empty array should be truthy");
let result = evaluator.evaluate("[] ? true : false").unwrap();
assert_eq!(
result,
Value::Bool(true),
"Empty array literal should be truthy"
);
}
#[test]
fn test_empty_object_truthiness() {
let mut context = HashMap::new();
context.insert(
"emptyObj".to_string(),
Value::Object(serde_json::Map::new()),
);
let evaluator = Evaluator::new(context, HashMap::new());
let result = evaluator.evaluate("emptyObj ? true : false").unwrap();
assert_eq!(result, Value::Bool(true), "Empty object should be truthy");
}
#[test]
fn test_abstract_equality_type_coercion() {
let context = HashMap::new();
let evaluator = Evaluator::new(context, HashMap::new());
let result = evaluator.evaluate("'5' == 5").unwrap();
assert_eq!(result, Value::Bool(true), "'5' == 5 should be true");
let result = evaluator.evaluate("5 == '5'").unwrap();
assert_eq!(result, Value::Bool(true), "5 == '5' should be true");
let result = evaluator.evaluate("true == 1").unwrap();
assert_eq!(result, Value::Bool(true), "true == 1 should be true");
let result = evaluator.evaluate("false == 0").unwrap();
assert_eq!(result, Value::Bool(true), "false == 0 should be true");
let result = evaluator.evaluate("1 == true").unwrap();
assert_eq!(result, Value::Bool(true), "1 == true should be true");
}
#[test]
fn test_strict_equality_no_coercion() {
let context = HashMap::new();
let evaluator = Evaluator::new(context, HashMap::new());
let result = evaluator.evaluate("'5' === 5").unwrap();
assert_eq!(result, Value::Bool(false), "'5' === 5 should be false");
let result = evaluator.evaluate("5 === '5'").unwrap();
assert_eq!(result, Value::Bool(false), "5 === '5' should be false");
let result = evaluator.evaluate("true === 1").unwrap();
assert_eq!(result, Value::Bool(false), "true === 1 should be false");
let result = evaluator.evaluate("false === 0").unwrap();
assert_eq!(result, Value::Bool(false), "false === 0 should be false");
let result = evaluator.evaluate("5 === 5").unwrap();
assert_eq!(result, Value::Bool(true), "5 === 5 should be true");
let result = evaluator.evaluate("'hello' === 'hello'").unwrap();
assert_eq!(
result,
Value::Bool(true),
"'hello' === 'hello' should be true"
);
}
#[test]
fn test_string_escape_sequences() {
let context = HashMap::new();
let evaluator = Evaluator::new(context, HashMap::new());
let result = evaluator.evaluate("'line1\\nline2'").unwrap();
if let Value::String(s) = result {
assert!(s.contains('\n'), "Should contain actual newline character");
assert_eq!(s, "line1\nline2");
} else {
panic!("Expected string value");
}
let result = evaluator.evaluate("'col1\\tcol2'").unwrap();
if let Value::String(s) = result {
assert!(s.contains('\t'), "Should contain actual tab character");
assert_eq!(s, "col1\tcol2");
} else {
panic!("Expected string value");
}
let result = evaluator.evaluate("'quote: \\'hello\\''").unwrap();
if let Value::String(s) = result {
assert_eq!(s, "quote: 'hello'");
} else {
panic!("Expected string value");
}
let result = evaluator.evaluate("\"quote: \\\"hello\\\"\"").unwrap();
if let Value::String(s) = result {
assert_eq!(s, "quote: \"hello\"");
} else {
panic!("Expected string value");
}
let result = evaluator.evaluate("'path\\\\to\\\\file'").unwrap();
if let Value::String(s) = result {
assert_eq!(s, "path\\to\\file");
} else {
panic!("Expected string value");
}
}
#[test]
fn test_string_to_number_conversion() {
let mut context = HashMap::new();
context.insert("numStr".to_string(), Value::String("42".to_string()));
context.insert("emptyStr".to_string(), Value::String("".to_string()));
context.insert("infStr".to_string(), Value::String("Infinity".to_string()));
let evaluator = Evaluator::new(context, HashMap::new());
let result = evaluator.evaluate("numStr * 2").unwrap();
if let Value::Number(n) = result {
assert_eq!(
n.as_f64().unwrap(),
84.0,
"Valid number string should convert"
);
} else {
panic!("Expected number value");
}
let result = evaluator.evaluate("emptyStr + 5").unwrap();
if let Value::String(s) = result {
assert!(
s == "5" || s == "5.0",
"Empty string should concatenate with number: got {}",
s
);
} else {
panic!("Expected string concatenation");
}
let result = evaluator.evaluate("infStr > 1000");
assert!(result.is_ok(), "Infinity string should be parseable");
}
#[test]
fn test_array_to_number_conversion() {
let mut context = HashMap::new();
context.insert("emptyArr".to_string(), Value::Array(vec![]));
context.insert(
"singleNumArr".to_string(),
Value::Array(vec![Value::Number(42.into())]),
);
context.insert(
"multiArr".to_string(),
Value::Array(vec![Value::Number(1.into()), Value::Number(2.into())]),
);
let evaluator = Evaluator::new(context, HashMap::new());
let result = evaluator.evaluate("emptyArr * 5").unwrap();
if let Value::Number(n) = result {
assert_eq!(n.as_f64().unwrap(), 0.0, "Empty array should convert to 0");
} else {
panic!("Expected number value");
}
let result = evaluator.evaluate("singleNumArr * 2").unwrap();
if let Value::Number(n) = result {
assert_eq!(
n.as_f64().unwrap(),
84.0,
"Single element array should convert to its element"
);
} else {
panic!("Expected number value");
}
let result = evaluator.evaluate("multiArr * 2");
assert!(
result.is_ok(),
"Multi-element array multiplication should not error"
);
}
#[test]
fn test_undefined_identifier() {
let context = HashMap::new();
let evaluator = Evaluator::new(context, HashMap::new());
let result = evaluator.evaluate("undefined");
assert!(result.is_ok(), "undefined identifier should work");
assert_eq!(result.unwrap(), Value::Null, "undefined should be null");
let result = evaluator.evaluate("undefined ? true : false").unwrap();
assert_eq!(result, Value::Bool(false), "undefined should be falsy");
}
#[test]
fn test_complex_rule_engine_expressions() {
let mut context = HashMap::new();
context.insert("user_age".to_string(), Value::Number(25.into()));
context.insert(
"user_status".to_string(),
Value::String("active".to_string()),
);
context.insert("user_score".to_string(), Value::Number(85.into()));
context.insert("user_premium".to_string(), Value::Bool(true));
let evaluator = Evaluator::new(context, HashMap::new());
let result = evaluator
.evaluate("user_age >= 18 && user_status === 'active'")
.unwrap();
assert_eq!(result, Value::Bool(true));
let result = evaluator
.evaluate("user_score >= 80 ? 'pass' : 'fail'")
.unwrap();
assert_eq!(result, Value::String("pass".to_string()));
let result = evaluator.evaluate("user_premium == 1").unwrap();
assert_eq!(
result,
Value::Bool(true),
"Boolean true should equal number 1 with =="
);
let result = evaluator.evaluate("user_premium === 1").unwrap();
assert_eq!(
result,
Value::Bool(false),
"Boolean true should not strictly equal number 1"
);
let result = evaluator
.evaluate("(user_age > 20 && user_score >= 80) || user_premium")
.unwrap();
assert_eq!(result, Value::Bool(true));
}
#[test]
fn test_logical_operators_with_new_truthiness() {
let mut context = HashMap::new();
context.insert("emptyArr".to_string(), Value::Array(vec![]));
context.insert(
"emptyObj".to_string(),
Value::Object(serde_json::Map::new()),
);
let evaluator = Evaluator::new(context, HashMap::new());
let result = evaluator.evaluate("emptyArr && true").unwrap();
assert_eq!(
result,
Value::Bool(true),
"Empty array is truthy, so AND should evaluate second operand"
);
let result = evaluator.evaluate("emptyObj || false").unwrap();
assert_eq!(result, Value::Bool(true), "Empty object is truthy");
let result = evaluator.evaluate("!emptyArr").unwrap();
assert_eq!(
result,
Value::Bool(false),
"Negation of truthy empty array should be false"
);
}