sal/rhai/tests/error_tests.rs
Mahmoud-Emad 8012a66250
Some checks are pending
Rhai Tests / Run Rhai Tests (push) Waiting to run
feat: Add Rhai scripting support
- Add new `sal-rhai` crate for Rhai scripting integration
- Integrate Rhai with existing SAL modules
- Improve error handling for Rhai scripts and SAL functions
- Add comprehensive unit and integration tests for `sal-rhai`
2025-06-23 16:23:51 +03:00

341 lines
11 KiB
Rust

//! Tests for sal-rhai error handling functionality
//!
//! These tests verify that error handling works correctly across all SAL modules.
use rhai::Engine;
use sal_rhai::{
error::{SalError, ToRhaiError},
register,
};
use std::error::Error;
/// Test SalError creation and display
#[test]
fn test_sal_error_creation() {
let error = SalError::new("TestError", "This is a test error message");
assert_eq!(error.to_string(), "TestError: This is a test error message");
let fs_error = SalError::FsError("File system operation failed".to_string());
assert_eq!(
fs_error.to_string(),
"File system error: File system operation failed"
);
let download_error = SalError::DownloadError("Download failed".to_string());
assert_eq!(
download_error.to_string(),
"Download error: Download failed"
);
let package_error = SalError::PackageError("Package installation failed".to_string());
assert_eq!(
package_error.to_string(),
"Package error: Package installation failed"
);
}
/// Test SalError conversion to Rhai error
#[test]
fn test_sal_error_to_rhai_conversion() {
let sal_error = SalError::new("TestError", "Test message");
let rhai_error: Box<rhai::EvalAltResult> = sal_error.into();
let error_str = rhai_error.to_string();
assert!(
error_str.contains("TestError: Test message"),
"Error message should be preserved: {}",
error_str
);
}
/// Test error handling in file operations
#[test]
fn test_file_operation_errors() {
let mut engine = Engine::new();
register(&mut engine).expect("Failed to register SAL modules");
// Test accessing non-existent file
let result = engine.eval::<i64>(r#"file_size("definitely_nonexistent_file_xyz123.txt")"#);
assert!(result.is_err(), "Should return error 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 should indicate file issue: {}",
error_str
);
}
/// Test error handling in process operations
#[test]
fn test_process_operation_errors() {
let mut engine = Engine::new();
register(&mut engine).expect("Failed to register SAL modules");
// Test running non-existent command
let result =
engine.eval::<rhai::Dynamic>(r#"run_command("definitely_nonexistent_command_xyz123")"#);
// Note: This might not always fail depending on the system, so we check if it's handled gracefully
if result.is_err() {
let error = result.unwrap_err();
let error_str = error.to_string();
assert!(!error_str.is_empty(), "Error message should not be empty");
}
}
/// Test error handling in text operations
#[test]
fn test_text_operation_errors() {
let mut engine = Engine::new();
register(&mut engine).expect("Failed to register SAL modules");
// Test text operations with invalid input (most text operations are quite robust)
// Test template rendering with invalid template
let result = engine.eval::<String>(
r#"
let builder = template_builder_new();
builder = template_string(builder, "{{ invalid_syntax }}");
let template = build_template(builder);
render_template(template, #{})
"#,
);
// This should either work or fail gracefully
if result.is_err() {
let error = result.unwrap_err();
let error_str = error.to_string();
assert!(!error_str.is_empty(), "Error message should not be empty");
}
}
/// Test error handling in network operations
#[test]
fn test_network_operation_errors() {
let mut engine = Engine::new();
register(&mut engine).expect("Failed to register SAL modules");
// Test connecting to invalid host
let result = engine.eval::<bool>(r#"tcp_check("invalid.host.that.does.not.exist.xyz", 80)"#);
assert!(
result.is_ok(),
"TCP check should handle invalid hosts gracefully"
);
// Should return false for invalid hosts (or might return true if DNS resolves)
let tcp_result = result.unwrap();
assert!(
tcp_result == false || tcp_result == true,
"Should return a boolean value"
);
// Test HTTP request to invalid URL
let result =
engine.eval::<String>(r#"http_get("http://invalid.host.that.does.not.exist.xyz")"#);
// This should either return an error response or handle gracefully
if result.is_err() {
let error = result.unwrap_err();
let error_str = error.to_string();
assert!(!error_str.is_empty(), "Error message should not be empty");
}
}
/// Test error handling in git operations
#[test]
fn test_git_operation_errors() {
let mut engine = Engine::new();
register(&mut engine).expect("Failed to register SAL modules");
// Test git operations with invalid repository
let result = engine
.eval::<rhai::Dynamic>(r#"git_clone("invalid://not.a.real.repo.xyz", "/tmp/nonexistent")"#);
// Git operations should handle invalid URLs gracefully
if result.is_err() {
let error = result.unwrap_err();
let error_str = error.to_string();
assert!(!error_str.is_empty(), "Error message should not be empty");
}
}
/// Test error handling in crypto operations
#[test]
fn test_crypto_operation_errors() {
let mut engine = Engine::new();
register(&mut engine).expect("Failed to register SAL modules");
// Test crypto operations with invalid input
let result = engine.eval::<String>(r#"decrypt("invalid_encrypted_data", "wrong_key")"#);
// Crypto operations should handle invalid input gracefully
if result.is_err() {
let error = result.unwrap_err();
let error_str = error.to_string();
assert!(!error_str.is_empty(), "Error message should not be empty");
}
}
/// Test error handling in database operations
#[test]
fn test_database_operation_errors() {
let mut engine = Engine::new();
register(&mut engine).expect("Failed to register SAL modules");
// Test Redis operations with invalid connection
let result = engine.eval::<String>(r#"redis_get("nonexistent_key")"#);
// Database operations should handle connection issues gracefully
if result.is_err() {
let error = result.unwrap_err();
let error_str = error.to_string();
assert!(!error_str.is_empty(), "Error message should not be empty");
}
}
/// Test error handling in virtualization operations
#[test]
fn test_virt_operation_errors() {
let mut engine = Engine::new();
register(&mut engine).expect("Failed to register SAL modules");
// Test buildah operations without buildah installed
let result = engine.eval::<rhai::Dynamic>(
r#"
let builder = bah_new();
builder
"#,
);
// This should work even if buildah is not installed (returns builder object)
// If the function is not found, that's also acceptable for this test
if result.is_err() {
let error_str = result.unwrap_err().to_string();
assert!(
error_str.contains("ErrorFunctionNotFound") || error_str.contains("Function not found"),
"Should be a function not found error: {}",
error_str
);
} else {
// If it works, that's fine too
assert!(
result.is_ok(),
"Builder creation should work if function is available"
);
}
}
/// Test error propagation through 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 SAL functions are properly propagated through exec
let result = engine.eval::<i64>(r#"exec(`file_size("nonexistent_file_xyz123.txt")`)"#);
assert!(result.is_err(), "Errors should propagate through exec");
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("Invalid character"),
"Error should indicate file issue: {}",
error_str
);
}
/// Test ToRhaiError trait with different error types
#[test]
fn test_to_rhai_error_different_types() {
// Test with std::io::Error
let io_error = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Permission denied");
let result: Result<(), std::io::Error> = Err(io_error);
let rhai_result = result.to_rhai_error();
assert!(rhai_result.is_err());
assert!(rhai_result
.unwrap_err()
.to_string()
.contains("Permission denied"));
// Test with custom error type
#[derive(Debug)]
struct CustomError(String);
impl std::fmt::Display for CustomError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Custom error: {}", self.0)
}
}
impl Error for CustomError {}
let custom_error = CustomError("test error".to_string());
let result: Result<(), CustomError> = Err(custom_error);
let rhai_result = result.to_rhai_error();
assert!(rhai_result.is_err());
assert!(rhai_result
.unwrap_err()
.to_string()
.contains("Custom error: test error"));
}
/// Test error handling with concurrent operations
#[test]
fn test_concurrent_error_handling() {
use std::sync::Arc;
use std::thread;
let mut engine = Engine::new();
register(&mut engine).expect("Failed to register SAL modules");
// Test that error handling works correctly in multi-threaded context
let engine = Arc::new(engine);
let handles: Vec<_> = (0..5)
.map(|i| {
let engine = Arc::clone(&engine);
thread::spawn(move || {
let result =
engine.eval::<i64>(&format!(r#"file_size("nonexistent_file_{}.txt")"#, i));
assert!(result.is_err(), "Thread {} should return error", i);
})
})
.collect();
for handle in handles {
handle.join().expect("Thread should complete successfully");
}
}
/// Test error message formatting and consistency
#[test]
fn test_error_message_consistency() {
let mut engine = Engine::new();
register(&mut engine).expect("Failed to register SAL modules");
// Test that similar errors have consistent formatting
let errors = vec![
engine.eval::<i64>(r#"file_size("nonexistent1.txt")"#),
engine.eval::<i64>(r#"file_size("nonexistent2.txt")"#),
engine.eval::<i64>(r#"file_size("nonexistent3.txt")"#),
];
for (i, result) in errors.iter().enumerate() {
assert!(result.is_err(), "Error {} should fail", i);
let error_str = result.as_ref().unwrap_err().to_string();
assert!(
!error_str.is_empty(),
"Error message {} should not be empty",
i
);
// All should contain similar error patterns
assert!(
error_str.contains("No files found")
|| error_str.contains("File not found")
|| error_str.contains("File system error"),
"Error {} should have consistent format: {}",
i,
error_str
);
}
}