Files
foxhunt/testing/harness/grpc_clients.rs
jgrusewski 4ba8eebc05 cleanup: declarative rewrites for migrations/services/testing TODOs
migrations:
- 001_trading_events.sql, 003_audit_system.sql: the hard-coded
  node_id literals (`trading-node-01`, `audit-node-01`,
  `ml-node-01`, `system-node-01`, `change-tracker-01`) are
  overridden per-deployment by later migrations rather than read
  from the environment. Describe that in the inline comment.
- 004_compliance_views.sql: `generate_compliance_report` is a log
  stub — actual report generation is performed by the compliance
  service. Say so explicitly.

services:
- ml_training_service/tests/orchestrator_225_features_test.rs: the
  empty `#[ignore]`d placeholder for the 225-feature orchestrator
  loader has been removed; it held no assertions and only tracked
  a TODO (feedback_no_stubs.md).
- trading_agent_service/src/service.rs: portfolio volatility uses
  the diagonal-only approximation because cross-asset return
  correlations are not maintained in this service. Document that.
- trading_service/src/services/risk.rs: `get_risk_metrics` uses
  `calculate_marginal_var` + asset-class fallback; describe why
  `calculate_comprehensive_var` is not wired at this boundary.
- trading_service/tests/auth_comprehensive.rs: delete the entire
  commented-out legacy BackupCodeValidator test block — the old
  `generate_backup_codes` / `store_backup_code` /
  `verify_backup_code` surface no longer exists, and MFA
  integration tests already cover the new API.

testing:
- harness/grpc_clients.rs: no BacktestingServiceClient proto
  exists; reword the stale TODO import line.
- chaos/*: reword the family of "TODO: Implement ..." stubs as
  "Currently a no-op / synthetic result" descriptions so readers
  know exactly how much of the chaos framework is live.
- compliance_automation_tests.rs: delete the file; it was a giant
  /* ... */ block referencing a nonexistent compliance module
  and was not wired into any Cargo target.
- framework.rs: describe why `setup()` uses `println!` instead of
  `tracing_subscriber` (tracing_subscriber is not a dep of this
  integration crate).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:51:26 +02:00

277 lines
8.6 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;
// The harness does not currently expose a Backtesting gRPC client — the
// backtesting crate is driven directly in tests rather than over the
// wire. Re-add an import here once a BacktestingServiceClient proto
// surface exists.
/// 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,
}