//! Unit tests for SAL Kubernetes //! //! These tests focus on testing individual components and error handling //! without requiring a live Kubernetes cluster. use sal_kubernetes::KubernetesError; #[test] fn test_kubernetes_error_creation() { let config_error = KubernetesError::config_error("Test config error"); assert!(matches!(config_error, KubernetesError::ConfigError(_))); assert_eq!( config_error.to_string(), "Configuration error: Test config error" ); let operation_error = KubernetesError::operation_error("Test operation error"); assert!(matches!( operation_error, KubernetesError::OperationError(_) )); assert_eq!( operation_error.to_string(), "Operation failed: Test operation error" ); let namespace_error = KubernetesError::namespace_error("Test namespace error"); assert!(matches!( namespace_error, KubernetesError::NamespaceError(_) )); assert_eq!( namespace_error.to_string(), "Namespace error: Test namespace error" ); let permission_error = KubernetesError::permission_denied("Test permission error"); assert!(matches!( permission_error, KubernetesError::PermissionDenied(_) )); assert_eq!( permission_error.to_string(), "Permission denied: Test permission error" ); let timeout_error = KubernetesError::timeout("Test timeout error"); assert!(matches!(timeout_error, KubernetesError::Timeout(_))); assert_eq!( timeout_error.to_string(), "Operation timed out: Test timeout error" ); } #[test] fn test_regex_error_conversion() { use regex::Regex; // Test invalid regex pattern let invalid_pattern = "[invalid"; let regex_result = Regex::new(invalid_pattern); assert!(regex_result.is_err()); // Convert to KubernetesError let k8s_error = KubernetesError::from(regex_result.unwrap_err()); assert!(matches!(k8s_error, KubernetesError::RegexError(_))); } #[test] fn test_error_display() { let errors = vec![ KubernetesError::config_error("Config test"), KubernetesError::operation_error("Operation test"), KubernetesError::namespace_error("Namespace test"), KubernetesError::permission_denied("Permission test"), KubernetesError::timeout("Timeout test"), ]; for error in errors { let error_string = error.to_string(); assert!(!error_string.is_empty()); assert!(error_string.contains("test")); } } #[cfg(feature = "rhai")] #[test] fn test_rhai_module_registration() { use rhai::Engine; use sal_kubernetes::rhai::register_kubernetes_module; let mut engine = Engine::new(); let result = register_kubernetes_module(&mut engine); assert!( result.is_ok(), "Failed to register Kubernetes module: {:?}", result ); } #[cfg(feature = "rhai")] #[test] fn test_rhai_functions_registered() { use rhai::Engine; use sal_kubernetes::rhai::register_kubernetes_module; let mut engine = Engine::new(); register_kubernetes_module(&mut engine).unwrap(); // Test that functions are registered by checking if they exist in the engine // We can't actually call async functions without a runtime, so we just verify registration // Check that the main functions are registered by looking for them in the engine let function_names = vec![ "kubernetes_manager_new", "pods_list", "services_list", "deployments_list", "delete", "namespace_create", "namespace_exists", ]; for function_name in function_names { // Try to parse a script that references the function // This will succeed if the function is registered, even if we don't call it let script = format!("let f = {};", function_name); let result = engine.compile(&script); assert!( result.is_ok(), "Function '{}' should be registered in the engine", function_name ); } } #[test] fn test_namespace_validation() { // Test valid namespace names let valid_names = vec!["default", "kube-system", "my-app", "test123"]; for name in valid_names { assert!(!name.is_empty()); assert!(name.chars().all(|c| c.is_alphanumeric() || c == '-')); } } #[test] fn test_resource_name_patterns() { use regex::Regex; // Test common patterns that might be used with the delete function let patterns = vec![ r"test-.*", // Match anything starting with "test-" r".*-temp$", // Match anything ending with "-temp" r"^pod-\d+$", // Match "pod-" followed by digits r"app-[a-z]+", // Match "app-" followed by lowercase letters ]; for pattern in patterns { let regex = Regex::new(pattern); assert!(regex.is_ok(), "Pattern '{}' should be valid", pattern); let regex = regex.unwrap(); // Test some example matches based on the pattern match pattern { r"test-.*" => { assert!(regex.is_match("test-pod")); assert!(regex.is_match("test-service")); assert!(!regex.is_match("prod-pod")); } r".*-temp$" => { assert!(regex.is_match("my-pod-temp")); assert!(regex.is_match("service-temp")); assert!(!regex.is_match("temp-pod")); } r"^pod-\d+$" => { assert!(regex.is_match("pod-123")); assert!(regex.is_match("pod-1")); assert!(!regex.is_match("pod-abc")); assert!(!regex.is_match("service-123")); } r"app-[a-z]+" => { assert!(regex.is_match("app-frontend")); assert!(regex.is_match("app-backend")); assert!(!regex.is_match("app-123")); assert!(!regex.is_match("service-frontend")); } _ => {} } } } #[test] fn test_invalid_regex_patterns() { use regex::Regex; // Test invalid regex patterns that should fail let invalid_patterns = vec![ "[invalid", // Unclosed bracket "*invalid", // Invalid quantifier "(?invalid)", // Invalid group "\\", // Incomplete escape ]; for pattern in invalid_patterns { let regex = Regex::new(pattern); assert!(regex.is_err(), "Pattern '{}' should be invalid", pattern); } } #[test] fn test_kubernetes_config_creation() { use sal_kubernetes::KubernetesConfig; use std::time::Duration; // Test default configuration let default_config = KubernetesConfig::default(); assert_eq!(default_config.operation_timeout, Duration::from_secs(30)); assert_eq!(default_config.max_retries, 3); assert_eq!(default_config.rate_limit_rps, 10); assert_eq!(default_config.rate_limit_burst, 20); // Test custom configuration let custom_config = KubernetesConfig::new() .with_timeout(Duration::from_secs(60)) .with_retries(5, Duration::from_secs(2), Duration::from_secs(60)) .with_rate_limit(50, 100); assert_eq!(custom_config.operation_timeout, Duration::from_secs(60)); assert_eq!(custom_config.max_retries, 5); assert_eq!(custom_config.retry_base_delay, Duration::from_secs(2)); assert_eq!(custom_config.retry_max_delay, Duration::from_secs(60)); assert_eq!(custom_config.rate_limit_rps, 50); assert_eq!(custom_config.rate_limit_burst, 100); // Test pre-configured profiles let high_throughput = KubernetesConfig::high_throughput(); assert_eq!(high_throughput.rate_limit_rps, 50); assert_eq!(high_throughput.rate_limit_burst, 100); let low_latency = KubernetesConfig::low_latency(); assert_eq!(low_latency.operation_timeout, Duration::from_secs(10)); assert_eq!(low_latency.max_retries, 2); let development = KubernetesConfig::development(); assert_eq!(development.operation_timeout, Duration::from_secs(120)); assert_eq!(development.rate_limit_rps, 100); } #[test] fn test_retryable_error_detection() { use kube::Error as KubeError; use sal_kubernetes::kubernetes_manager::is_retryable_error; // Test that the function exists and works with basic error types // Note: We can't easily create all error types, so we test what we can // Test API errors with different status codes let api_error_500 = KubeError::Api(kube::core::ErrorResponse { status: "Failure".to_string(), message: "Internal server error".to_string(), reason: "InternalError".to_string(), code: 500, }); assert!( is_retryable_error(&api_error_500), "500 errors should be retryable" ); let api_error_429 = KubeError::Api(kube::core::ErrorResponse { status: "Failure".to_string(), message: "Too many requests".to_string(), reason: "TooManyRequests".to_string(), code: 429, }); assert!( is_retryable_error(&api_error_429), "429 errors should be retryable" ); let api_error_404 = KubeError::Api(kube::core::ErrorResponse { status: "Failure".to_string(), message: "Not found".to_string(), reason: "NotFound".to_string(), code: 404, }); assert!( !is_retryable_error(&api_error_404), "404 errors should not be retryable" ); let api_error_400 = KubeError::Api(kube::core::ErrorResponse { status: "Failure".to_string(), message: "Bad request".to_string(), reason: "BadRequest".to_string(), code: 400, }); assert!( !is_retryable_error(&api_error_400), "400 errors should not be retryable" ); }