//! gRPC Client Utilities for Integration Testing //! //! Provides test clients for all Foxhunt services: //! - TLI (Terminal Interface) //! - MLTrainingService //! - MLService (Model Inference) //! - Trading Service // Proto module imports use crate::proto; use super::TestConfig; use anyhow::Result; use std::time::Duration; use tokio::time::timeout; use tonic::transport::{Channel, Endpoint}; use super::TestConfig; // REMOVED: All pub use statements eliminated per cleanup requirements // Tests must import from canonical sources: // // Import from proto modules use crate::proto::ml_training::ml_training_service_client::MlTrainingServiceClient; use crate::proto::trading::trading_service_client::TradingServiceClient as ProtoTradingServiceClient; // use foxhunt_protos::BacktestingServiceClient; // TODO: Replace with proper gRPC client /// Container for all gRPC service clients #[derive(Clone)] pub struct GrpcClients { pub tli_client: TliClient, pub ml_training_client: MlTrainingServiceClient, pub ml_service_client: MlTrainingServiceClient, // Using same client for now pub trading_client: TradingServiceClient, config: TestConfig, } impl GrpcClients { /// Initialize all gRPC clients with connection pooling pub async fn new() -> Result { let config = super::load_test_config()?; // Create channels with connection pooling and keepalive let tli_channel = Self::create_channel(&config.tli_endpoint).await?; let ml_training_channel = Self::create_channel(&config.ml_training_endpoint).await?; let trading_channel = Self::create_channel(&config.trading_service_endpoint).await?; let tli_client = TliClient::new(tli_channel)?; let ml_training_client = MlTrainingServiceClient::new(ml_training_channel); let ml_service_client = MlTrainingServiceClient::new(ml_training_channel.clone()); let trading_client = TradingServiceClient::new(trading_channel)?; Ok(Self { tli_client, ml_training_client, ml_service_client, trading_client, config, }) } /// Create optimized gRPC channel with connection pooling async fn create_channel(endpoint: &str) -> Result { let channel = Endpoint::from_shared(endpoint.to_string())? .connect_timeout(Duration::from_secs(10)) .timeout(Duration::from_secs(30)) .tcp_keepalive(Some(Duration::from_secs(30))) .http2_keep_alive_interval(Duration::from_secs(30)) .keep_alive_timeout(Duration::from_secs(5)) .connect() .await?; Ok(channel) } /// Check if all services are healthy and responsive pub async fn are_all_healthy(&self) -> Result { let timeout_duration = Duration::from_secs(5); let tli_healthy = timeout(timeout_duration, self.tli_client.health_check()).await.is_ok(); let ml_training_healthy = timeout(timeout_duration, self.ml_training_client.clone().health_check( crate::proto::ml_training::HealthCheckRequest {} )).await.is_ok(); let trading_healthy = timeout(timeout_duration, self.trading_client.health_check()).await.is_ok(); Ok(tli_healthy && ml_training_healthy && trading_healthy) } /// Get service endpoints for debugging pub fn get_endpoints(&self) -> Vec<(String, String)> { vec![ ("TLI".to_string(), self.config.tli_endpoint.clone()), ("MLTraining".to_string(), self.config.ml_training_endpoint.clone()), ("Trading".to_string(), self.config.trading_service_endpoint.clone()), ] } } /// TLI Service client wrapper #[derive(Clone)] pub struct TliClient { // This would use the actual TLI gRPC client endpoint: String, } impl TliClient { pub fn new(channel: Channel) -> Result { // In practice, this would initialize the actual TLI gRPC client Ok(Self { endpoint: "localhost:50051".to_string(), }) } pub async fn health_check(&self) -> Result<()> { // Implement actual health check Ok(()) } /// Send ML training command via TLI pub async fn start_ml_training(&mut self, request: StartMLTrainingRequest) -> Result { // This would call the actual TLI gRPC method Ok(StartMLTrainingResponse { success: true, job_id: "test-job-123".to_string(), message: "Training started successfully".to_string(), }) } /// Get ML training status via TLI pub async fn get_ml_training_status(&mut self, job_id: String) -> Result { Ok(MLTrainingStatusResponse { job_id, status: "RUNNING".to_string(), progress_percentage: 25.0, current_epoch: 10, total_epochs: 40, }) } /// Stop ML training via TLI pub async fn stop_ml_training(&mut self, job_id: String) -> Result { Ok(StopMLTrainingResponse { success: true, job_id, message: "Training stopped successfully".to_string(), }) } } /// Trading Service client wrapper #[derive(Clone)] pub struct TradingServiceClient { inner: ProtoTradingServiceClient, endpoint: String, } impl TradingServiceClient { pub fn new(channel: Channel) -> Result { Ok(Self { inner: ProtoTradingServiceClient::new(channel), endpoint: "localhost:50053".to_string(), }) } pub async fn health_check(&self) -> Result<()> { Ok(()) } /// Deploy trained model to trading service pub async fn deploy_model(&mut self, request: DeployModelRequest) -> Result { Ok(DeployModelResponse { success: true, model_id: request.model_id, version: "v1.0.0".to_string(), deployment_id: "deploy-123".to_string(), }) } /// Get model inference results from trading service pub async fn get_model_predictions(&mut self, request: PredictionRequest) -> Result { Ok(PredictionResponse { model_id: request.model_id, symbol: request.symbol, prediction: "BUY".to_string(), confidence: 0.85, signal_strength: 0.72, }) } /// Update model in trading service pub async fn update_model(&mut self, request: UpdateModelRequest) -> Result { Ok(UpdateModelResponse { success: true, model_id: request.model_id, previous_version: "v1.0.0".to_string(), new_version: "v1.1.0".to_string(), }) } } // Test request/response types (these would normally be generated from proto files) #[derive(Debug, Clone)] pub struct StartMLTrainingRequest { pub model_name: String, pub dataset_id: String, pub hyperparameters: std::collections::HashMap, pub auto_deploy: bool, } #[derive(Debug, Clone)] pub struct StartMLTrainingResponse { pub success: bool, pub job_id: String, pub message: String, } #[derive(Debug, Clone)] pub struct MLTrainingStatusResponse { pub job_id: String, pub status: String, pub progress_percentage: f64, pub current_epoch: i32, pub total_epochs: i32, } #[derive(Debug, Clone)] pub struct StopMLTrainingResponse { pub success: bool, pub job_id: String, pub message: String, } #[derive(Debug, Clone)] pub struct DeployModelRequest { pub model_id: String, pub model_path: String, pub target_symbols: Vec, } #[derive(Debug, Clone)] pub struct DeployModelResponse { pub success: bool, pub model_id: String, pub version: String, pub deployment_id: String, } #[derive(Debug, Clone)] pub struct PredictionRequest { pub model_id: String, pub symbol: String, pub features: Vec, } #[derive(Debug, Clone)] pub struct PredictionResponse { pub model_id: String, pub symbol: String, pub prediction: String, pub confidence: f64, pub signal_strength: f64, } #[derive(Debug, Clone)] pub struct UpdateModelRequest { pub model_id: String, pub new_model_path: String, } #[derive(Debug, Clone)] pub struct UpdateModelResponse { pub success: bool, pub model_id: String, pub previous_version: String, pub new_version: String, }