sal-rhai 0.1.0

SAL Rhai - Rhai scripting integration for the System Abstraction Layer
Documentation
//! Tests for sal-rhai core module functionality
//!
//! These tests verify the core Rhai integration functions work correctly.

use rhai::Engine;
use sal_rhai::{error::ToRhaiError, register};
use std::fs;
use tempfile::TempDir;

/// Test the ToRhaiError trait implementation
#[test]
fn test_to_rhai_error_trait() {
    // Test with a standard Result<T, E> where E implements std::error::Error
    let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found");
    let result: Result<String, std::io::Error> = Err(io_error);

    let rhai_result = result.to_rhai_error();
    assert!(rhai_result.is_err(), "Should convert to Rhai error");

    let error = rhai_result.unwrap_err();
    let error_str = error.to_string();
    assert!(
        error_str.contains("File not found"),
        "Error message should be preserved: {}",
        error_str
    );
}

/// Test the ToRhaiError trait with successful result
#[test]
fn test_to_rhai_error_success() {
    let result: Result<String, std::io::Error> = Ok("success".to_string());
    let rhai_result = result.to_rhai_error();
    assert!(rhai_result.is_ok(), "Should preserve successful result");
    assert_eq!(rhai_result.unwrap(), "success", "Value should be preserved");
}

/// Test core module registration
#[test]
fn test_core_module_registration() {
    let mut engine = Engine::new();

    // Register only the core module
    let result = sal_rhai::core::register_core_module(&mut engine);
    assert!(
        result.is_ok(),
        "Core module registration should succeed: {:?}",
        result
    );

    // Verify exec function is registered
    let script = r#"exec("42")"#;
    let result = engine.eval::<i64>(script);
    assert!(
        result.is_ok(),
        "Exec function should be available: {:?}",
        result
    );
    assert_eq!(
        result.unwrap(),
        42,
        "Exec should return the evaluated result"
    );
}

