Files
foxhunt/testing/integration/smoke_tests/service_health.rs
jgrusewski d25c82f8f3 refactor: rename api_gateway → api across workspace, tests, and load crate
- Workspace Cargo.toml: remove web-gateway + api_gateway members, keep api
- trading_service: dep api-gateway → api, update test imports
- testing/api-gateway-load → testing/api-load (crate renamed)
- All test crates: get_api_gateway_addr → get_api_addr + variable renames

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:38:21 +01:00

266 lines
8.7 KiB
Rust

//! Service Health Smoke Tests
//!
//! Tests to validate that gRPC services are running and responsive.
//! Uses gRPC health check protocol to verify service availability.
use super::common::*;
#[tokio::test]
async fn test_trading_service_health() {
let result = with_timeout(async {
let url = trading_service_url();
println!("Testing Trading Service at {}", url);
// Try HTTP health endpoint first (faster)
let client = reqwest::Client::new();
let http_health_url = url.replace("50052", "8080").replace("http://", "http://");
match client.get(format!("{}/health", http_health_url)).send().await {
Ok(response) if response.status().is_success() => {
println!("✅ Trading Service HTTP health check passed");
return Ok(());
}
_ => {
println!(" HTTP health check not available, trying gRPC...");
}
}
// Fallback to gRPC connection test
// Note: We can't use tonic_health without the proto definitions,
// so we'll just test if we can connect
match tonic::transport::Channel::from_shared(url.clone()) {
Ok(endpoint) => {
match endpoint.connect().await {
Ok(_channel) => {
println!("✅ Trading Service gRPC connection successful");
Ok(())
}
Err(e) => Err(Box::new(e) as Box<dyn std::error::Error>),
}
}
Err(e) => Err(Box::new(e) as Box<dyn std::error::Error>),
}
}).await;
skip_if_unavailable!("Trading Service", { result });
}
#[tokio::test]
async fn test_api_health() {
let result = with_timeout(async {
let url = api_url();
println!("Testing API Gateway at {}", url);
// Try gRPC connection
match tonic::transport::Channel::from_shared(url.clone()) {
Ok(endpoint) => {
match endpoint.connect().await {
Ok(_channel) => {
println!("✅ API Gateway gRPC connection successful");
Ok(())
}
Err(e) => Err(Box::new(e) as Box<dyn std::error::Error>),
}
}
Err(e) => Err(Box::new(e) as Box<dyn std::error::Error>),
}
}).await;
skip_if_unavailable!("API", { result });
}
#[tokio::test]
#[ignore = "Backtesting Service may not be available due to configuration issues"]
async fn test_backtesting_service_health() {
let result = with_timeout(async {
let url = backtesting_service_url();
println!("Testing Backtesting Service at {}", url);
match tonic::transport::Channel::from_shared(url.clone()) {
Ok(endpoint) => {
match endpoint.connect().await {
Ok(_channel) => {
println!("✅ Backtesting Service gRPC connection successful");
Ok(())
}
Err(e) => Err(Box::new(e) as Box<dyn std::error::Error>),
}
}
Err(e) => Err(Box::new(e) as Box<dyn std::error::Error>),
}
}).await;
skip_if_unavailable!("Backtesting Service", { result });
}
#[tokio::test]
#[ignore = "ML Training Service may not be available due to configuration issues"]
async fn test_ml_training_service_health() {
let result = with_timeout(async {
let url = ml_training_service_url();
println!("Testing ML Training Service at {}", url);
match tonic::transport::Channel::from_shared(url.clone()) {
Ok(endpoint) => {
match endpoint.connect().await {
Ok(_channel) => {
println!("✅ ML Training Service gRPC connection successful");
Ok(())
}
Err(e) => Err(Box::new(e) as Box<dyn std::error::Error>),
}
}
Err(e) => Err(Box::new(e) as Box<dyn std::error::Error>),
}
}).await;
skip_if_unavailable!("ML Training Service", { result });
}
#[tokio::test]
async fn test_service_ports_listening() {
println!("🔍 Checking service ports...");
let ports_to_check = vec![
(50051, "API"), // Primary entry point
(50052, "Trading Service"), // Backend service
(50053, "Backtesting Service"),
(50054, "ML Training Service"),
];
let mut listening = 0;
let mut not_listening = 0;
for (port, service) in ports_to_check {
match std::net::TcpStream::connect(format!("localhost:{}", port)) {
Ok(_) => {
println!(" ✓ Port {} ({}) is listening", port, service);
listening += 1;
}
Err(_) => {
println!(" ✗ Port {} ({}) is not listening", port, service);
not_listening += 1;
}
}
}
println!("\n📊 Service Port Summary:");
println!(" Listening: {}", listening);
println!(" Not listening: {}", not_listening);
// Require at least Trading Service to be up
assert!(listening >= 1, "No services are listening");
println!("✅ Service port check complete");
}
#[tokio::test]
async fn test_service_response_times() {
println!("⏱️ Measuring service response times...");
let services = vec![
(trading_service_url(), "Trading Service"),
(api_url(), "API"),
];
for (url, name) in services {
let start = std::time::Instant::now();
match tonic::transport::Channel::from_shared(url.clone()) {
Ok(endpoint) => {
match endpoint.connect().await {
Ok(_channel) => {
let elapsed = start.elapsed();
println!("{} connected in {:?}", name, elapsed);
// Warn if connection takes too long
if elapsed.as_millis() > 1000 {
println!(" ⚠ Connection took longer than expected");
}
}
Err(_) => {
println!("{} not available", name);
}
}
}
Err(_) => {
println!("{} invalid URL", name);
}
}
}
println!("✅ Service response time check complete");
}
#[tokio::test]
async fn test_metrics_endpoints() {
println!("📊 Checking metrics endpoints...");
let client = reqwest::Client::new();
let metrics_ports = vec![
(9091, "API"),
(9092, "Trading Service"),
(9093, "Backtesting Service"),
(9094, "ML Training Service"),
];
let mut available = 0;
let mut unavailable = 0;
for (port, service) in metrics_ports {
let url = format!("http://localhost:{}/metrics", port);
match client.get(&url).send().await {
Ok(response) if response.status().is_success() => {
println!("{} metrics available at port {}", service, port);
available += 1;
// Verify Prometheus format
if let Ok(text) = response.text().await {
assert!(
text.contains("# HELP") || text.contains("# TYPE"),
"{} metrics not in Prometheus format",
service
);
}
}
_ => {
println!("{} metrics not available at port {}", service, port);
unavailable += 1;
}
}
}
println!("\n📊 Metrics Endpoint Summary:");
println!(" Available: {}", available);
println!(" Unavailable: {}", unavailable);
println!("✅ Metrics endpoint check complete");
}
#[tokio::test]
async fn test_service_versions() {
println!("🔖 Checking service versions...");
// Try to get version info from Trading Service health endpoint
let client = reqwest::Client::new();
let health_url = "http://localhost:8080/health";
match client.get(health_url).send().await {
Ok(response) if response.status().is_success() => {
if let Ok(json) = response.json::<serde_json::Value>().await {
if let Some(version) = json.get("version") {
println!(" ✓ Trading Service version: {}", version);
}
if let Some(service) = json.get("service") {
println!(" ✓ Service name: {}", service);
}
}
}
_ => {
println!(" ⚠ Could not retrieve version information");
}
}
println!("✅ Service version check complete");
}