- 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>
266 lines
8.7 KiB
Rust
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");
|
|
}
|