//! 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 = 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::(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::(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::( 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::(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::(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::(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::(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::(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::( 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::(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::(&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::(r#"file_size("nonexistent1.txt")"#), engine.eval::(r#"file_size("nonexistent2.txt")"#), engine.eval::(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 ); } }