use std::collections::HashMap; use std::sync::{Arc, RwLock}; use rhai::{Engine, AST}; use tera::{Value, Function}; use tera_factory::function_adapter::RhaiFunctionAdapter; // Helper function to create a test AST with a simple function fn create_test_ast(fn_name: &str, fn_body: &str) -> AST { let engine = Engine::new(); // Create the full script with the function let script = format!("fn {}() {{ {} }}", fn_name, fn_body); println!("Creating test AST with script: {}", script); engine.compile(script).expect("Failed to compile test script") } #[test] fn test_tera_to_rhai_conversion() { println!("Running test_tera_to_rhai_conversion"); // Create a test function that echoes its input let ast = create_test_ast("echo", "params[0]"); let hot_ast = Arc::new(RwLock::new(ast)); // Create a function adapter let adapter = RhaiFunctionAdapter { fn_name: "echo".to_string(), hot_ast: hot_ast.clone(), }; // Test with different Tera value types let test_cases = vec![ // String ("string_value", Value::String("hello".to_string()), "hello"), // Integer ("int_value", Value::Number(42.into()), "42"), // Float ("float_value", Value::Number(serde_json::Number::from_f64(3.14).unwrap()), "3.14"), // Boolean ("bool_value", Value::Bool(true), "true"), // Array ("array_value", Value::Array(vec![ Value::Number(1.into()), Value::Number(2.into()), Value::Number(3.into()), ]), "[1, 2, 3]"), // Object ("object_value", { let mut obj = serde_json::Map::new(); obj.insert("name".to_string(), Value::String("John".to_string())); obj.insert("age".to_string(), Value::Number(30.into())); Value::Object(obj) }, "{\"age\": 30, \"name\": \"John\"}"), ]; for (name, tera_value, expected_output) in test_cases { println!("Testing conversion of {}: {:?}", name, tera_value); // Create args with the test value let mut args = HashMap::new(); args.insert("value".to_string(), tera_value); // Call the function adapter let result = adapter.call(&args); // Verify the result assert!(result.is_ok(), "Failed to call function with {}", name); let result_value = result.unwrap(); // For direct value comparison instead of string comparison println!("Result for {}: {:?}", name, result_value); // Compare based on the expected type match (name, &result_value) { ("string_value", Value::String(s)) => { assert_eq!(s, "hello", "Incorrect string result for {}", name); }, ("int_value", Value::Number(n)) => { assert!(n.is_i64(), "Expected integer for {}", name); assert_eq!(n.as_i64().unwrap(), 42, "Incorrect integer result for {}", name); }, ("float_value", Value::Number(n)) => { assert!(n.is_f64(), "Expected float for {}", name); assert!((n.as_f64().unwrap() - 3.14).abs() < 0.001, "Incorrect float result for {}", name); }, ("bool_value", Value::Bool(b)) => { assert_eq!(*b, true, "Incorrect boolean result for {}", name); }, ("array_value", Value::Array(arr)) => { assert_eq!(arr.len(), 3, "Incorrect array length for {}", name); if let (Some(Value::Number(n1)), Some(Value::Number(n2)), Some(Value::Number(n3))) = (arr.get(0), arr.get(1), arr.get(2)) { assert_eq!(n1.as_i64().unwrap(), 1, "Incorrect first array element for {}", name); assert_eq!(n2.as_i64().unwrap(), 2, "Incorrect second array element for {}", name); assert_eq!(n3.as_i64().unwrap(), 3, "Incorrect third array element for {}", name); } else { panic!("Array elements have incorrect types for {}", name); } }, ("object_value", Value::Object(obj)) => { assert_eq!(obj.len(), 2, "Incorrect object size for {}", name); assert!(obj.contains_key("name"), "Object missing 'name' key for {}", name); assert!(obj.contains_key("age"), "Object missing 'age' key for {}", name); if let Some(Value::String(name_val)) = obj.get("name") { assert_eq!(name_val, "John", "Incorrect name value for {}", name); } else { panic!("Name has incorrect type for {}", name); } if let Some(Value::Number(age_val)) = obj.get("age") { assert_eq!(age_val.as_i64().unwrap(), 30, "Incorrect age value for {}", name); } else { panic!("Age has incorrect type for {}", name); } }, _ => panic!("Unexpected result type for {}: {:?}", name, result_value), } } println!("test_tera_to_rhai_conversion passed"); } #[test] fn test_rhai_to_tera_conversion() { println!("Running test_rhai_to_tera_conversion"); // Create test functions that return different types let test_cases = vec![ // Integer ("return_int", "42", Value::Number(42.into())), // Float ("return_float", "3.14", Value::Number(serde_json::Number::from_f64(3.14).unwrap())), // String ("return_string", "\"hello\"", Value::String("hello".to_string())), // Boolean ("return_bool", "true", Value::Bool(true)), // Array ("return_array", "[1, 2, 3]", Value::Array(vec![ Value::Number(1.into()), Value::Number(2.into()), Value::Number(3.into()), ])), // Object/Map ("return_map", "#{\"name\": \"John\", \"age\": 30}", { let mut obj = serde_json::Map::new(); obj.insert("name".to_string(), Value::String("John".to_string())); obj.insert("age".to_string(), Value::Number(30.into())); Value::Object(obj) }), ]; for (fn_name, fn_body, expected_value) in test_cases { println!("Testing conversion of Rhai {} to Tera value", fn_name); // Create the AST with the test function let ast = create_test_ast(fn_name, fn_body); let hot_ast = Arc::new(RwLock::new(ast)); // Create a function adapter let adapter = RhaiFunctionAdapter { fn_name: fn_name.to_string(), hot_ast: hot_ast.clone(), }; // Call the function adapter with empty args let args = HashMap::new(); let result = adapter.call(&args); // Verify the result assert!(result.is_ok(), "Failed to call function {}", fn_name); let result_value = result.unwrap(); println!("Result for {}: {:?}", fn_name, result_value); // Compare with expected value match (&result_value, &expected_value) { (Value::Number(n1), Value::Number(n2)) => { // Special handling for numbers to handle float comparison if n1.is_f64() || n2.is_f64() { let f1 = n1.as_f64().unwrap(); let f2 = n2.as_f64().unwrap(); assert!((f1 - f2).abs() < 0.0001, "Incorrect float result for {}", fn_name); } else { assert_eq!(n1, n2, "Incorrect number result for {}", fn_name); } }, (Value::Array(a1), Value::Array(a2)) => { assert_eq!(a1.len(), a2.len(), "Array length mismatch for {}", fn_name); // Compare each element for (i, (v1, v2)) in a1.iter().zip(a2.iter()).enumerate() { assert_eq!(v1, v2, "Array element {} mismatch for {}", i, fn_name); } }, (Value::Object(o1), Value::Object(o2)) => { assert_eq!(o1.len(), o2.len(), "Object key count mismatch for {}", fn_name); // Compare each key-value pair for (k, v1) in o1 { assert!(o2.contains_key(k), "Missing key {} in object for {}", k, fn_name); assert_eq!(v1, o2.get(k).unwrap(), "Value mismatch for key {} in {}", k, fn_name); } }, _ => { assert_eq!(result_value, expected_value, "Incorrect result for {}", fn_name); } } } println!("test_rhai_to_tera_conversion passed"); } #[test] fn test_error_handling_in_function_adapter() { println!("Running test_error_handling_in_function_adapter"); // Create a test function that throws an error let ast = create_test_ast("throw_error", "throw \"This is a test error\";"); let hot_ast = Arc::new(RwLock::new(ast)); // Create a function adapter let adapter = RhaiFunctionAdapter { fn_name: "throw_error".to_string(), hot_ast: hot_ast.clone(), }; // Call the function adapter let args = HashMap::new(); let result = adapter.call(&args); // Verify that the call returns an error assert!(result.is_err(), "Function should return an error"); let error = result.err().unwrap(); println!("Error (expected): {:?}", error); // Create a test function that doesn't exist let adapter_non_existent = RhaiFunctionAdapter { fn_name: "non_existent_function".to_string(), hot_ast: hot_ast.clone(), }; // Call the function adapter let result = adapter_non_existent.call(&args); // Verify that the call returns an error assert!(result.is_err(), "Call to non-existent function should return an error"); let error = result.err().unwrap(); println!("Error for non-existent function (expected): {:?}", error); println!("test_error_handling_in_function_adapter passed"); } #[test] fn test_function_with_parameters() { println!("Running test_function_with_parameters"); // Create a test function that takes parameters let ast = create_test_ast("add", "params[0] + params[1]"); let hot_ast = Arc::new(RwLock::new(ast)); // Create a function adapter let adapter = RhaiFunctionAdapter { fn_name: "add".to_string(), hot_ast: hot_ast.clone(), }; // Create args with the test values let mut args = HashMap::new(); args.insert("a".to_string(), Value::Number(5.into())); args.insert("b".to_string(), Value::Number(7.into())); // Call the function adapter let result = adapter.call(&args); // Verify the result assert!(result.is_ok(), "Failed to call function with parameters"); let result_value = result.unwrap(); println!("Result for add(5, 7): {:?}", result_value); assert_eq!(result_value, Value::Number(12.into()), "Incorrect result for add function"); println!("test_function_with_parameters passed"); } #[test] fn test_concurrent_access() { use std::thread; println!("Running test_concurrent_access"); // Create a test function let ast = create_test_ast("increment", "params[0] + 1"); let hot_ast = Arc::new(RwLock::new(ast)); // Create a function adapter let _adapter = RhaiFunctionAdapter { fn_name: "increment".to_string(), hot_ast: hot_ast.clone(), }; // Create multiple threads to call the function concurrently let mut handles = vec![]; for i in 0..10 { // Create a new adapter for this thread with the same function name and AST let adapter_clone = RhaiFunctionAdapter { fn_name: "increment".to_string(), hot_ast: Arc::clone(&hot_ast), }; let handle = thread::spawn(move || { // Create args with the test value let mut args = HashMap::new(); args.insert("value".to_string(), Value::Number(i.into())); // Call the function adapter let result = adapter_clone.call(&args); // Verify the result assert!(result.is_ok(), "Failed to call function in thread {}", i); let result_value = result.unwrap(); println!("Thread {} result: {:?}", i, result_value); assert_eq!(result_value, Value::Number((i + 1).into()), "Incorrect result for increment function in thread {}", i); }); handles.push(handle); } // Wait for all threads to complete for (i, handle) in handles.into_iter().enumerate() { handle.join().unwrap(); println!("Thread {} completed successfully", i); } println!("test_concurrent_access passed"); }