//! Client integration tests for TLI gRPC functionality use std::time::Duration; use tokio::time::{sleep, timeout}; use fxt::prelude::*; use fxt::{TliClient, ServiceEndpoints}; use crate::integration::{TestConfig, TestUtilities, integration_test}; use crate::mocks::grpc_server::MockGrpcServer; #[tokio::test] async fn test_client_connection_success() { TestUtilities::validate_test_environment().unwrap(); let mock_server = MockGrpcServer::start(51000).await.unwrap(); let mut client = TestUtilities::create_test_client(51000); // Test connection let result = timeout(Duration::from_secs(5), client.connect()).await; assert!(result.is_ok(), "Client connection should succeed"); } #[tokio::test] async fn test_client_connection_failure() { TestUtilities::validate_test_environment().unwrap(); // Try to connect to non-existent server let mut client = TestUtilities::create_test_client(59999); let result = client.connect().await; // Connection should proceed without error (connections are lazy) assert!(result.is_ok()); // But health check should fail let health_result = client.check_health().await; assert!(health_result.is_err() || health_result.unwrap().is_empty()); } #[tokio::test] async fn test_order_submission_workflow() { TestUtilities::validate_test_environment().unwrap(); let mock_server = MockGrpcServer::start(51001).await.unwrap(); let mut client = TestUtilities::create_test_client(51001); // Connect to services client.connect().await.unwrap(); // Allow some time for connection establishment sleep(Duration::from_millis(100)).await; // Get trading client and submit order let trading_client = client.trading(); if trading_client.is_err() { // Skip test if trading service not available return; } let order_request = tli::proto::trading::SubmitOrderRequest { symbol: "AAPL".to_string(), side: tli::proto::trading::OrderSide::Buy as i32, order_type: tli::proto::trading::OrderType::Market as i32, quantity: 100.0, price: Some(150.0), }; let result = trading_client.unwrap() .submit_order(tonic::Request::new(order_request)) .await; if let Ok(response) = result { let order_response = response.into_inner(); assert!(!order_response.order_id.is_empty()); assert_eq!(order_response.status, tli::proto::trading::OrderStatus::New as i32); } } #[tokio::test] async fn test_health_check_workflow() { TestUtilities::validate_test_environment().unwrap(); let mock_server = MockGrpcServer::start(51002).await.unwrap(); let mut client = TestUtilities::create_test_client(51002); client.connect().await.unwrap(); sleep(Duration::from_millis(100)).await; let health_status = client.check_health().await; if let Ok(status_list) = health_status { assert!(!status_list.is_empty()); for status in status_list { assert!(!status.service.is_empty()); assert!(!status.endpoint.is_empty()); } } } #[tokio::test] async fn test_configuration_management() { TestUtilities::validate_test_environment().unwrap(); let mock_server = MockGrpcServer::start(51003).await.unwrap(); let mut client = TestUtilities::create_test_client(51003); client.connect().await.unwrap(); sleep(Duration::from_millis(100)).await; let config_client = client.config(); if config_client.is_err() { return; // Skip if config service not available } // Test getting existing configuration let get_request = tli::proto::trading::GetConfigRequest { key: "max_order_size".to_string(), }; let result = config_client.unwrap() .get_config(tonic::Request::new(get_request)) .await; if let Ok(response) = result { let config_response = response.into_inner(); assert!(config_response.config.is_some()); let config = config_response.config.unwrap(); assert_eq!(config.key, "max_order_size"); assert!(!config.value.is_empty()); } } #[tokio::test] async fn test_monitoring_metrics() { TestUtilities::validate_test_environment().unwrap(); let mock_server = MockGrpcServer::start(51004).await.unwrap(); let mut client = TestUtilities::create_test_client(51004); client.connect().await.unwrap(); sleep(Duration::from_millis(100)).await; let monitoring_client = client.monitoring(); if monitoring_client.is_err() { return; // Skip if monitoring service not available } // Test system status let status_request = tli::proto::trading::GetSystemStatusRequest {}; let result = monitoring_client.unwrap() .get_system_status(tonic::Request::new(status_request)) .await; if let Ok(response) = result { let status_response = response.into_inner(); assert!(!status_response.services.is_empty()); for service in status_response.services { assert!(!service.name.is_empty()); assert!(service.last_check_unix_nanos > 0); } } } #[tokio::test] async fn test_concurrent_operations() { TestUtilities::validate_test_environment().unwrap(); let mock_server = MockGrpcServer::start(51005).await.unwrap(); // Create multiple clients let mut client1 = TestUtilities::create_test_client(51005); let mut client2 = TestUtilities::create_test_client(51005); // Connect both clients concurrently let (result1, result2) = tokio::join!( client1.connect(), client2.connect() ); assert!(result1.is_ok()); assert!(result2.is_ok()); sleep(Duration::from_millis(100)).await; // Perform concurrent health checks let (health1, health2) = tokio::join!( client1.check_health(), client2.check_health() ); // Both should succeed or fail consistently assert_eq!(health1.is_ok(), health2.is_ok()); } #[tokio::test] async fn test_error_handling() { TestUtilities::validate_test_environment().unwrap(); let mock_server = MockGrpcServer::start(51006).await.unwrap(); let mut client = TestUtilities::create_test_client(51006); client.connect().await.unwrap(); sleep(Duration::from_millis(100)).await; let trading_client = client.trading(); if trading_client.is_err() { return; } // Test invalid order submission let invalid_order = tli::proto::trading::SubmitOrderRequest { symbol: "".to_string(), // Empty symbol should cause error side: tli::proto::trading::OrderSide::Buy as i32, order_type: tli::proto::trading::OrderType::Market as i32, quantity: 100.0, price: Some(150.0), }; let result = trading_client.unwrap() .submit_order(tonic::Request::new(invalid_order)) .await; assert!(result.is_err(), "Empty symbol should cause error"); if let Err(status) = result { assert_eq!(status.code(), tonic::Code::InvalidArgument); } } #[tokio::test] async fn test_service_discovery() { TestUtilities::validate_test_environment().unwrap(); // Test default endpoints let endpoints = ServiceEndpoints::default(); assert!(endpoints.trading_engine.contains("localhost")); assert!(endpoints.risk_management.contains("localhost")); assert!(endpoints.ml_signals.contains("localhost")); assert!(endpoints.market_data.contains("localhost")); assert!(endpoints.health_check.contains("localhost")); // Test custom endpoints let custom_endpoints = ServiceEndpoints { trading_engine: "http://custom:8080".to_string(), risk_management: "http://custom:8081".to_string(), ml_signals: "http://custom:8082".to_string(), market_data: "http://custom:8083".to_string(), health_check: "http://custom:8084".to_string(), }; let client = TliClient::with_endpoints(custom_endpoints.clone()); // Verify endpoints are stored correctly assert_eq!(client.endpoints.trading_engine, custom_endpoints.trading_engine); } #[tokio::test] async fn test_graceful_disconnection() { TestUtilities::validate_test_environment().unwrap(); let mock_server = MockGrpcServer::start(51007).await.unwrap(); let mut client = TestUtilities::create_test_client(51007); // Connect client.connect().await.unwrap(); sleep(Duration::from_millis(100)).await; // Verify connection works let health_before = client.check_health().await; // Disconnect client.disconnect().await; // Verify clients are cleared assert!(client.trading().is_err()); assert!(client.monitoring().is_err()); assert!(client.config().is_err()); }