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>
294 lines
8.8 KiB
Rust
294 lines
8.8 KiB
Rust
//! Infrastructure Health Smoke Tests
|
|
//!
|
|
//! Tests to validate that core infrastructure services are available and functional.
|
|
//! These tests run first to ensure the foundation is solid before testing application services.
|
|
|
|
use super::common::*;
|
|
|
|
#[tokio::test]
|
|
async fn test_postgres_connection() {
|
|
let result = with_timeout(async {
|
|
let pool = sqlx::PgPool::connect(&database_url())
|
|
.await
|
|
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
|
|
|
// Verify database is responsive
|
|
let row: (i32,) = sqlx::query_as("SELECT 1")
|
|
.fetch_one(&pool)
|
|
.await
|
|
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
|
|
|
assert_eq!(row.0, 1, "Database query returned unexpected value");
|
|
|
|
// Check TimescaleDB extension
|
|
let has_timescale: (bool,) = sqlx::query_as(
|
|
"SELECT COUNT(*) > 0 FROM pg_extension WHERE extname = 'timescaledb'"
|
|
)
|
|
.fetch_one(&pool)
|
|
.await
|
|
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
|
|
|
pool.close().await;
|
|
|
|
println!("✅ PostgreSQL connection successful (TimescaleDB: {})", has_timescale.0);
|
|
Ok(())
|
|
}).await;
|
|
|
|
skip_if_unavailable!("PostgreSQL", { result });
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_postgres_schema_exists() {
|
|
let result = with_timeout(async {
|
|
let pool = sqlx::PgPool::connect(&database_url())
|
|
.await
|
|
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
|
|
|
// Check for key tables
|
|
let tables = vec!["orders", "positions", "market_data", "audit_log"];
|
|
for table in tables {
|
|
let exists: (bool,) = sqlx::query_as(
|
|
"SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = $1)"
|
|
)
|
|
.bind(table)
|
|
.fetch_one(&pool)
|
|
.await
|
|
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
|
|
|
assert!(exists.0, "Table '{}' does not exist", table);
|
|
println!(" ✓ Table '{}' exists", table);
|
|
}
|
|
|
|
pool.close().await;
|
|
println!("✅ PostgreSQL schema validation successful");
|
|
Ok(())
|
|
}).await;
|
|
|
|
skip_if_unavailable!("PostgreSQL", { result });
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_redis_connection() {
|
|
let result = with_timeout(async {
|
|
use redis::AsyncCommands;
|
|
|
|
let client = redis::Client::open(redis_url().as_str())
|
|
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
|
|
|
let mut con = client.get_multiplexed_async_connection()
|
|
.await
|
|
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
|
|
|
// Test PING
|
|
let pong: String = redis::cmd("PING")
|
|
.query_async(&mut con)
|
|
.await
|
|
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
|
|
|
assert_eq!(pong, "PONG", "Redis PING returned unexpected value");
|
|
|
|
// Test SET/GET
|
|
let test_key = "smoke_test:redis_health";
|
|
let test_value = "healthy";
|
|
|
|
con.set::<_, _, ()>(test_key, test_value)
|
|
.await
|
|
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
|
|
|
let retrieved: String = con.get(test_key)
|
|
.await
|
|
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
|
|
|
assert_eq!(retrieved, test_value, "Redis GET returned unexpected value");
|
|
|
|
// Cleanup
|
|
con.del::<_, ()>(test_key)
|
|
.await
|
|
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
|
|
|
println!("✅ Redis connection and operations successful");
|
|
Ok(())
|
|
}).await;
|
|
|
|
skip_if_unavailable!("Redis", { result });
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_vault_connectivity() {
|
|
let result = with_timeout(async {
|
|
let client = reqwest::Client::new();
|
|
let vault_url = vault_url();
|
|
|
|
// Test Vault health endpoint
|
|
let response = client
|
|
.get(format!("{}/v1/sys/health", vault_url))
|
|
.send()
|
|
.await
|
|
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
|
|
|
// Vault health returns 200 (initialized and unsealed), 429 (unsealed and standby),
|
|
// 472 (data recovery mode), 473 (performance standby), or 501 (not initialized)
|
|
let status = response.status();
|
|
assert!(
|
|
status == 200 || status == 429 || status == 473,
|
|
"Vault health check returned unexpected status: {}",
|
|
status
|
|
);
|
|
|
|
println!("✅ Vault connectivity successful (status: {})", status);
|
|
Ok(())
|
|
}).await;
|
|
|
|
skip_if_unavailable!("Vault", { result });
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_influxdb_connectivity() {
|
|
let result = with_timeout(async {
|
|
let client = reqwest::Client::new();
|
|
let influxdb_url = influxdb_url();
|
|
|
|
// Test InfluxDB ping endpoint
|
|
let response = client
|
|
.get(format!("{}/ping", influxdb_url))
|
|
.send()
|
|
.await
|
|
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
|
|
|
assert!(
|
|
response.status().is_success(),
|
|
"InfluxDB ping failed with status: {}",
|
|
response.status()
|
|
);
|
|
|
|
println!("✅ InfluxDB connectivity successful");
|
|
Ok(())
|
|
}).await;
|
|
|
|
skip_if_unavailable!("InfluxDB", { result });
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_prometheus_connectivity() {
|
|
let result = with_timeout(async {
|
|
let client = reqwest::Client::new();
|
|
let prometheus_url = prometheus_url();
|
|
|
|
// Test Prometheus health endpoint
|
|
let response = client
|
|
.get(format!("{}/-/healthy", prometheus_url))
|
|
.send()
|
|
.await
|
|
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
|
|
|
assert!(
|
|
response.status().is_success(),
|
|
"Prometheus health check failed with status: {}",
|
|
response.status()
|
|
);
|
|
|
|
println!("✅ Prometheus connectivity successful");
|
|
Ok(())
|
|
}).await;
|
|
|
|
skip_if_unavailable!("Prometheus", { result });
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_grafana_connectivity() {
|
|
let result = with_timeout(async {
|
|
let client = reqwest::Client::new();
|
|
let grafana_url = grafana_url();
|
|
|
|
// Test Grafana API health endpoint
|
|
let response = client
|
|
.get(format!("{}/api/health", grafana_url))
|
|
.send()
|
|
.await
|
|
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
|
|
|
assert!(
|
|
response.status().is_success(),
|
|
"Grafana health check failed with status: {}",
|
|
response.status()
|
|
);
|
|
|
|
println!("✅ Grafana connectivity successful");
|
|
Ok(())
|
|
}).await;
|
|
|
|
skip_if_unavailable!("Grafana", { result });
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_infrastructure_all_healthy() {
|
|
println!("🏥 Running comprehensive infrastructure health check...");
|
|
|
|
let mut checks_passed = 0;
|
|
let mut checks_failed = 0;
|
|
|
|
// PostgreSQL
|
|
match sqlx::PgPool::connect(&database_url()).await {
|
|
Ok(pool) => {
|
|
pool.close().await;
|
|
println!(" ✓ PostgreSQL");
|
|
checks_passed += 1;
|
|
}
|
|
Err(e) => {
|
|
eprintln!(" ✗ PostgreSQL: {}", e);
|
|
checks_failed += 1;
|
|
}
|
|
}
|
|
|
|
// Redis
|
|
match redis::Client::open(redis_url().as_str()) {
|
|
Ok(client) => {
|
|
match client.get_multiplexed_async_connection().await {
|
|
Ok(_) => {
|
|
println!(" ✓ Redis");
|
|
checks_passed += 1;
|
|
}
|
|
Err(e) => {
|
|
eprintln!(" ✗ Redis: {}", e);
|
|
checks_failed += 1;
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
eprintln!(" ✗ Redis: {}", e);
|
|
checks_failed += 1;
|
|
}
|
|
}
|
|
|
|
// Vault (optional - don't fail if unavailable)
|
|
let client = reqwest::Client::new();
|
|
match client.get(format!("{}/v1/sys/health", vault_url())).send().await {
|
|
Ok(response) if response.status() == 200 || response.status() == 429 => {
|
|
println!(" ✓ Vault");
|
|
checks_passed += 1;
|
|
}
|
|
_ => {
|
|
println!(" ⚠ Vault (not critical)");
|
|
}
|
|
}
|
|
|
|
// InfluxDB (optional - don't fail if unavailable)
|
|
match client.get(format!("{}/ping", influxdb_url())).send().await {
|
|
Ok(response) if response.status().is_success() => {
|
|
println!(" ✓ InfluxDB");
|
|
checks_passed += 1;
|
|
}
|
|
_ => {
|
|
println!(" ⚠ InfluxDB (not critical)");
|
|
}
|
|
}
|
|
|
|
println!("\n📊 Infrastructure Health Summary:");
|
|
println!(" Passed: {}", checks_passed);
|
|
println!(" Failed: {}", checks_failed);
|
|
|
|
// Require at least PostgreSQL and Redis
|
|
assert!(checks_passed >= 2, "Critical infrastructure services are down");
|
|
println!("✅ Infrastructure health check complete");
|
|
}
|