- Add Kubernetes cluster management and operations - Include pod, service, and deployment management - Implement pattern-based resource deletion - Support namespace creation and management - Provide Rhai scripting wrappers for all functions - Include production safety features (timeouts, retries, rate limiting)
304 lines
9.6 KiB
Rust
304 lines
9.6 KiB
Rust
//! 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"
|
|
);
|
|
}
|