Files
foxhunt/testing/vault-integration/mod.rs
jgrusewski 9c3d741a08 refactor: restructure repo — crates/, bin/, testing/ layout
Move 17 library crates into crates/, CLI binary into bin/fxt,
consolidate 10 test crates into testing/, split config crate
from deployment config files.

Root directory reduced from 38+ to ~17 directories.
All Cargo.toml paths and build.rs proto refs updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 11:56:00 +01:00

485 lines
17 KiB
Rust

//! 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<Duration>,
/// Certificate cache lookup time
pub cache_lookup_time: Option<Duration>,
/// TLS handshake time with Vault certificates
pub tls_handshake_time: Option<Duration>,
/// Memory usage during certificate operations
pub memory_usage_mb: Option<f64>,
/// CPU usage during background rotation
pub cpu_usage_percent: Option<f64>,
/// Vault API call count
pub vault_api_calls: u64,
/// Cache hit rate percentage
pub cache_hit_rate: Option<f64>,
}
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<String, String>,
}
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<RwLock<VaultTestResults>>,
}
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<VaultTestResults> {
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%"));
}
}