- Fixed systematic array indexing corruption: [0_i32] → [0] - Fixed numeric literal suffixes across 835 files - Fixed iterator patterns on RwLockReadGuard (.iter() required) - Fixed float type annotations (365.25_f64 for sqrt) - Fixed missing semicolons in position manager - Fixed reference dereferencing in data loader Root cause: Mass refactoring incorrectly added _i32 suffixes to array indices Impact: Complete compilation failure (463 errors) Resolution: Automated regex + targeted fixes Result: 100% compilation success (0 errors) Validated: cargo check --workspace passes Ready for: Production deployment
1027 lines
48 KiB
Rust
1027 lines
48 KiB
Rust
#![allow(unused_crate_dependencies)]
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
use std::time::{Duration, Instant};
|
|
use tokio::time::timeout;
|
|
|
|
use crate::framework::{TestOrchestrator, TestFrameworkConfig, PerformanceThresholds, IntegrationTestResult};
|
|
use crate::framework::mocks::MockServiceRegistry;
|
|
|
|
use config::{ConfigManager, TLIConfig};
|
|
use tli::client::{TLIClient, ConnectionManager, ServiceConnection};
|
|
use tli::ui::{Dashboard, Terminal, CommandInterface};
|
|
use tli::commands::{Command, CommandResult, CommandType};
|
|
|
|
/// TLI Client Integration Tests
|
|
///
|
|
/// Tests the Terminal Line Interface client functionality including:
|
|
/// - gRPC connections to all three services
|
|
///
|
|
/// - Command execution and response handling
|
|
/// - Real-time data streaming and display
|
|
///
|
|
/// - Configuration management interface
|
|
/// - Performance monitoring dashboard
|
|
///
|
|
/// - Error handling and recovery
|
|
pub struct TLIClientTests {
|
|
orchestrator: TestOrchestrator,
|
|
mock_registry: MockServiceRegistry,
|
|
tli_config: TLIConfig,
|
|
}
|
|
|
|
impl TLIClientTests {
|
|
/// Initialize TLI Client test suite
|
|
pub async fn new() -> Result<Self, Box<dyn std::error::Error>> {
|
|
let config = TestFrameworkConfig {
|
|
performance_thresholds: PerformanceThresholds {
|
|
max_e2e_latency_us: 50, // 50μs end-to-end
|
|
max_order_latency_us: 20, // 20μs order processing
|
|
max_risk_latency_us: 10, // 10μs risk validation
|
|
max_ml_latency_ms: 50, // 50ms ML inference
|
|
max_config_reload_ms: 100, // 100ms config reload
|
|
min_throughput_ops_sec: 10000, // 10k ops/sec minimum
|
|
},
|
|
database_url: std::env::var("TEST_DATABASE_URL")
|
|
.unwrap_or_else(|_| "postgresql://test:test@localhost:5432/foxhunt_test".to_string()),
|
|
service_ports: {
|
|
let mut ports = HashMap::new();
|
|
ports.insert("trading".to_string(), 50051);
|
|
ports.insert("backtesting".to_string(), 50052);
|
|
ports.insert("ml_training".to_string(), 50053);
|
|
ports
|
|
},
|
|
test_timeout_secs: 30,
|
|
};
|
|
|
|
let orchestrator = TestOrchestrator::new(config).await?;
|
|
let mock_registry = MockServiceRegistry::new().await?;
|
|
|
|
// Load TLI configuration
|
|
let config_manager = ConfigManager::new().await?;
|
|
let tli_config = config_manager.get_tli_config().await?;
|
|
|
|
Ok(Self {
|
|
orchestrator,
|
|
mock_registry,
|
|
tli_config,
|
|
})
|
|
}
|
|
|
|
/// Test Suite 1: Service Connection Management
|
|
///
|
|
/// Validates TLI's ability to connect and manage connections to services:
|
|
/// - gRPC connection establishment
|
|
///
|
|
/// - Connection health monitoring
|
|
/// - Automatic reconnection on failures
|
|
///
|
|
/// - Service discovery and failover
|
|
pub async fn test_service_connection_management(&self) -> IntegrationTestResult {
|
|
println!("🔗 Testing TLI Client - Service Connection Management");
|
|
|
|
let start_time = Instant::now();
|
|
let mut test_results = IntegrationTestResult::new("TLI Client Service Connection Management");
|
|
|
|
// Start all required services
|
|
for service_name in ["trading", "backtesting", "ml_training"] {
|
|
match self.orchestrator.start_service(service_name).await {
|
|
Ok(_) => {
|
|
test_results.add_success(&format!("Started {} service for TLI testing", service_name));
|
|
}
|
|
Err(e) => {
|
|
test_results.add_failure(&format!("Failed to start {} service: {}", service_name, e));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Allow services to initialize
|
|
tokio::time::sleep(Duration::from_millis(2000)).await;
|
|
|
|
// Test Case 1: TLI Client Initialization
|
|
let tli_client_config = TLIClientConfig {
|
|
service_endpoints: {
|
|
let mut endpoints = HashMap::new();
|
|
endpoints.insert("trading".to_string(), "http://localhost:50051".to_string());
|
|
endpoints.insert("backtesting".to_string(), "http://localhost:50052".to_string());
|
|
endpoints.insert("ml_training".to_string(), "http://localhost:50053".to_string());
|
|
endpoints
|
|
},
|
|
connection_timeout_ms: 5000,
|
|
retry_attempts: 3,
|
|
retry_delay_ms: 1000,
|
|
keepalive_interval_s: 30,
|
|
};
|
|
|
|
let tli_init_start = Instant::now();
|
|
let tli_client_result = TLIClient::new(tli_client_config.clone()).await;
|
|
let tli_init_duration = tli_init_start.elapsed();
|
|
|
|
match tli_client_result {
|
|
Ok(mut tli_client) => {
|
|
test_results.add_success("TLI client initialized successfully");
|
|
|
|
if tli_init_duration.as_millis() <= 5000 {
|
|
test_results.add_success(&format!(
|
|
"TLI initialization time acceptable: {}ms",
|
|
tli_init_duration.as_millis()
|
|
));
|
|
} else {
|
|
test_results.add_failure(&format!(
|
|
"TLI initialization time too long: {}ms",
|
|
tli_init_duration.as_millis()
|
|
));
|
|
}
|
|
|
|
// Test Case 2: Service Connection Tests
|
|
for service_name in ["trading", "backtesting", "ml_training"] {
|
|
let connection_start = Instant::now();
|
|
match tli_client.connect_to_service(service_name).await {
|
|
Ok(_) => {
|
|
let connection_duration = connection_start.elapsed();
|
|
test_results.add_success(&format!("Connected to {} service", service_name));
|
|
|
|
if connection_duration.as_millis() <= 2000 {
|
|
test_results.add_success(&format!(
|
|
"{} connection time acceptable: {}ms",
|
|
service_name, connection_duration.as_millis()
|
|
));
|
|
} else {
|
|
test_results.add_failure(&format!(
|
|
"{} connection time too long: {}ms",
|
|
service_name, connection_duration.as_millis()
|
|
));
|
|
}
|
|
|
|
// Test health check for each service
|
|
match tli_client.check_service_health(service_name).await {
|
|
Ok(health_status) => {
|
|
if health_status.is_healthy {
|
|
test_results.add_success(&format!("{} service health check passed", service_name));
|
|
} else {
|
|
test_results.add_failure(&format!("{} service reports unhealthy: {}",
|
|
service_name, health_status.status_message));
|
|
}
|
|
}
|
|
Err(e) => {
|
|
test_results.add_failure(&format!("Failed {} service health check: {}", service_name, e));
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
test_results.add_failure(&format!("Failed to connect to {} service: {}", service_name, e));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test Case 3: Connection Resilience
|
|
// Simulate a service disconnection and reconnection
|
|
println!("Testing connection resilience by restarting trading service...");
|
|
|
|
// Stop trading service temporarily
|
|
match self.orchestrator.stop_service("trading").await {
|
|
Ok(_) => {
|
|
test_results.add_success("Trading service stopped for resilience test");
|
|
|
|
// Wait a moment for connection to detect failure
|
|
tokio::time::sleep(Duration::from_millis(1000)).await;
|
|
|
|
// Check if TLI detects the disconnection
|
|
match tli_client.check_service_health("trading").await {
|
|
Ok(health_status) => {
|
|
if !health_status.is_healthy {
|
|
test_results.add_success("TLI correctly detected service disconnection");
|
|
} else {
|
|
test_results.add_failure("TLI failed to detect service disconnection");
|
|
}
|
|
}
|
|
Err(_) => {
|
|
test_results.add_success("TLI correctly detected service unavailability");
|
|
}
|
|
}
|
|
|
|
// Restart trading service
|
|
match self.orchestrator.start_service("trading").await {
|
|
Ok(_) => {
|
|
test_results.add_success("Trading service restarted");
|
|
|
|
// Allow time for reconnection
|
|
tokio::time::sleep(Duration::from_millis(2000)).await;
|
|
|
|
// Test automatic reconnection
|
|
let reconnect_start = Instant::now();
|
|
match tli_client.reconnect_to_service("trading").await {
|
|
Ok(_) => {
|
|
let reconnect_duration = reconnect_start.elapsed();
|
|
test_results.add_success("TLI reconnected to trading service");
|
|
|
|
if reconnect_duration.as_millis() <= 3000 {
|
|
test_results.add_success(&format!(
|
|
"Reconnection time acceptable: {}ms",
|
|
reconnect_duration.as_millis()
|
|
));
|
|
} else {
|
|
test_results.add_failure(&format!(
|
|
"Reconnection time too long: {}ms",
|
|
reconnect_duration.as_millis()
|
|
));
|
|
}
|
|
|
|
// Verify reconnection works
|
|
match tli_client.check_service_health("trading").await {
|
|
Ok(health_status) => {
|
|
if health_status.is_healthy {
|
|
test_results.add_success("Trading service health confirmed after reconnection");
|
|
} else {
|
|
test_results.add_failure("Trading service still unhealthy after reconnection");
|
|
}
|
|
}
|
|
Err(e) => {
|
|
test_results.add_failure(&format!("Failed to verify reconnection: {}", e));
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
test_results.add_failure(&format!("Failed to reconnect to trading service: {}", e));
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
test_results.add_failure(&format!("Failed to restart trading service: {}", e));
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
test_results.add_failure(&format!("Failed to stop trading service for resilience test: {}", e));
|
|
}
|
|
}
|
|
|
|
// Test Case 4: Concurrent Connection Management
|
|
let concurrent_test_start = Instant::now();
|
|
let mut concurrent_tasks = Vec::new();
|
|
|
|
for i in 0..5 {
|
|
let mut client_clone = tli_client.clone();
|
|
let task = tokio::spawn(async move {
|
|
// Test concurrent health checks
|
|
let health_results = futures::join!(
|
|
client_clone.check_service_health("trading"),
|
|
client_clone.check_service_health("backtesting"),
|
|
client_clone.check_service_health("ml_training")
|
|
);
|
|
|
|
(i, health_results)
|
|
});
|
|
concurrent_tasks.push(task);
|
|
}
|
|
|
|
let mut concurrent_success = 0;
|
|
let mut concurrent_failures = 0;
|
|
|
|
for task in concurrent_tasks {
|
|
match task.await {
|
|
Ok((task_id, (trading_health, backtesting_health, ml_health))) => {
|
|
let mut task_success = true;
|
|
|
|
if trading_health.is_err() || backtesting_health.is_err() || ml_health.is_err() {
|
|
task_success = false;
|
|
}
|
|
|
|
if task_success {
|
|
concurrent_success += 1;
|
|
} else {
|
|
concurrent_failures += 1;
|
|
}
|
|
}
|
|
Err(_) => {
|
|
concurrent_failures += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
let concurrent_duration = concurrent_test_start.elapsed();
|
|
|
|
if concurrent_failures == 0 {
|
|
test_results.add_success(&format!(
|
|
"All {} concurrent connection tests passed in {}ms",
|
|
concurrent_success, concurrent_duration.as_millis()
|
|
));
|
|
} else {
|
|
test_results.add_failure(&format!(
|
|
"Concurrent connection test failures: {} success, {} failures",
|
|
concurrent_success, concurrent_failures
|
|
));
|
|
}
|
|
}
|
|
Err(e) => {
|
|
test_results.add_failure(&format!("Failed to initialize TLI client: {}", e));
|
|
}
|
|
}
|
|
|
|
test_results.duration = start_time.elapsed();
|
|
test_results.finalize();
|
|
|
|
Ok(test_results)
|
|
}
|
|
|
|
/// Test Suite 2: Command Execution Interface
|
|
///
|
|
/// Validates TLI's command processing and execution:
|
|
/// - Command parsing and validation
|
|
///
|
|
/// - Service command routing
|
|
/// - Response formatting and display
|
|
///
|
|
/// - Error handling and user feedback
|
|
pub async fn test_command_execution_interface(&self) -> IntegrationTestResult {
|
|
println!("⌨️ Testing TLI Client - Command Execution Interface");
|
|
|
|
let start_time = Instant::now();
|
|
let mut test_results = IntegrationTestResult::new("TLI Client Command Execution Interface");
|
|
|
|
// Initialize TLI client for command testing
|
|
let tli_client_config = TLIClientConfig {
|
|
service_endpoints: {
|
|
let mut endpoints = HashMap::new();
|
|
endpoints.insert("trading".to_string(), "http://localhost:50051".to_string());
|
|
endpoints.insert("backtesting".to_string(), "http://localhost:50052".to_string());
|
|
endpoints.insert("ml_training".to_string(), "http://localhost:50053".to_string());
|
|
endpoints
|
|
},
|
|
connection_timeout_ms: 5000,
|
|
retry_attempts: 3,
|
|
retry_delay_ms: 1000,
|
|
keepalive_interval_s: 30,
|
|
};
|
|
|
|
match TLIClient::new(tli_client_config).await {
|
|
Ok(mut tli_client) => {
|
|
test_results.add_success("TLI client initialized for command testing");
|
|
|
|
// Ensure connections are established
|
|
tokio::time::sleep(Duration::from_millis(1000)).await;
|
|
|
|
// Test Case 1: Basic Command Parsing
|
|
let test_commands = vec![
|
|
("help", CommandType::Help, "Display help information"),
|
|
("status", CommandType::Status, "Show system status"),
|
|
("list orders", CommandType::ListOrders, "List active orders"),
|
|
("list positions", CommandType::ListPositions, "List open positions"),
|
|
("start backtest --strategy SMA --symbol EURUSD", CommandType::StartBacktest, "Start backtesting"),
|
|
("train model --type MAMBA --symbol EURUSD", CommandType::TrainModel, "Train ML model"),
|
|
("config get trading.max_position_size", CommandType::ConfigGet, "Get configuration value"),
|
|
("config set trading.max_position_size 1000000", CommandType::ConfigSet, "Set configuration value"),
|
|
];
|
|
|
|
for (command_str, expected_type, description) in &test_commands {
|
|
let parse_start = Instant::now();
|
|
match tli_client.parse_command(command_str).await {
|
|
Ok(parsed_command) => {
|
|
let parse_duration = parse_start.elapsed();
|
|
test_results.add_success(&format!("Parsed command: {} - {}", command_str, description));
|
|
|
|
if parsed_command.command_type == *expected_type {
|
|
test_results.add_success(&format!("Command type correctly identified: {:?}", expected_type));
|
|
} else {
|
|
test_results.add_failure(&format!(
|
|
"Command type mismatch for '{}': expected {:?}, got {:?}",
|
|
command_str, expected_type, parsed_command.command_type
|
|
));
|
|
}
|
|
|
|
if parse_duration.as_micros() <= 1000 { // < 1ms for parsing
|
|
test_results.add_success(&format!(
|
|
"Command parsing time acceptable: {}μs",
|
|
parse_duration.as_micros()
|
|
));
|
|
} else {
|
|
test_results.add_failure(&format!(
|
|
"Command parsing time too long: {}μs",
|
|
parse_duration.as_micros()
|
|
));
|
|
}
|
|
}
|
|
Err(e) => {
|
|
test_results.add_failure(&format!("Failed to parse command '{}': {}", command_str, e));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test Case 2: Service Command Execution
|
|
let service_commands = vec![
|
|
(
|
|
"status",
|
|
CommandType::Status,
|
|
"System status",
|
|
None, // No specific service
|
|
),
|
|
(
|
|
"list orders",
|
|
CommandType::ListOrders,
|
|
"List orders from trading service",
|
|
Some("trading"),
|
|
),
|
|
(
|
|
"list positions",
|
|
CommandType::ListPositions,
|
|
"List positions from trading service",
|
|
Some("trading"),
|
|
),
|
|
];
|
|
|
|
for (command_str, command_type, description, target_service) in &service_commands {
|
|
let execute_start = Instant::now();
|
|
match tli_client.execute_command(command_str).await {
|
|
Ok(command_result) => {
|
|
let execute_duration = execute_start.elapsed();
|
|
test_results.add_success(&format!("Executed command: {} - {}", command_str, description));
|
|
|
|
// Validate command result structure
|
|
if command_result.success {
|
|
test_results.add_success(&format!("Command '{}' executed successfully", command_str));
|
|
} else {
|
|
test_results.add_failure(&format!(
|
|
"Command '{}' failed: {}",
|
|
command_str,
|
|
command_result.error_message.unwrap_or_else(|| "No error message".to_string())
|
|
));
|
|
}
|
|
|
|
if command_result.response_data.is_some() {
|
|
test_results.add_success(&format!("Command '{}' returned data", command_str));
|
|
}
|
|
|
|
// Validate execution time based on command type
|
|
let max_duration = match command_type {
|
|
CommandType::Status => Duration::from_millis(500),
|
|
CommandType::ListOrders | CommandType::ListPositions => Duration::from_millis(1000),
|
|
_ => Duration::from_millis(2000),
|
|
};
|
|
|
|
if execute_duration <= max_duration {
|
|
test_results.add_success(&format!(
|
|
"Command '{}' execution time acceptable: {}ms",
|
|
command_str, execute_duration.as_millis()
|
|
));
|
|
} else {
|
|
test_results.add_failure(&format!(
|
|
"Command '{}' execution time too long: {}ms > {}ms",
|
|
command_str, execute_duration.as_millis(), max_duration.as_millis()
|
|
));
|
|
}
|
|
}
|
|
Err(e) => {
|
|
test_results.add_failure(&format!("Failed to execute command '{}': {}", command_str, e));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test Case 3: Invalid Command Handling
|
|
let invalid_commands = vec![
|
|
"invalid_command",
|
|
"list unknown_entity",
|
|
"config set invalid.path value",
|
|
"train model --invalid-flag value",
|
|
"", // Empty command
|
|
];
|
|
|
|
for invalid_command in &invalid_commands {
|
|
match tli_client.execute_command(invalid_command).await {
|
|
Ok(result) => {
|
|
if !result.success {
|
|
test_results.add_success(&format!(
|
|
"Invalid command '{}' properly rejected", invalid_command
|
|
));
|
|
} else {
|
|
test_results.add_failure(&format!(
|
|
"Invalid command '{}' was incorrectly accepted", invalid_command
|
|
));
|
|
}
|
|
}
|
|
Err(_) => {
|
|
test_results.add_success(&format!(
|
|
"Invalid command '{}' properly raised error", invalid_command
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test Case 4: Concurrent Command Execution
|
|
let concurrent_commands = vec![
|
|
"status",
|
|
"list orders",
|
|
"list positions",
|
|
];
|
|
|
|
let concurrent_start = Instant::now();
|
|
let mut concurrent_tasks = Vec::new();
|
|
|
|
for (i, command) in concurrent_commands.into_iter().enumerate() {
|
|
let mut client_clone = tli_client.clone();
|
|
let command_str = command.to_string();
|
|
|
|
let task = tokio::spawn(async move {
|
|
let result = client_clone.execute_command(&command_str).await;
|
|
(i, command_str, result)
|
|
});
|
|
|
|
concurrent_tasks.push(task);
|
|
}
|
|
|
|
let mut concurrent_success = 0;
|
|
let mut concurrent_failures = 0;
|
|
|
|
for task in concurrent_tasks {
|
|
match task.await {
|
|
Ok((task_id, command_str, result)) => {
|
|
match result {
|
|
Ok(command_result) => {
|
|
if command_result.success {
|
|
concurrent_success += 1;
|
|
} else {
|
|
concurrent_failures += 1;
|
|
}
|
|
}
|
|
Err(_) => {
|
|
concurrent_failures += 1;
|
|
}
|
|
}
|
|
}
|
|
Err(_) => {
|
|
concurrent_failures += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
let concurrent_duration = concurrent_start.elapsed();
|
|
|
|
if concurrent_failures == 0 {
|
|
test_results.add_success(&format!(
|
|
"All {} concurrent commands executed successfully in {}ms",
|
|
concurrent_success, concurrent_duration.as_millis()
|
|
));
|
|
} else {
|
|
test_results.add_failure(&format!(
|
|
"Concurrent command execution had failures: {} success, {} failures",
|
|
concurrent_success, concurrent_failures
|
|
));
|
|
}
|
|
}
|
|
Err(e) => {
|
|
test_results.add_failure(&format!("Failed to initialize TLI client for command testing: {}", e));
|
|
}
|
|
}
|
|
|
|
test_results.duration = start_time.elapsed();
|
|
test_results.finalize();
|
|
|
|
Ok(test_results)
|
|
}
|
|
|
|
/// Test Suite 3: Real-time Data Streaming
|
|
///
|
|
/// Validates TLI's real-time data display capabilities:
|
|
/// - Market data streaming and display
|
|
///
|
|
/// - Order status updates
|
|
/// - Performance metrics streaming
|
|
///
|
|
/// - Dashboard refresh and updates
|
|
pub async fn test_realtime_data_streaming(&self) -> IntegrationTestResult {
|
|
println!("📊 Testing TLI Client - Real-time Data Streaming");
|
|
|
|
let start_time = Instant::now();
|
|
let mut test_results = IntegrationTestResult::new("TLI Client Real-time Data Streaming");
|
|
|
|
// Initialize TLI client for streaming tests
|
|
let tli_client_config = TLIClientConfig {
|
|
service_endpoints: {
|
|
let mut endpoints = HashMap::new();
|
|
endpoints.insert("trading".to_string(), "http://localhost:50051".to_string());
|
|
endpoints.insert("backtesting".to_string(), "http://localhost:50052".to_string());
|
|
endpoints.insert("ml_training".to_string(), "http://localhost:50053".to_string());
|
|
endpoints
|
|
},
|
|
connection_timeout_ms: 5000,
|
|
retry_attempts: 3,
|
|
retry_delay_ms: 1000,
|
|
keepalive_interval_s: 30,
|
|
};
|
|
|
|
match TLIClient::new(tli_client_config).await {
|
|
Ok(mut tli_client) => {
|
|
test_results.add_success("TLI client initialized for streaming tests");
|
|
|
|
// Test Case 1: Market Data Streaming
|
|
let market_symbols = vec!["EURUSD", "GBPUSD", "USDJPY"];
|
|
|
|
for symbol in &market_symbols {
|
|
let stream_start = Instant::now();
|
|
match tli_client.subscribe_market_data(symbol).await {
|
|
Ok(mut stream) => {
|
|
test_results.add_success(&format!("Subscribed to {} market data stream", symbol));
|
|
|
|
// Collect some streaming data
|
|
let mut tick_count = 0;
|
|
let max_ticks = 10;
|
|
let timeout_duration = Duration::from_secs(5);
|
|
|
|
while tick_count < max_ticks {
|
|
match timeout(Duration::from_millis(500), stream.next()).await {
|
|
Ok(Some(tick_data)) => {
|
|
tick_count += 1;
|
|
|
|
// Validate tick data structure
|
|
if tick_data.symbol == *symbol {
|
|
test_results.add_success(&format!(
|
|
"Received valid {} tick: bid={:.5}, ask={:.5}",
|
|
symbol, tick_data.bid, tick_data.ask
|
|
));
|
|
} else {
|
|
test_results.add_failure(&format!(
|
|
"Tick data symbol mismatch: expected {}, got {}",
|
|
symbol, tick_data.symbol
|
|
));
|
|
}
|
|
|
|
// Validate tick data freshness
|
|
let tick_age = chrono::Utc::now().signed_duration_since(tick_data.timestamp);
|
|
if tick_age.num_seconds() <= 2 {
|
|
test_results.add_success(&format!("{} tick data is fresh", symbol));
|
|
} else {
|
|
test_results.add_failure(&format!(
|
|
"{} tick data is stale: {} seconds old",
|
|
symbol, tick_age.num_seconds()
|
|
));
|
|
}
|
|
}
|
|
Ok(None) => {
|
|
test_results.add_failure(&format!("Market data stream for {} ended unexpectedly", symbol));
|
|
break;
|
|
}
|
|
Err(_) => {
|
|
// Timeout - continue waiting
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if stream_start.elapsed() > timeout_duration {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if tick_count > 0 {
|
|
test_results.add_success(&format!(
|
|
"Received {} ticks for {} in streaming test",
|
|
tick_count, symbol
|
|
));
|
|
} else {
|
|
test_results.add_failure(&format!(
|
|
"No ticks received for {} within timeout",
|
|
symbol
|
|
));
|
|
}
|
|
|
|
// Unsubscribe
|
|
match tli_client.unsubscribe_market_data(symbol).await {
|
|
Ok(_) => {
|
|
test_results.add_success(&format!("Unsubscribed from {} market data", symbol));
|
|
}
|
|
Err(e) => {
|
|
test_results.add_failure(&format!("Failed to unsubscribe from {}: {}", symbol, e));
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
test_results.add_failure(&format!("Failed to subscribe to {} market data: {}", symbol, e));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test Case 2: Order Status Streaming
|
|
match tli_client.subscribe_order_updates().await {
|
|
Ok(mut order_stream) => {
|
|
test_results.add_success("Subscribed to order status updates");
|
|
|
|
// Place a test order to generate updates
|
|
let test_order = TestOrder {
|
|
symbol: "EURUSD".to_string(),
|
|
side: "buy".to_string(),
|
|
quantity: 100000.0,
|
|
order_type: "market".to_string(),
|
|
};
|
|
|
|
match tli_client.execute_command(&format!(
|
|
"place order --symbol {} --side {} --quantity {} --type {}",
|
|
test_order.symbol, test_order.side, test_order.quantity, test_order.order_type
|
|
)).await {
|
|
Ok(order_result) => {
|
|
if order_result.success {
|
|
test_results.add_success("Test order placed for streaming validation");
|
|
|
|
// Listen for order updates
|
|
let mut update_count = 0;
|
|
let max_updates = 3;
|
|
let update_timeout = Duration::from_secs(10);
|
|
let update_start = Instant::now();
|
|
|
|
while update_count < max_updates && update_start.elapsed() < update_timeout {
|
|
match timeout(Duration::from_millis(1000), order_stream.next()).await {
|
|
Ok(Some(order_update)) => {
|
|
update_count += 1;
|
|
test_results.add_success(&format!(
|
|
"Received order update #{}: status={}",
|
|
update_count, order_update.status
|
|
));
|
|
|
|
// Validate update structure
|
|
if order_update.order_id.is_some() {
|
|
test_results.add_success("Order update contains order ID");
|
|
} else {
|
|
test_results.add_failure("Order update missing order ID");
|
|
}
|
|
|
|
if order_update.timestamp.is_some() {
|
|
test_results.add_success("Order update contains timestamp");
|
|
} else {
|
|
test_results.add_failure("Order update missing timestamp");
|
|
}
|
|
}
|
|
Ok(None) => {
|
|
test_results.add_failure("Order update stream ended unexpectedly");
|
|
break;
|
|
}
|
|
Err(_) => {
|
|
// Timeout - continue waiting
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if update_count > 0 {
|
|
test_results.add_success(&format!("Received {} order updates", update_count));
|
|
} else {
|
|
test_results.add_failure("No order updates received");
|
|
}
|
|
} else {
|
|
test_results.add_failure("Failed to place test order for streaming");
|
|
}
|
|
}
|
|
Err(e) => {
|
|
test_results.add_failure(&format!("Failed to execute order command: {}", e));
|
|
}
|
|
}
|
|
|
|
// Unsubscribe from order updates
|
|
match tli_client.unsubscribe_order_updates().await {
|
|
Ok(_) => {
|
|
test_results.add_success("Unsubscribed from order updates");
|
|
}
|
|
Err(e) => {
|
|
test_results.add_failure(&format!("Failed to unsubscribe from order updates: {}", e));
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
test_results.add_failure(&format!("Failed to subscribe to order updates: {}", e));
|
|
}
|
|
}
|
|
|
|
// Test Case 3: Performance Metrics Streaming
|
|
match tli_client.subscribe_performance_metrics().await {
|
|
Ok(mut metrics_stream) => {
|
|
test_results.add_success("Subscribed to performance metrics stream");
|
|
|
|
let mut metrics_count = 0;
|
|
let max_metrics = 5;
|
|
let metrics_timeout = Duration::from_secs(15);
|
|
let metrics_start = Instant::now();
|
|
|
|
while metrics_count < max_metrics && metrics_start.elapsed() < metrics_timeout {
|
|
match timeout(Duration::from_millis(2000), metrics_stream.next()).await {
|
|
Ok(Some(performance_metrics)) => {
|
|
metrics_count += 1;
|
|
test_results.add_success(&format!(
|
|
"Received performance metrics #{}", metrics_count
|
|
));
|
|
|
|
// Validate metrics structure
|
|
if performance_metrics.latency_stats.is_some() {
|
|
let latency = performance_metrics.latency_stats.unwrap();
|
|
test_results.add_success(&format!(
|
|
"Latency metrics: avg={}μs, p99={}μs",
|
|
latency.average_us, latency.p99_us
|
|
));
|
|
}
|
|
|
|
if performance_metrics.throughput_stats.is_some() {
|
|
let throughput = performance_metrics.throughput_stats.unwrap();
|
|
test_results.add_success(&format!(
|
|
"Throughput metrics: {}ops/s",
|
|
throughput.operations_per_second
|
|
));
|
|
}
|
|
|
|
if performance_metrics.system_stats.is_some() {
|
|
test_results.add_success("System stats included in performance metrics");
|
|
}
|
|
}
|
|
Ok(None) => {
|
|
test_results.add_failure("Performance metrics stream ended unexpectedly");
|
|
break;
|
|
}
|
|
Err(_) => {
|
|
// Timeout - continue waiting
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if metrics_count > 0 {
|
|
test_results.add_success(&format!("Received {} performance metric updates", metrics_count));
|
|
} else {
|
|
test_results.add_failure("No performance metrics received");
|
|
}
|
|
|
|
// Unsubscribe
|
|
match tli_client.unsubscribe_performance_metrics().await {
|
|
Ok(_) => {
|
|
test_results.add_success("Unsubscribed from performance metrics");
|
|
}
|
|
Err(e) => {
|
|
test_results.add_failure(&format!("Failed to unsubscribe from performance metrics: {}", e));
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
test_results.add_failure(&format!("Failed to subscribe to performance metrics: {}", e));
|
|
}
|
|
}
|
|
|
|
// Test Case 4: Multiple Stream Management
|
|
let multi_stream_start = Instant::now();
|
|
let mut active_streams = 0;
|
|
|
|
// Subscribe to multiple streams simultaneously
|
|
let eurusd_stream = tli_client.subscribe_market_data("EURUSD").await;
|
|
let gbpusd_stream = tli_client.subscribe_market_data("GBPUSD").await;
|
|
let performance_stream = tli_client.subscribe_performance_metrics().await;
|
|
|
|
if eurusd_stream.is_ok() { active_streams += 1; }
|
|
if gbpusd_stream.is_ok() { active_streams += 1; }
|
|
if performance_stream.is_ok() { active_streams += 1; }
|
|
|
|
test_results.add_success(&format!("Successfully started {} simultaneous streams", active_streams));
|
|
|
|
// Let streams run for a short time
|
|
tokio::time::sleep(Duration::from_secs(3)).await;
|
|
|
|
// Clean up all streams
|
|
let _ = tli_client.unsubscribe_market_data("EURUSD").await;
|
|
let _ = tli_client.unsubscribe_market_data("GBPUSD").await;
|
|
let _ = tli_client.unsubscribe_performance_metrics().await;
|
|
|
|
let multi_stream_duration = multi_stream_start.elapsed();
|
|
test_results.add_success(&format!(
|
|
"Multiple stream test completed in {}s",
|
|
multi_stream_duration.as_secs()
|
|
));
|
|
}
|
|
Err(e) => {
|
|
test_results.add_failure(&format!("Failed to initialize TLI client for streaming tests: {}", e));
|
|
}
|
|
}
|
|
|
|
test_results.duration = start_time.elapsed();
|
|
test_results.finalize();
|
|
|
|
Ok(test_results)
|
|
}
|
|
|
|
/// Execute complete TLI Client test suite
|
|
pub async fn run_all_tests(&self) -> Result<Vec<IntegrationTestResult>, Box<dyn std::error::Error>> {
|
|
println!("🚀 Starting TLI Client Integration Test Suite");
|
|
|
|
let mut results = Vec::new();
|
|
|
|
// Test Suite 1: Service Connection Management
|
|
match timeout(Duration::from_secs(self.orchestrator.config.test_timeout_secs),
|
|
self.test_service_connection_management()).await {
|
|
Ok(Ok(result)) => results.push(result),
|
|
Ok(Err(e)) => {
|
|
let mut failed_result = IntegrationTestResult::new("TLI Client Service Connection Management");
|
|
failed_result.add_failure(&format!("Test suite failed: {}", e));
|
|
failed_result.finalize();
|
|
results.push(failed_result);
|
|
},
|
|
Err(_) => {
|
|
let mut timeout_result = IntegrationTestResult::new("TLI Client Service Connection Management");
|
|
timeout_result.add_failure("Test suite timed out");
|
|
timeout_result.finalize();
|
|
results.push(timeout_result);
|
|
}
|
|
}
|
|
|
|
// Test Suite 2: Command Execution Interface
|
|
match timeout(Duration::from_secs(self.orchestrator.config.test_timeout_secs),
|
|
self.test_command_execution_interface()).await {
|
|
Ok(Ok(result)) => results.push(result),
|
|
Ok(Err(e)) => {
|
|
let mut failed_result = IntegrationTestResult::new("TLI Client Command Execution Interface");
|
|
failed_result.add_failure(&format!("Test suite failed: {}", e));
|
|
failed_result.finalize();
|
|
results.push(failed_result);
|
|
},
|
|
Err(_) => {
|
|
let mut timeout_result = IntegrationTestResult::new("TLI Client Command Execution Interface");
|
|
timeout_result.add_failure("Test suite timed out");
|
|
timeout_result.finalize();
|
|
results.push(timeout_result);
|
|
}
|
|
}
|
|
|
|
// Test Suite 3: Real-time Data Streaming
|
|
match timeout(Duration::from_secs(self.orchestrator.config.test_timeout_secs * 2),
|
|
self.test_realtime_data_streaming()).await {
|
|
Ok(Ok(result)) => results.push(result),
|
|
Ok(Err(e)) => {
|
|
let mut failed_result = IntegrationTestResult::new("TLI Client Real-time Data Streaming");
|
|
failed_result.add_failure(&format!("Test suite failed: {}", e));
|
|
failed_result.finalize();
|
|
results.push(failed_result);
|
|
},
|
|
Err(_) => {
|
|
let mut timeout_result = IntegrationTestResult::new("TLI Client Real-time Data Streaming");
|
|
timeout_result.add_failure("Test suite timed out");
|
|
timeout_result.finalize();
|
|
results.push(timeout_result);
|
|
}
|
|
}
|
|
|
|
// Print summary
|
|
let total_tests = results.len();
|
|
let passed_tests = results.iter().filter(|r| r.passed).count();
|
|
let failed_tests = total_tests - passed_tests;
|
|
|
|
println!("📊 TLI Client Integration Test Summary:");
|
|
println!(" Total Test Suites: {}", total_tests);
|
|
println!(" Passed: {} ✅", passed_tests);
|
|
println!(" Failed: {} ❌", failed_tests);
|
|
|
|
if failed_tests == 0 {
|
|
println!("🎉 All TLI Client integration tests passed!");
|
|
} else {
|
|
println!("⚠️ {} TLI Client integration test suite(s) failed", failed_tests);
|
|
}
|
|
|
|
Ok(results)
|
|
}
|
|
}
|
|
|
|
// Supporting types for TLI client tests
|
|
#[derive(Debug, Clone)]
|
|
pub struct TLIClientConfig {
|
|
pub service_endpoints: HashMap<String, String>,
|
|
pub connection_timeout_ms: u64,
|
|
pub retry_attempts: usize,
|
|
pub retry_delay_ms: u64,
|
|
pub keepalive_interval_s: u64,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct TestOrder {
|
|
pub symbol: String,
|
|
pub side: String,
|
|
pub quantity: f64,
|
|
pub order_type: String,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use tokio;
|
|
|
|
#[tokio::test]
|
|
async fn integration_test_tli_client_complete() {
|
|
let test_suite = TLIClientTests::new().await
|
|
.expect("Failed to initialize TLI Client test suite");
|
|
|
|
let results = test_suite.run_all_tests().await
|
|
.expect("Failed to run TLI Client test suite");
|
|
|
|
// Ensure all tests passed
|
|
for result in &results {
|
|
assert!(result.passed, "Test suite '{}' failed: {:?}", result.test_name, result.failures);
|
|
}
|
|
|
|
// Validate performance requirements met
|
|
for result in &results {
|
|
assert!(
|
|
result.duration.as_secs() <= 120, // 2 minutes max for TLI tests
|
|
"Test suite '{}' took too long: {}s",
|
|
result.test_name,
|
|
result.duration.as_secs()
|
|
);
|
|
}
|
|
}
|
|
} |