/// Test exec function with direct code execution
#[test]
fn test_exec_direct_code() {
    let mut engine = Engine::new();
    register(&mut engine).expect("Failed to register SAL modules");

    // Test simple arithmetic
    let result = engine.eval::<i64>(r#"exec("10 + 20")"#);
    assert!(result.is_ok(), "Direct code execution failed: {:?}", result);
    assert_eq!(result.unwrap(), 30, "Should return 30");

    // Test string operations
    let result = engine.eval::<String>(r#"exec(`"Hello" + " " + "World"`)"#);
    assert!(result.is_ok(), "String operation failed: {:?}", result);
    assert_eq!(result.unwrap(), "Hello World", "Should concatenate strings");

    // Test variable assignment and usage
    let result = engine.eval::<i64>(r#"exec("let x = 5; let y = 10; x * y")"#);
    assert!(result.is_ok(), "Variable operations failed: {:?}", result);
    assert_eq!(result.unwrap(), 50, "Should return 5 * 10 = 50");
}

/// Test exec function with file execution
#[test]
fn test_exec_file_execution() {
    let mut engine = Engine::new();
    register(&mut engine).expect("Failed to register SAL modules");

    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let script_file = temp_dir.path().join("test_exec.rhai");

    // Create a test script file
    let script_content = r#"
        let numbers = [1, 2, 3, 4, 5];
        let sum = 0;
        for num in numbers {
            sum += num;
        }
        sum
    "#;

    fs::write(&script_file, script_content).expect("Failed to write script file");

    // Execute the script file
    let exec_script = format!(r#"exec("{}")"#, script_file.display());
    let result = engine.eval::<i64>(&exec_script);
    assert!(result.is_ok(), "File execution failed: {:?}", result);
    assert_eq!(result.unwrap(), 15, "Should return sum of 1+2+3+4+5 = 15");
}

/// Test exec function with non-existent file
#[test]
fn test_exec_nonexistent_file() {
    let mut engine = Engine::new();
    register(&mut engine).expect("Failed to register SAL modules");

    // Try to execute a non-existent file
    let result = engine.eval::<i64>(r#"exec(`nonexistent_file_xyz123.rhai`)"#);
    assert!(result.is_err(), "Should fail for non-existent file");

    let error = result.unwrap_err();
    let error_str = error.to_string();
    assert!(
        error_str.contains("No files found")
            || error_str.contains("File not found")
            || error_str.contains("File system error")
            || error_str.contains("Variable not found"),
        "Error should indicate file not found: {}",
        error_str
    );
}

/// Test exec function with malformed Rhai code
#[test]
fn test_exec_malformed_code() {
    let mut engine = Engine::new();
    register(&mut engine).expect("Failed to register SAL modules");

    // Test with syntax error
    let result = engine.eval::<i64>(r#"exec("let x = ; // malformed")"#);
    assert!(result.is_err(), "Should fail for malformed code");

    // Test with undefined variable
    let result = engine.eval::<i64>(r#"exec("undefined_variable")"#);
    assert!(result.is_err(), "Should fail for undefined variable");
}

/// Test exec function with complex nested operations
#[test]
fn test_exec_complex_operations() {
    let mut engine = Engine::new();
    register(&mut engine).expect("Failed to register SAL modules");

    let complex_script = r#"
        exec(`
            fn factorial(n) {
                if n <= 1 {
                    1
                } else {
                    n * factorial(n - 1)
                }
            }
            factorial(5)
        `)
    "#;

    let result = engine.eval::<i64>(complex_script);
    assert!(result.is_ok(), "Complex operation failed: {:?}", result);
    assert_eq!(result.unwrap(), 120, "Should return 5! = 120");
}

/// Test exec function with SAL functions
#[test]
fn test_exec_with_sal_functions() {
    let mut engine = Engine::new();
    register(&mut engine).expect("Failed to register SAL modules");

    // Test using SAL functions within exec
    let script = r#"exec(`exist("Cargo.toml")`)"#;
    let result = engine.eval::<bool>(script);
    assert!(result.is_ok(), "SAL function in exec failed: {:?}", result);
    assert!(result.unwrap(), "Cargo.toml should exist");
}

/// Test exec function return types
#[test]
fn test_exec_return_types() {
    let mut engine = Engine::new();
    register(&mut engine).expect("Failed to register SAL modules");

    // Test boolean return
    let result = engine.eval::<bool>(r#"exec("true")"#);
    assert!(
        result.is_ok() && result.unwrap(),
        "Should return boolean true"
    );

    // Test string return
    let result = engine.eval::<String>(r#"exec(`"test string"`)"#);
    assert!(result.is_ok(), "String return failed: {:?}", result);
    assert_eq!(
        result.unwrap(),
        "test string",
        "Should return correct string"
    );

    // Test array return
    let result = engine.eval::<rhai::Array>(r#"exec("[1, 2, 3]")"#);
    assert!(result.is_ok(), "Array return failed: {:?}", result);
    let array = result.unwrap();
    assert_eq!(array.len(), 3, "Array should have 3 elements");

    // Test unit return (no return value)
    let result = engine.eval::<()>(r#"exec("let x = 42;")"#);
    assert!(result.is_ok(), "Unit return failed: {:?}", result);
}

/// Test error propagation in exec function
#[test]
fn test_exec_error_propagation() {
    let mut engine = Engine::new();
    register(&mut engine).expect("Failed to register SAL modules");

    // Test that errors from executed code are properly propagated
    let result = engine.eval::<i64>(r#"exec("1 / 0")"#);
    assert!(result.is_err(), "Division by zero should cause error");

    // Test that runtime errors are caught
    let result = engine.eval::<i64>(r#"exec("throw 'Custom error'")"#);
    assert!(result.is_err(), "Thrown errors should be caught");
}

/// Test exec function with file containing SAL operations
#[test]
fn test_exec_file_with_sal_operations() {
    let mut engine = Engine::new();
    register(&mut engine).expect("Failed to register SAL modules");

    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let script_file = temp_dir.path().join("sal_operations.rhai");

    // Create a script that uses SAL functions
    let script_content = r#"
        // Test text processing
        let text = "    indented text    ";
        let processed = dedent(text);
        let prefixed = prefix(processed, ">> ");
        
        // Return length of processed text
        prefixed.len()
    "#;

    fs::write(&script_file, script_content).expect("Failed to write script file");

    // Execute the script file
    let exec_script = format!(r#"exec("{}")"#, script_file.display());
    let result = engine.eval::<i64>(&exec_script);
    assert!(
        result.is_ok(),
        "SAL operations in file failed: {:?}",
        result
    );
    assert!(result.unwrap() > 0, "Should return positive length");
}