Files
foxhunt/testing/harness/grpc_clients.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

274 lines
8.5 KiB
Rust

//! 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<Channel>,
pub ml_service_client: MlTrainingServiceClient<Channel>, // 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<Self> {
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<Channel> {
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<bool> {
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<Self> {
// 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<StartMLTrainingResponse> {
// 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<MLTrainingStatusResponse> {
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<StopMLTrainingResponse> {
Ok(StopMLTrainingResponse {
success: true,
job_id,
message: "Training stopped successfully".to_string(),
})
}
}
/// Trading Service client wrapper
#[derive(Clone)]
pub struct TradingServiceClient {
inner: ProtoTradingServiceClient<Channel>,
endpoint: String,
}
impl TradingServiceClient {
pub fn new(channel: Channel) -> Result<Self> {
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<DeployModelResponse> {
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<PredictionResponse> {
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<UpdateModelResponse> {
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<String, String>,
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<String>,
}
#[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<f64>,
}
#[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,
}