//! Comprehensive End-to-End Tests for HashiCorp Vault Integration //! //! This module provides comprehensive E2E testing for Vault integration in the Foxhunt HFT system: //! - Real Vault server testing with PKI secrets engine //! - Service startup dependency validation //! - Certificate lifecycle management (generation, caching, rotation) //! - Failure scenario testing (Vault down, network partitions, timeouts) //! - Performance impact measurement for HFT requirements //! - Circuit breaker behavior validation //! - Multi-service integration testing use anyhow::{Context, Result}; use std::collections::HashMap; use std::sync::Arc; use std::time::{Duration, Instant}; use tokio::sync::RwLock; use tracing::{debug, info, warn, error}; // Re-export test modules pub mod docker_compose; pub mod vault_connectivity_tests; pub mod certificate_lifecycle_tests; pub mod service_integration_tests; pub mod failure_scenario_tests; pub mod performance_impact_tests; /// Configuration for Vault E2E tests #[derive(Debug, Clone)] pub struct VaultTestConfig { /// Vault server address for testing pub vault_addr: String, /// Vault root token for test setup pub vault_token: String, /// Test timeout for individual tests pub test_timeout: Duration, /// Performance test iterations pub perf_iterations: u32, /// Docker Compose project name pub compose_project: String, /// Certificate cache directory for tests pub cert_cache_dir: String, /// Whether to cleanup resources after tests pub cleanup_after_tests: bool, } impl Default for VaultTestConfig { fn default() -> Self { Self { vault_addr: "http://localhost:8200".to_string(), vault_token: "vault-root-token".to_string(), test_timeout: Duration::from_secs(30), perf_iterations: 1000, compose_project: "foxhunt-vault-e2e".to_string(), cert_cache_dir: "/tmp/foxhunt-vault-test-certs".to_string(), cleanup_after_tests: true, } } } /// Performance metrics for test validation #[derive(Debug, Clone, Default)] pub struct PerformanceMetrics { /// Certificate generation time from Vault pub cert_generation_time: Option, /// Certificate cache lookup time pub cache_lookup_time: Option, /// TLS handshake time with Vault certificates pub tls_handshake_time: Option, /// Memory usage during certificate operations pub memory_usage_mb: Option, /// CPU usage during background rotation pub cpu_usage_percent: Option, /// Vault API call count pub vault_api_calls: u64, /// Cache hit rate percentage pub cache_hit_rate: Option, } impl PerformanceMetrics { pub fn new() -> Self { Self::default() } /// Validate metrics against HFT requirements pub fn validate_hft_requirements(&self) -> Result<()> { // Certificate generation should be under 100ms if let Some(cert_time) = self.cert_generation_time { if cert_time > Duration::from_millis(100) { return Err(anyhow::anyhow!( "Certificate generation time {}ms exceeds 100ms HFT requirement", cert_time.as_millis() )); } } // Cache lookup should be under 1μs if let Some(cache_time) = self.cache_lookup_time { if cache_time > Duration::from_micros(1) { return Err(anyhow::anyhow!( "Cache lookup time {}μs exceeds 1μs HFT requirement", cache_time.as_micros() )); } } // TLS handshake should be under 1ms for cached certificates if let Some(tls_time) = self.tls_handshake_time { if tls_time > Duration::from_millis(1) { return Err(anyhow::anyhow!( "TLS handshake time {}ms exceeds 1ms HFT requirement", tls_time.as_millis() )); } } // Memory usage should be under 10MB per service if let Some(memory) = self.memory_usage_mb { if memory > 10.0 { return Err(anyhow::anyhow!( "Memory usage {:.1}MB exceeds 10MB HFT requirement", memory )); } } // CPU usage should be under 5% for background tasks if let Some(cpu) = self.cpu_usage_percent { if cpu > 5.0 { return Err(anyhow::anyhow!( "CPU usage {:.1}% exceeds 5% HFT requirement", cpu )); } } // Cache hit rate should be over 80% if let Some(hit_rate) = self.cache_hit_rate { if hit_rate < 80.0 { return Err(anyhow::anyhow!( "Cache hit rate {:.1}% is below 80% efficiency requirement", hit_rate )); } } Ok(()) } } /// Test results aggregation for comprehensive reporting #[derive(Debug, Default)] pub struct VaultTestResults { /// Successful tests with timing pub successes: Vec<(String, Duration)>, /// Failed tests with error messages pub failures: Vec<(String, String)>, /// Performance metrics collected pub performance: PerformanceMetrics, /// Additional metadata pub metadata: HashMap, } impl VaultTestResults { pub fn new() -> Self { Self::default() } pub fn add_success(&mut self, test_name: &str, duration: Duration) { self.successes.push((test_name.to_string(), duration)); info!("✓ Test passed: {} ({}ms)", test_name, duration.as_millis()); } pub fn add_failure(&mut self, test_name: &str, error: String) { self.failures.push((test_name.to_string(), error.clone())); error!("✗ Test failed: {} - {}", test_name, error); } pub fn add_metadata(&mut self, key: &str, value: String) { self.metadata.insert(key.to_string(), value); } pub fn success_count(&self) -> usize { self.successes.len() } pub fn failure_count(&self) -> usize { self.failures.len() } pub fn total_count(&self) -> usize { self.successes.len() + self.failures.len() } pub fn success_rate(&self) -> f64 { if self.total_count() == 0 { return 0.0; } self.success_count() as f64 / self.total_count() as f64 } /// Generate comprehensive test report pub fn generate_report(&self) -> String { let mut report = String::new(); report.push_str("\n======= VAULT E2E TEST RESULTS =======\n"); report.push_str(&format!("Total Tests: {}\n", self.total_count())); report.push_str(&format!("Successes: {}\n", self.success_count())); report.push_str(&format!("Failures: {}\n", self.failure_count())); report.push_str(&format!("Success Rate: {:.1}%\n", self.success_rate() * 100.0)); // Performance metrics report.push_str("\n--- Performance Metrics ---\n"); if let Some(cert_time) = self.performance.cert_generation_time { report.push_str(&format!("Certificate Generation: {}ms\n", cert_time.as_millis())); } if let Some(cache_time) = self.performance.cache_lookup_time { report.push_str(&format!("Cache Lookup: {}μs\n", cache_time.as_micros())); } if let Some(tls_time) = self.performance.tls_handshake_time { report.push_str(&format!("TLS Handshake: {}ms\n", tls_time.as_millis())); } if let Some(memory) = self.performance.memory_usage_mb { report.push_str(&format!("Memory Usage: {:.1}MB\n", memory)); } if let Some(cpu) = self.performance.cpu_usage_percent { report.push_str(&format!("CPU Usage: {:.1}%\n", cpu)); } if let Some(hit_rate) = self.performance.cache_hit_rate { report.push_str(&format!("Cache Hit Rate: {:.1}%\n", hit_rate)); } report.push_str(&format!("Vault API Calls: {}\n", self.performance.vault_api_calls)); // HFT requirements validation report.push_str("\n--- HFT Requirements Validation ---\n"); match self.performance.validate_hft_requirements() { Ok(()) => report.push_str("✓ All HFT performance requirements met\n"), Err(e) => report.push_str(&format!("✗ HFT requirement violation: {}\n", e)), } // Successful tests if !self.successes.is_empty() { report.push_str("\n--- Successful Tests ---\n"); for (name, duration) in &self.successes { report.push_str(&format!("✓ {} - {}ms\n", name, duration.as_millis())); } } // Failed tests if !self.failures.is_empty() { report.push_str("\n--- Failed Tests ---\n"); for (name, error) in &self.failures { report.push_str(&format!("✗ {} - {}\n", name, error)); } } // Metadata if !self.metadata.is_empty() { report.push_str("\n--- Test Metadata ---\n"); for (key, value) in &self.metadata { report.push_str(&format!("{}: {}\n", key, value)); } } report.push_str("======================================\n"); report } } /// Main Vault E2E test suite coordinator pub struct VaultE2ETestSuite { config: VaultTestConfig, results: Arc>, } impl VaultE2ETestSuite { /// Create new test suite with configuration pub fn new(config: VaultTestConfig) -> Self { Self { config, results: Arc::new(RwLock::new(VaultTestResults::new())), } } /// Run all Vault E2E tests in sequence pub async fn run_all_tests(&self) -> Result { info!("Starting comprehensive Vault E2E test suite"); // Add test metadata { let mut results = self.results.write().await; results.add_metadata("vault_addr".to_string(), self.config.vault_addr.clone()); results.add_metadata("test_started".to_string(), chrono::Utc::now().to_rfc3339()); } // Test 1: Docker environment setup self.run_docker_setup_tests().await?; // Test 2: Basic Vault connectivity self.run_connectivity_tests().await?; // Test 3: Certificate lifecycle management self.run_certificate_tests().await?; // Test 4: Service integration testing self.run_service_integration_tests().await?; // Test 5: Failure scenario testing self.run_failure_scenario_tests().await?; // Test 6: Performance impact validation self.run_performance_tests().await?; // Test 7: Cleanup and validation if self.config.cleanup_after_tests { self.run_cleanup_tests().await?; } let results = self.results.read().await; info!("Vault E2E test suite completed: {}/{} tests passed", results.success_count(), results.total_count()); Ok(results.clone()) } /// Run Docker environment setup tests async fn run_docker_setup_tests(&self) -> Result<()> { info!("Running Docker environment setup tests"); let test_start = Instant::now(); match docker_compose::DockerEnvironment::new(&self.config).await { Ok(mut env) => { if let Err(e) = env.start_all_services().await { let mut results = self.results.write().await; results.add_failure("docker_environment_startup", e.to_string()); return Err(e); } let mut results = self.results.write().await; results.add_success("docker_environment_startup", test_start.elapsed()); Ok(()) } Err(e) => { let mut results = self.results.write().await; results.add_failure("docker_environment_setup", e.to_string()); Err(e) } } } /// Run Vault connectivity tests async fn run_connectivity_tests(&self) -> Result<()> { info!("Running Vault connectivity tests"); match vault_connectivity_tests::run_connectivity_tests(&self.config).await { Ok(duration) => { let mut results = self.results.write().await; results.add_success("vault_connectivity", duration); Ok(()) } Err(e) => { let mut results = self.results.write().await; results.add_failure("vault_connectivity", e.to_string()); Err(e) } } } /// Run certificate lifecycle tests async fn run_certificate_tests(&self) -> Result<()> { info!("Running certificate lifecycle tests"); match certificate_lifecycle_tests::run_certificate_tests(&self.config, &self.results).await { Ok(()) => Ok(()), Err(e) => { let mut results = self.results.write().await; results.add_failure("certificate_lifecycle", e.to_string()); Err(e) } } } /// Run service integration tests async fn run_service_integration_tests(&self) -> Result<()> { info!("Running service integration tests"); match service_integration_tests::run_service_tests(&self.config, &self.results).await { Ok(()) => Ok(()), Err(e) => { let mut results = self.results.write().await; results.add_failure("service_integration", e.to_string()); Err(e) } } } /// Run failure scenario tests async fn run_failure_scenario_tests(&self) -> Result<()> { info!("Running failure scenario tests"); match failure_scenario_tests::run_failure_tests(&self.config, &self.results).await { Ok(()) => Ok(()), Err(e) => { let mut results = self.results.write().await; results.add_failure("failure_scenarios", e.to_string()); Err(e) } } } /// Run performance impact tests async fn run_performance_tests(&self) -> Result<()> { info!("Running performance impact tests"); match performance_impact_tests::run_performance_tests(&self.config, &self.results).await { Ok(()) => Ok(()), Err(e) => { let mut results = self.results.write().await; results.add_failure("performance_validation", e.to_string()); Err(e) } } } /// Run cleanup and validation tests async fn run_cleanup_tests(&self) -> Result<()> { info!("Running cleanup tests"); let test_start = Instant::now(); // Cleanup Docker resources if let Err(e) = docker_compose::DockerEnvironment::cleanup(&self.config).await { warn!("Docker cleanup failed: {}", e); } let mut results = self.results.write().await; results.add_success("cleanup", test_start.elapsed()); Ok(()) } } #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_vault_test_config() { let config = VaultTestConfig::default(); assert_eq!(config.vault_addr, "http://localhost:8200"); assert_eq!(config.vault_token, "vault-root-token"); assert!(config.cleanup_after_tests); } #[test] fn test_performance_metrics_validation() { let mut metrics = PerformanceMetrics::new(); // Should pass with good metrics metrics.cert_generation_time = Some(Duration::from_millis(50)); metrics.cache_lookup_time = Some(Duration::from_nanos(500)); metrics.tls_handshake_time = Some(Duration::from_millis(1)); metrics.memory_usage_mb = Some(5.0); metrics.cpu_usage_percent = Some(2.0); metrics.cache_hit_rate = Some(90.0); assert!(metrics.validate_hft_requirements().is_ok()); // Should fail with bad metrics metrics.cert_generation_time = Some(Duration::from_millis(150)); assert!(metrics.validate_hft_requirements().is_err()); } #[test] fn test_results_aggregation() { let mut results = VaultTestResults::new(); results.add_success("test1", Duration::from_millis(10)); results.add_failure("test2", "Test error".to_string()); assert_eq!(results.success_count(), 1); assert_eq!(results.failure_count(), 1); assert_eq!(results.success_rate(), 0.5); let report = results.generate_report(); assert!(report.contains("Total Tests: 2")); assert!(report.contains("Success Rate: 50.0%")); } }