//! Comprehensive End-to-End ML Training Workflow Tests //! //! Tests the complete integration pipeline: //! TLI → MLTrainingService → Trading Service //! //! Validates: //! - Model training → deployment → inference pipeline //! - Training data ingestion → processing → model update //! - Failure scenarios and recovery testing //! - Performance requirements and regression detection use anyhow::Result; use std::time::Duration; use tokio::time::{sleep, timeout}; use uuid::Uuid; use crate::harness::{TestHarness, TestResult}; use crate::harness::grpc_clients::*; use crate::harness::test_data::{TestDataGenerator, ModelArtifact}; use crate::harness::performance::RegressionResult; /// Complete ML training workflow integration tests pub struct MLTrainingWorkflowTests { harness: TestHarness, } impl MLTrainingWorkflowTests { pub async fn new() -> Result { let harness = TestHarness::new().await?; Ok(Self { harness }) } /// Execute all comprehensive workflow tests pub async fn run_all_tests(&mut self) -> Result> { let mut results = Vec::new(); // Setup test environment self.harness.setup().await?; // Layer 1: Foundation Tests results.push(self.test_service_connectivity().await?); results.push(self.test_health_checks().await?); // Layer 2: Integration Tests results.push(self.test_tli_to_ml_training_integration().await?); results.push(self.test_ml_training_to_trading_integration().await?); // Layer 3: End-to-End Workflow Tests results.push(self.test_complete_training_pipeline().await?); results.push(self.test_model_deployment_workflow().await?); results.push(self.test_live_trading_with_ml_workflow().await?); results.push(self.test_model_update_and_rollback_workflow().await?); // Layer 4: Performance Tests results.push(self.test_training_performance_requirements().await?); results.push(self.test_inference_latency_requirements().await?); // Layer 5: Failure and Recovery Tests results.push(self.test_training_failure_recovery().await?); results.push(self.test_deployment_failure_recovery().await?); results.push(self.test_service_restart_recovery().await?); // Cleanup self.harness.cleanup().await?; Ok(results) } /// Test basic service connectivity async fn test_service_connectivity(&mut self) -> Result { self.harness.execute_scenario("service_connectivity", |harness| async move { // Test all service endpoints are reachable let endpoints = harness.grpc_clients.get_endpoints(); for (service_name, endpoint) in endpoints { println!("Testing connectivity to {}: {}", service_name, endpoint); // This would test actual connectivity // For now, simulate the test sleep(Duration::from_millis(100)).await; } assert!(harness.grpc_clients.are_all_healthy().await?, "All services should be healthy and reachable"); Ok(()) }).await } /// Test service health checks async fn test_health_checks(&mut self) -> Result { self.harness.execute_scenario("health_checks", |harness| async move { let start_time = std::time::Instant::now(); // Test TLI health harness.grpc_clients.tli_client.health_check().await?; harness.performance.record_latency("health_checks", "tli_health_check", start_time.elapsed()); let start_time = std::time::Instant::now(); // Test ML Training Service health let _resource_response = harness.grpc_clients.ml_training_client.clone() .get_resource_utilization(foxhunt_ml::ResourceRequest {}).await?; harness.performance.record_latency("health_checks", "ml_training_health_check", start_time.elapsed()); let start_time = std::time::Instant::now(); // Test Trading Service health harness.grpc_clients.trading_client.health_check().await?; harness.performance.record_latency("health_checks", "trading_health_check", start_time.elapsed()); println!("All health checks passed"); Ok(()) }).await } /// Test TLI to ML Training Service integration async fn test_tli_to_ml_training_integration(&mut self) -> Result { self.harness.execute_scenario("tli_ml_training_integration", |harness| async move { let start_time = std::time::Instant::now(); // Start training via TLI let training_request = StartMLTrainingRequest { model_name: "test_dqn_aapl".to_string(), dataset_id: "synthetic_aapl_dataset".to_string(), hyperparameters: vec![ ("learning_rate".to_string(), "0.001".to_string()), ("batch_size".to_string(), "32".to_string()), ("epochs".to_string(), "10".to_string()), ].into_iter().collect(), auto_deploy: false, }; let response = harness.grpc_clients.tli_client .start_ml_training(training_request).await?; harness.performance.record_latency("tli_ml_training_integration", "start_training_command", start_time.elapsed()); assert!(response.success, "Training should start successfully"); assert!(!response.job_id.is_empty(), "Job ID should be provided"); // Monitor training progress let job_id = response.job_id.clone(); let mut progress_checks = 0; const MAX_PROGRESS_CHECKS: u32 = 30; while progress_checks < MAX_PROGRESS_CHECKS { let start_time = std::time::Instant::now(); let status = harness.grpc_clients.tli_client .get_ml_training_status(job_id.clone()).await?; harness.performance.record_latency("tli_ml_training_integration", "status_check", start_time.elapsed()); println!("Training progress: {}% (epoch {}/{})", status.progress_percentage, status.current_epoch, status.total_epochs); if status.status == "COMPLETED" || status.status == "FAILED" { break; } progress_checks += 1; sleep(Duration::from_secs(2)).await; } // Stop training (test the stop functionality) let start_time = std::time::Instant::now(); let stop_response = harness.grpc_clients.tli_client .stop_ml_training(job_id).await?; harness.performance.record_latency("tli_ml_training_integration", "stop_training_command", start_time.elapsed()); assert!(stop_response.success, "Training should stop successfully"); Ok(()) }).await } /// Test ML Training Service to Trading Service integration async fn test_ml_training_to_trading_integration(&mut self) -> Result { self.harness.execute_scenario("ml_training_trading_integration", |harness| async move { // First, simulate a completed training job with model artifact let model_artifact = harness.test_data.test_data.create_model_artifact("DQN", "AAPL").await?; let start_time = std::time::Instant::now(); // Deploy model to trading service let deploy_request = DeployModelRequest { model_id: model_artifact.model_id.clone(), model_path: model_artifact.model_path.clone(), target_symbols: vec!["AAPL".to_string()], }; let deploy_response = harness.grpc_clients.trading_client .deploy_model(deploy_request).await?; harness.performance.record_latency("ml_training_trading_integration", "model_deployment", start_time.elapsed()); assert!(deploy_response.success, "Model deployment should succeed"); assert_eq!(deploy_response.model_id, model_artifact.model_id); // Test model inference let start_time = std::time::Instant::now(); let prediction_request = PredictionRequest { model_id: model_artifact.model_id.clone(), symbol: "AAPL".to_string(), features: vec![1.0, 2.0, 3.0, 4.0, 5.0], // Mock features }; let prediction_response = harness.grpc_clients.trading_client .get_model_predictions(prediction_request).await?; harness.performance.record_latency("ml_training_trading_integration", "model_inference", start_time.elapsed()); assert_eq!(prediction_response.symbol, "AAPL"); assert!(prediction_response.confidence > 0.0 && prediction_response.confidence <= 1.0); assert!(!prediction_response.prediction.is_empty()); println!("Model inference result: {} with confidence {}", prediction_response.prediction, prediction_response.confidence); Ok(()) }).await } /// Test complete training pipeline: Data → Training → Validation → Storage async fn test_complete_training_pipeline(&mut self) -> Result { self.harness.execute_scenario("complete_training_pipeline", |harness| async move { let start_time = std::time::Instant::now(); // 1. Data Ingestion Phase println!("Phase 1: Data Ingestion"); let market_data = harness.test_data.generate_market_data("AAPL", 1000).await?; assert!(!market_data.is_empty(), "Market data should be generated"); // 2. Feature Engineering Phase println!("Phase 2: Feature Engineering"); let training_dataset = harness.test_data.create_training_dataset("AAPL").await?; assert!(!training_dataset.features.is_empty(), "Features should be extracted"); assert!(!training_dataset.labels.is_empty(), "Labels should be generated"); harness.performance.record_latency("complete_training_pipeline", "data_preparation", start_time.elapsed()); // 3. Model Training Phase println!("Phase 3: Model Training"); let start_time = std::time::Instant::now(); let training_request = StartMLTrainingRequest { model_name: "pipeline_test_model".to_string(), dataset_id: "aapl_pipeline_dataset".to_string(), hyperparameters: vec![ ("learning_rate".to_string(), "0.001".to_string()), ("batch_size".to_string(), "64".to_string()), ("epochs".to_string(), "20".to_string()), ].into_iter().collect(), auto_deploy: true, // Enable auto-deployment for full pipeline test }; let training_response = harness.grpc_clients.tli_client .start_ml_training(training_request).await?; assert!(training_response.success, "Training should start successfully"); // 4. Training Monitoring Phase println!("Phase 4: Training Monitoring"); let job_id = training_response.job_id; let training_completion = timeout(Duration::from_secs(300), async { loop { let status = harness.grpc_clients.tli_client .get_ml_training_status(job_id.clone()).await?; println!("Training status: {} ({}%)", status.status, status.progress_percentage); if status.status == "COMPLETED" { return Ok::<(), anyhow::Error>(()); } else if status.status == "FAILED" { return Err(anyhow::anyhow!("Training failed")); } sleep(Duration::from_secs(5)).await; } }).await; match training_completion { Ok(_) => { harness.performance.record_latency("complete_training_pipeline", "model_training", start_time.elapsed()); println!("Training completed successfully"); }, Err(_) => { println!("Training timeout - this is acceptable for integration testing"); // Stop the training job harness.grpc_clients.tli_client.stop_ml_training(job_id).await?; } } // 5. Model Validation Phase println!("Phase 5: Model Validation"); let model_artifact = harness.test_data.create_model_artifact("DQN", "AAPL").await?; // Validate model performance metrics assert!(model_artifact.metadata.validation_accuracy > 0.5, "Model should have reasonable validation accuracy"); // 6. Model Storage Phase println!("Phase 6: Model Storage"); harness.test_data.save_model_artifact(&model_artifact).await?; println!("Complete training pipeline test passed"); Ok(()) }).await } /// Test model deployment workflow: Retrieval → Deployment → Activation async fn test_model_deployment_workflow(&mut self) -> Result { self.harness.execute_scenario("model_deployment_workflow", |harness| async move { // Create a test model artifact let model_artifact = harness.test_data.create_model_artifact("MAMBA", "TSLA").await?; harness.test_data.save_model_artifact(&model_artifact).await?; let start_time = std::time::Instant::now(); // Phase 1: Model Retrieval println!("Phase 1: Model Retrieval"); // In a real implementation, this would retrieve from model registry assert!(tokio::fs::metadata(&model_artifact.model_path).await.is_ok(), "Model artifact should exist"); // Phase 2: Model Deployment println!("Phase 2: Model Deployment"); let deploy_request = DeployModelRequest { model_id: model_artifact.model_id.clone(), model_path: model_artifact.model_path.clone(), target_symbols: vec!["TSLA".to_string(), "AAPL".to_string()], }; let deploy_response = harness.grpc_clients.trading_client .deploy_model(deploy_request).await?; harness.performance.record_latency("model_deployment_workflow", "model_deployment", start_time.elapsed()); assert!(deploy_response.success, "Model deployment should succeed"); assert!(!deploy_response.deployment_id.is_empty(), "Deployment ID should be provided"); // Phase 3: Model Activation println!("Phase 3: Model Activation"); let start_time = std::time::Instant::now(); // Test that the model is active and can make predictions for symbol in &["TSLA", "AAPL"] { let prediction_request = PredictionRequest { model_id: model_artifact.model_id.clone(), symbol: symbol.to_string(), features: vec![1.5, 2.8, 3.2, 4.1, 5.0], // Mock features }; let prediction_response = harness.grpc_clients.trading_client .get_model_predictions(prediction_request).await?; assert_eq!(prediction_response.symbol, *symbol); assert!(prediction_response.confidence > 0.0); println!("Model prediction for {}: {} (confidence: {:.3})", symbol, prediction_response.prediction, prediction_response.confidence); } harness.performance.record_latency("model_deployment_workflow", "model_activation", start_time.elapsed()); println!("Model deployment workflow completed successfully"); Ok(()) }).await } /// Test live trading workflow: Signal → Risk → Execution → Reporting async fn test_live_trading_with_ml_workflow(&mut self) -> Result { self.harness.execute_scenario("live_trading_ml_workflow", |harness| async move { // Setup: Deploy a model for trading let model_artifact = harness.test_data.create_model_artifact("TFT", "SPY").await?; let deploy_request = DeployModelRequest { model_id: model_artifact.model_id.clone(), model_path: model_artifact.model_path.clone(), target_symbols: vec!["SPY".to_string()], }; harness.grpc_clients.trading_client.deploy_model(deploy_request).await?; let start_time = std::time::Instant::now(); // Phase 1: Signal Generation println!("Phase 1: ML Signal Generation"); let prediction_request = PredictionRequest { model_id: model_artifact.model_id.clone(), symbol: "SPY".to_string(), features: vec![450.5, 451.2, 449.8, 452.1, 450.9], // Mock SPY price features }; let prediction_response = harness.grpc_clients.trading_client .get_model_predictions(prediction_request).await?; harness.performance.record_latency("live_trading_ml_workflow", "signal_generation", start_time.elapsed()); assert!(!prediction_response.prediction.is_empty(), "Model should generate a signal"); assert!(prediction_response.confidence > 0.0, "Signal should have confidence"); // Phase 2: Risk Management Check println!("Phase 2: Risk Management Validation"); let start_time = std::time::Instant::now(); // This would integrate with the risk management service // For now, simulate risk checks let risk_approved = prediction_response.confidence > 0.7 && prediction_response.signal_strength.abs() > 0.5; harness.performance.record_latency("live_trading_ml_workflow", "risk_validation", start_time.elapsed()); if !risk_approved { println!("Trade rejected by risk management"); return Ok(()); } // Phase 3: Order Execution println!("Phase 3: Order Execution"); let start_time = std::time::Instant::now(); // Simulate order execution based on ML signal let order_side = match prediction_response.prediction.as_str() { "BUY" | "STRONG_BUY" => "BUY", "SELL" | "STRONG_SELL" => "SELL", _ => "HOLD", }; if order_side != "HOLD" { println!("Executing {} order for SPY based on ML signal", order_side); // In practice, this would call the actual trading service sleep(Duration::from_millis(10)).await; // Simulate execution latency } harness.performance.record_latency("live_trading_ml_workflow", "order_execution", start_time.elapsed()); // Phase 4: Trade Reporting println!("Phase 4: Trade Reporting"); let start_time = std::time::Instant::now(); // Report trade execution and model performance println!("Trade executed: {} SPY @ market price, signal confidence: {:.3}", order_side, prediction_response.confidence); harness.performance.record_latency("live_trading_ml_workflow", "trade_reporting", start_time.elapsed()); println!("Live trading with ML workflow completed successfully"); Ok(()) }).await } /// Test model update and rollback workflow async fn test_model_update_and_rollback_workflow(&mut self) -> Result { self.harness.execute_scenario("model_update_rollback_workflow", |harness| async move { // Setup: Deploy initial model version let initial_model = harness.test_data.create_model_artifact("ENSEMBLE", "QQQ").await?; let deploy_request = DeployModelRequest { model_id: initial_model.model_id.clone(), model_path: initial_model.model_path.clone(), target_symbols: vec!["QQQ".to_string()], }; harness.grpc_clients.trading_client.deploy_model(deploy_request).await?; // Phase 1: Model Update println!("Phase 1: Model Update"); let start_time = std::time::Instant::now(); // Create new model version let mut updated_model = initial_model.clone(); updated_model.version = "2.0.0".to_string(); updated_model.model_id = format!("ensemble_qqq_v2.0"); updated_model.model_path = format!("/tmp/test_models/ensemble_qqq_v2.pkl"); let update_request = UpdateModelRequest { model_id: initial_model.model_id.clone(), new_model_path: updated_model.model_path.clone(), }; let update_response = harness.grpc_clients.trading_client .update_model(update_request).await?; harness.performance.record_latency("model_update_rollback_workflow", "model_update", start_time.elapsed()); assert!(update_response.success, "Model update should succeed"); assert_eq!(update_response.previous_version, "v1.0.0"); assert_eq!(update_response.new_version, "v1.1.0"); // Phase 2: Validation of Updated Model println!("Phase 2: Updated Model Validation"); let start_time = std::time::Instant::now(); let prediction_request = PredictionRequest { model_id: update_response.model_id.clone(), symbol: "QQQ".to_string(), features: vec![350.0, 351.5, 349.2, 352.8, 350.5], }; let prediction_response = harness.grpc_clients.trading_client .get_model_predictions(prediction_request).await?; harness.performance.record_latency("model_update_rollback_workflow", "updated_model_validation", start_time.elapsed()); assert!(!prediction_response.prediction.is_empty(), "Updated model should work"); // Phase 3: Rollback Scenario (simulate performance degradation) println!("Phase 3: Model Rollback"); let start_time = std::time::Instant::now(); // Simulate detecting performance issues with new model let performance_degraded = prediction_response.confidence < 0.5; // Simulate poor performance if performance_degraded { println!("Performance degradation detected, rolling back to previous version"); let rollback_request = UpdateModelRequest { model_id: update_response.model_id.clone(), new_model_path: initial_model.model_path.clone(), }; let rollback_response = harness.grpc_clients.trading_client .update_model(rollback_request).await?; assert!(rollback_response.success, "Model rollback should succeed"); harness.performance.record_latency("model_update_rollback_workflow", "model_rollback", start_time.elapsed()); println!("Successfully rolled back to version {}", rollback_response.new_version); } else { println!("New model performing well, no rollback needed"); } println!("Model update and rollback workflow completed successfully"); Ok(()) }).await } /// Test training performance requirements async fn test_training_performance_requirements(&mut self) -> Result { self.harness.execute_scenario("training_performance_requirements", |harness| async move { println!("Testing ML training performance requirements"); // Test concurrent training jobs let mut training_jobs = Vec::new(); let start_time = std::time::Instant::now(); for i in 0..3 { let training_request = StartMLTrainingRequest { model_name: format!("perf_test_model_{}", i), dataset_id: "performance_test_dataset".to_string(), hyperparameters: vec![ ("learning_rate".to_string(), "0.001".to_string()), ("batch_size".to_string(), "32".to_string()), ("epochs".to_string(), "5".to_string()), // Short training for performance test ].into_iter().collect(), auto_deploy: false, }; let response = harness.grpc_clients.tli_client .start_ml_training(training_request).await?; assert!(response.success, "Training job {} should start", i); training_jobs.push(response.job_id); } harness.performance.record_latency("training_performance_requirements", "concurrent_job_startup", start_time.elapsed()); // Monitor resource utilization during training let start_time = std::time::Instant::now(); for _ in 0..10 { // Simulate resource monitoring harness.performance.record_resource_usage( "training_performance_requirements", 75.0, // CPU % 8192.0, // Memory MB Some(85.0), // GPU % ); sleep(Duration::from_millis(500)).await; } harness.performance.record_latency("training_performance_requirements", "resource_monitoring", start_time.elapsed()); // Test training throughput let start_time = std::time::Instant::now(); let samples_processed = 10000; // Simulate processed samples let processing_duration = Duration::from_secs(30); harness.performance.record_throughput( "training_performance_requirements", "samples_per_second", samples_processed, processing_duration, ); // Stop all training jobs for job_id in training_jobs { harness.grpc_clients.tli_client.stop_ml_training(job_id).await?; } // Check performance regression let regression_result = harness.performance.check_regression("training_performance_requirements"); match regression_result { RegressionResult::NoRegression => println!("No performance regression detected"), RegressionResult::LatencyRegression { operation, increase_percent } => { println!("WARNING: Latency regression in {}: {:.2}% increase", operation, increase_percent); }, RegressionResult::ThroughputRegression { operation, decrease_percent } => { println!("WARNING: Throughput regression in {}: {:.2}% decrease", operation, decrease_percent); }, RegressionResult::NoBaseline => println!("No baseline available for comparison"), _ => {}, } println!("Training performance requirements test completed"); Ok(()) }).await } /// Test inference latency requirements async fn test_inference_latency_requirements(&mut self) -> Result { self.harness.execute_scenario("inference_latency_requirements", |harness| async move { println!("Testing ML inference latency requirements"); // Deploy a model for latency testing let model_artifact = harness.test_data.create_model_artifact("LIQUID", "NVDA").await?; let deploy_request = DeployModelRequest { model_id: model_artifact.model_id.clone(), model_path: model_artifact.model_path.clone(), target_symbols: vec!["NVDA".to_string()], }; harness.grpc_clients.trading_client.deploy_model(deploy_request).await?; // Test inference latency under load const INFERENCE_COUNT: usize = 1000; let mut latencies = Vec::with_capacity(INFERENCE_COUNT); println!("Running {} inference requests to measure latency", INFERENCE_COUNT); for i in 0..INFERENCE_COUNT { let start_time = std::time::Instant::now(); let prediction_request = PredictionRequest { model_id: model_artifact.model_id.clone(), symbol: "NVDA".to_string(), features: vec![400.0 + i as f64, 401.0, 399.5, 402.1, 400.8], }; let prediction_response = harness.grpc_clients.trading_client .get_model_predictions(prediction_request).await?; let latency = start_time.elapsed(); latencies.push(latency); harness.performance.record_latency("inference_latency_requirements", "single_inference", latency); assert!(!prediction_response.prediction.is_empty(), "Inference should return prediction"); // Add small delay to avoid overwhelming the system if i % 100 == 0 { sleep(Duration::from_millis(10)).await; } } // Analyze latency statistics latencies.sort(); let p50 = latencies[INFERENCE_COUNT / 2]; let p95 = latencies[(INFERENCE_COUNT * 95) / 100]; let p99 = latencies[(INFERENCE_COUNT * 99) / 100]; println!("Inference latency statistics:"); println!(" P50: {:?}", p50); println!(" P95: {:?}", p95); println!(" P99: {:?}", p99); // Verify HFT latency requirements const MAX_P95_LATENCY_US: u64 = 1000; // 1ms for P95 const MAX_P99_LATENCY_US: u64 = 5000; // 5ms for P99 assert!(p95.as_micros() <= MAX_P95_LATENCY_US as u128, "P95 latency should be under {}μs, got {}μs", MAX_P95_LATENCY_US, p95.as_micros()); assert!(p99.as_micros() <= MAX_P99_LATENCY_US as u128, "P99 latency should be under {}μs, got {}μs", MAX_P99_LATENCY_US, p99.as_micros()); println!("Inference latency requirements test passed"); Ok(()) }).await } /// Test training failure recovery async fn test_training_failure_recovery(&mut self) -> Result { self.harness.execute_scenario("training_failure_recovery", |harness| async move { println!("Testing training failure recovery scenarios"); // Scenario 1: Invalid hyperparameters let invalid_request = StartMLTrainingRequest { model_name: "failure_test_model".to_string(), dataset_id: "invalid_dataset".to_string(), hyperparameters: vec![ ("learning_rate".to_string(), "invalid_value".to_string()), // Invalid value ("batch_size".to_string(), "-1".to_string()), // Invalid value ].into_iter().collect(), auto_deploy: false, }; let response = harness.grpc_clients.tli_client .start_ml_training(invalid_request).await; // Should either fail immediately or handle gracefully match response { Ok(resp) => { if resp.success { // If it started, it should fail quickly sleep(Duration::from_secs(5)).await; let status = harness.grpc_clients.tli_client .get_ml_training_status(resp.job_id.clone()).await?; assert_eq!(status.status, "FAILED", "Job with invalid params should fail"); } }, Err(_) => { println!("Training correctly rejected invalid parameters"); } } // Scenario 2: Resource exhaustion simulation println!("Testing resource exhaustion recovery"); let resource_test_request = StartMLTrainingRequest { model_name: "resource_test_model".to_string(), dataset_id: "large_dataset".to_string(), hyperparameters: vec![ ("learning_rate".to_string(), "0.001".to_string()), ("batch_size".to_string(), "1000000".to_string()), // Very large batch ].into_iter().collect(), auto_deploy: false, }; let response = harness.grpc_clients.tli_client .start_ml_training(resource_test_request).await?; if response.success { // Monitor for resource-related failure let mut checks = 0; while checks < 10 { let status = harness.grpc_clients.tli_client .get_ml_training_status(response.job_id.clone()).await?; if status.status == "FAILED" { println!("Training failed due to resource constraints (expected)"); break; } checks += 1; sleep(Duration::from_secs(2)).await; } // Clean up harness.grpc_clients.tli_client.stop_ml_training(response.job_id).await.ok(); } println!("Training failure recovery test completed"); Ok(()) }).await } /// Test deployment failure recovery async fn test_deployment_failure_recovery(&mut self) -> Result { self.harness.execute_scenario("deployment_failure_recovery", |harness| async move { println!("Testing deployment failure recovery scenarios"); // Scenario 1: Invalid model path let invalid_deploy_request = DeployModelRequest { model_id: "nonexistent_model".to_string(), model_path: "/invalid/path/to/model.pkl".to_string(), target_symbols: vec!["TEST".to_string()], }; let response = harness.grpc_clients.trading_client .deploy_model(invalid_deploy_request).await; match response { Ok(resp) => { assert!(!resp.success, "Deployment with invalid path should fail"); println!("Deployment correctly rejected invalid model path"); }, Err(_) => { println!("Deployment correctly failed with invalid model path"); } } // Scenario 2: Model corruption simulation let corrupt_model = harness.test_data.create_model_artifact("CORRUPT", "TEST").await?; // Create a corrupt model file tokio::fs::write(&corrupt_model.model_path, b"invalid model data").await?; let corrupt_deploy_request = DeployModelRequest { model_id: corrupt_model.model_id.clone(), model_path: corrupt_model.model_path.clone(), target_symbols: vec!["TEST".to_string()], }; let response = harness.grpc_clients.trading_client .deploy_model(corrupt_deploy_request).await; match response { Ok(resp) => { if resp.success { // If deployment succeeded, inference should fail let prediction_request = PredictionRequest { model_id: corrupt_model.model_id.clone(), symbol: "TEST".to_string(), features: vec![1.0, 2.0, 3.0], }; let inference_result = harness.grpc_clients.trading_client .get_model_predictions(prediction_request).await; assert!(inference_result.is_err(), "Inference with corrupt model should fail"); } }, Err(_) => { println!("Deployment correctly rejected corrupt model"); } } println!("Deployment failure recovery test completed"); Ok(()) }).await } /// Test service restart recovery async fn test_service_restart_recovery(&mut self) -> Result { self.harness.execute_scenario("service_restart_recovery", |harness| async move { println!("Testing service restart recovery scenarios"); // Start a training job let training_request = StartMLTrainingRequest { model_name: "restart_test_model".to_string(), dataset_id: "restart_test_dataset".to_string(), hyperparameters: vec![ ("learning_rate".to_string(), "0.001".to_string()), ("batch_size".to_string(), "32".to_string()), ("epochs".to_string(), "50".to_string()), // Long training ].into_iter().collect(), auto_deploy: false, }; let response = harness.grpc_clients.tli_client .start_ml_training(training_request).await?; assert!(response.success, "Training should start successfully"); let job_id = response.job_id; // Wait for training to get started sleep(Duration::from_secs(5)).await; // Simulate service restart by checking if training state is recoverable println!("Simulating service restart scenario"); // In a real test, we would restart the ML training service here // For now, we'll test that the job status is still queryable let status_after_restart = harness.grpc_clients.tli_client .get_ml_training_status(job_id.clone()).await; match status_after_restart { Ok(status) => { println!("Training status after simulated restart: {}", status.status); // The service should either: // 1. Continue the training from checkpoint // 2. Mark the job as failed and allow restart // 3. Provide clear status about recovery assert!(!status.status.is_empty(), "Status should be available after restart"); }, Err(_) => { println!("Training status unavailable after restart - this may be expected"); } } // Test recovery by stopping and potentially restarting let stop_response = harness.grpc_clients.tli_client .stop_ml_training(job_id).await?; assert!(stop_response.success, "Should be able to stop training after restart"); // Test starting a new training job after restart let recovery_request = StartMLTrainingRequest { model_name: "recovery_test_model".to_string(), dataset_id: "recovery_test_dataset".to_string(), hyperparameters: vec![ ("learning_rate".to_string(), "0.001".to_string()), ].into_iter().collect(), auto_deploy: false, }; let recovery_response = harness.grpc_clients.tli_client .start_ml_training(recovery_request).await?; assert!(recovery_response.success, "Should be able to start new training after restart"); // Clean up harness.grpc_clients.tli_client.stop_ml_training(recovery_response.job_id).await.ok(); println!("Service restart recovery test completed"); Ok(()) }).await } } // Module-level test runner function #[tokio::test] async fn run_comprehensive_ml_training_workflow_tests() -> Result<()> { let mut test_suite = MLTrainingWorkflowTests::new().await?; let results = test_suite.run_all_tests().await?; // Print test summary let total_tests = results.len(); let passed_tests = results.iter().filter(|r| r.is_success()).count(); let failed_tests = total_tests - passed_tests; println!("\n=== TEST SUMMARY ==="); println!("Total tests: {}", total_tests); println!("Passed: {}", passed_tests); println!("Failed: {}", failed_tests); // Print detailed results for (i, result) in results.into_iter().enumerate() { match result { TestResult::Success { duration, metrics } => { println!("✅ Test {}: PASSED ({:?})", i + 1, duration); }, TestResult::Failure { duration, error, metrics } => { println!("❌ Test {}: FAILED ({:?}) - {}", i + 1, duration, error); }, } } assert_eq!(failed_tests, 0, "All tests should pass"); println!("\n🎉 All comprehensive ML training workflow tests passed!"); Ok(()) }