Some checks are pending
Rhai Tests / Run Rhai Tests (push) Waiting to run
- 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`
341 lines
11 KiB
Rust
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
|
|
);
|
|
}
|
|
}
|