sal-kubernetes 0.1.0

SAL Kubernetes - Kubernetes cluster management and operations using kube-rs SDK
Documentation
//! 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"
    );
}