#![allow( clippy::tests_outside_test_module, clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing, clippy::str_to_string, clippy::string_to_string, clippy::assertions_on_result_states, clippy::assertions_on_constants, clippy::let_underscore_must_use, clippy::use_debug, clippy::doc_markdown, clippy::shadow_unrelated, clippy::shadow_reuse, clippy::similar_names, clippy::clone_on_copy, clippy::get_unwrap, clippy::modulo_arithmetic, clippy::integer_division, clippy::non_ascii_literal, clippy::useless_vec, clippy::useless_format, clippy::wildcard_enum_match_arm, clippy::manual_range_contains, clippy::const_is_empty, clippy::needless_range_loop, clippy::field_reassign_with_default, clippy::items_after_test_module, clippy::missing_const_for_fn, unused_imports, unused_variables, unused_mut, unused_assignments, unused_comparisons, unused_must_use, dead_code, )] //! Comprehensive brokers module tests targeting 95% coverage //! Tests for brokers/mod.rs and related broker connection functionality //! //! These tests exercise BrokerConnector with default configuration, where no //! broker clients are actually connected. submit_order/cancel_order correctly //! return Err when no broker is available, and get_connected_brokers returns //! an empty list. use chrono::Utc; use common::{OrderSide, OrderStatus, OrderType, TimeInForce}; use rust_decimal::Decimal; use std::collections::HashMap; use trading_engine::brokers::config::BrokerConnectorConfig; use trading_engine::brokers::BrokerConnector; use trading_engine::trading_operations::TradingOrder; /// Create a minimal TradingOrder for testing. fn make_test_order(symbol: &str) -> TradingOrder { TradingOrder { id: common::OrderId::new(), symbol: symbol.to_owned(), side: OrderSide::Buy, order_type: OrderType::Market, quantity: Decimal::from(1), price: Decimal::from(100), time_in_force: TimeInForce::Day, account_id: None, metadata: HashMap::new(), created_at: Utc::now(), submitted_at: None, executed_at: None, status: OrderStatus::Created, fill_quantity: Decimal::ZERO, average_fill_price: None, } } // ============================================================================ // BrokerConnector::new() Tests // ============================================================================ #[cfg(test)] mod broker_connector_creation_tests { use super::*; #[test] fn test_broker_connector_new_default_config() { let config = BrokerConnectorConfig::default(); let connector = BrokerConnector::new(config); // Verify connector was created (should not panic) assert!(format!("{:?}", connector).contains("BrokerConnector")); } #[test] fn test_broker_connector_new_custom_config() { let mut config = BrokerConnectorConfig::default(); config.brokers.interactive_brokers.enabled = true; config.fail_on_broker_error = true; let connector = BrokerConnector::new(config); assert!(format!("{:?}", connector).contains("BrokerConnector")); } #[test] fn test_broker_connector_new_disabled_config() { let mut config = BrokerConnectorConfig::default(); config.brokers.interactive_brokers.enabled = false; let connector = BrokerConnector::new(config); assert!(format!("{:?}", connector).contains("BrokerConnector")); } #[test] fn test_multiple_broker_connectors_independent() { let config1 = BrokerConnectorConfig::default(); let config2 = BrokerConnectorConfig::default(); let connector1 = BrokerConnector::new(config1); let connector2 = BrokerConnector::new(config2); // Both should be independent instances assert!(format!("{:?}", connector1).contains("BrokerConnector")); assert!(format!("{:?}", connector2).contains("BrokerConnector")); } } // ============================================================================ // BrokerConnector::initialize() Tests // ============================================================================ #[cfg(test)] mod broker_connector_initialization_tests { use super::*; #[tokio::test] async fn test_broker_connector_initialize_success() { let config = BrokerConnectorConfig::default(); let mut connector = BrokerConnector::new(config); let result = connector.initialize().await; assert!(result.is_ok()); } #[tokio::test] async fn test_broker_connector_initialize_multiple_times() { let config = BrokerConnectorConfig::default(); let mut connector = BrokerConnector::new(config); // Initialize multiple times should be safe assert!(connector.initialize().await.is_ok()); assert!(connector.initialize().await.is_ok()); assert!(connector.initialize().await.is_ok()); } #[tokio::test] async fn test_broker_connector_initialize_with_custom_config() { let mut config = BrokerConnectorConfig::default(); config.fail_on_broker_error = true; let mut connector = BrokerConnector::new(config); // With default broker configs (both disabled) and fail_on_broker_error=true, // initialize succeeds because no connection attempts are made for disabled brokers. let result = connector.initialize().await; assert!(result.is_ok()); } #[tokio::test] async fn test_broker_connector_initialize_concurrent() { let config = BrokerConnectorConfig::default(); let mut connector1 = BrokerConnector::new(config.clone()); let mut connector2 = BrokerConnector::new(config); let (result1, result2) = tokio::join!(connector1.initialize(), connector2.initialize()); assert!(result1.is_ok()); assert!(result2.is_ok()); } } // ============================================================================ // BrokerConnector::submit_order() Tests // ============================================================================ #[cfg(test)] mod broker_connector_submit_order_tests { use super::*; #[tokio::test] async fn test_broker_connector_submit_order_no_broker_connected() { let config = BrokerConnectorConfig::default(); let connector = BrokerConnector::new(config); let order = make_test_order("ES.FUT"); let result = connector.submit_order(&order).await; // With default config, IB is enabled=false so ib_client is None. // submit_order routes to "InteractiveBrokers" and returns an error. assert!(result.is_err(), "submit_order should fail when no broker is configured"); } #[tokio::test] async fn test_broker_connector_submit_order_multiple() { let config = BrokerConnectorConfig::default(); let connector = BrokerConnector::new(config); let result1 = connector.submit_order(&make_test_order("ES.FUT")).await; let result2 = connector.submit_order(&make_test_order("NQ.FUT")).await; let result3 = connector.submit_order(&make_test_order("6E.FUT")).await; // All should return errors (no broker connected) assert!(result1.is_err()); assert!(result2.is_err()); assert!(result3.is_err()); } #[tokio::test] async fn test_broker_connector_submit_order_various_symbols() { let config = BrokerConnectorConfig::default(); let connector = BrokerConnector::new(config); let symbols = vec!["ES.FUT", "NQ.FUT", "6E.FUT", "ZN.FUT"]; for symbol in symbols { let order = make_test_order(symbol); let result = connector.submit_order(&order).await; assert!(result.is_err(), "Expected error for symbol: {}", symbol); } } #[tokio::test] async fn test_broker_connector_submit_order_concurrent() { let config = BrokerConnectorConfig::default(); let connector = std::sync::Arc::new(BrokerConnector::new(config)); let mut handles = vec![]; for i in 0..10 { let connector_clone = connector.clone(); let handle = tokio::spawn(async move { let order = make_test_order(&format!("SYM_{}", i)); let result = connector_clone.submit_order(&order).await; assert!(result.is_err(), "Expected error for concurrent order {}", i); }); handles.push(handle); } for handle in handles { handle.await.expect("Task should not panic"); } } #[tokio::test] async fn test_broker_connector_submit_order_unknown_broker() { let mut config = BrokerConnectorConfig::default(); config.routing.default_broker = "UnknownBroker".to_owned(); let connector = BrokerConnector::new(config); let order = make_test_order("ES.FUT"); let result = connector.submit_order(&order).await; assert!(result.is_err()); let err_msg = result.err().map(|e| e.to_string()).unwrap_or_default(); assert!( err_msg.contains("Unknown default broker"), "Expected unknown broker error, got: {}", err_msg ); } #[tokio::test] async fn test_broker_connector_submit_order_icmarkets_not_connected() { let mut config = BrokerConnectorConfig::default(); config.routing.default_broker = "ICMarkets".to_owned(); // ICMarkets is disabled by default so icm_client = None let connector = BrokerConnector::new(config); let order = make_test_order("EURUSD"); let result = connector.submit_order(&order).await; assert!(result.is_err()); } } // ============================================================================ // BrokerConnector::cancel_order() Tests // ============================================================================ #[cfg(test)] mod broker_connector_cancel_order_tests { use super::*; #[tokio::test] async fn test_broker_connector_cancel_order_no_broker_connected() { let config = BrokerConnectorConfig::default(); let connector = BrokerConnector::new(config); let result = connector.cancel_order("ORD_123").await; // No brokers connected, so cancel fails assert!(result.is_err(), "cancel_order should fail when no broker is connected"); } #[tokio::test] async fn test_broker_connector_cancel_order_nonexistent() { let config = BrokerConnectorConfig::default(); let connector = BrokerConnector::new(config); let result = connector.cancel_order("NONEXISTENT").await; assert!(result.is_err()); } #[tokio::test] async fn test_broker_connector_cancel_order_multiple_times() { let config = BrokerConnectorConfig::default(); let connector = BrokerConnector::new(config); // Cancel same order multiple times - all should fail (no broker) let order_id = "ORD_999"; assert!(connector.cancel_order(order_id).await.is_err()); assert!(connector.cancel_order(order_id).await.is_err()); assert!(connector.cancel_order(order_id).await.is_err()); } #[tokio::test] async fn test_broker_connector_cancel_order_empty_id() { let config = BrokerConnectorConfig::default(); let connector = BrokerConnector::new(config); let result = connector.cancel_order("").await; assert!(result.is_err()); } #[tokio::test] async fn test_broker_connector_cancel_order_concurrent() { let config = BrokerConnectorConfig::default(); let connector = std::sync::Arc::new(BrokerConnector::new(config)); let mut handles = vec![]; for i in 0..10 { let connector_clone = connector.clone(); let handle = tokio::spawn(async move { let result = connector_clone.cancel_order(&format!("ORD_{}", i)).await; assert!(result.is_err(), "Expected error for concurrent cancel {}", i); }); handles.push(handle); } for handle in handles { handle.await.expect("Task should not panic"); } } #[tokio::test] async fn test_broker_connector_submit_and_cancel_workflow() { let config = BrokerConnectorConfig::default(); let connector = BrokerConnector::new(config); // Both submit and cancel should return errors (no broker connected) let order = make_test_order("ES.FUT"); let submit_result = connector.submit_order(&order).await; assert!(submit_result.is_err()); let cancel_result = connector.cancel_order("ORD_WORKFLOW").await; assert!(cancel_result.is_err()); } } // ============================================================================ // BrokerConnector::get_connected_brokers() Tests // ============================================================================ #[cfg(test)] mod broker_connector_get_connected_brokers_tests { use super::*; #[tokio::test] async fn test_broker_connector_get_connected_brokers_initial() { let config = BrokerConnectorConfig::default(); let connector = BrokerConnector::new(config); let brokers = connector.get_connected_brokers().await; // Default config: both brokers disabled, none connected assert!( brokers.is_empty(), "Expected no connected brokers with default config, got: {:?}", brokers ); } #[tokio::test] async fn test_broker_connector_get_connected_brokers_after_init() { let config = BrokerConnectorConfig::default(); let mut connector = BrokerConnector::new(config); connector.initialize().await.expect("init should succeed"); let brokers = connector.get_connected_brokers().await; // Still empty because no broker is enabled in default config assert!(brokers.is_empty()); } #[tokio::test] async fn test_broker_connector_get_connected_brokers_multiple_calls() { let config = BrokerConnectorConfig::default(); let connector = BrokerConnector::new(config); let brokers1 = connector.get_connected_brokers().await; let brokers2 = connector.get_connected_brokers().await; let brokers3 = connector.get_connected_brokers().await; assert_eq!(brokers1, brokers2); assert_eq!(brokers2, brokers3); } #[tokio::test] async fn test_broker_connector_get_connected_brokers_concurrent() { let config = BrokerConnectorConfig::default(); let connector = std::sync::Arc::new(BrokerConnector::new(config)); let mut handles = vec![]; for _ in 0..5 { let connector_clone = connector.clone(); let handle = tokio::spawn(async move { connector_clone.get_connected_brokers().await }); handles.push(handle); } for handle in handles { let brokers = handle.await.expect("Task should not panic"); assert!(brokers.is_empty()); } } } // ============================================================================ // BrokerConnector::shutdown() Tests // ============================================================================ #[cfg(test)] mod broker_connector_shutdown_tests { use super::*; #[tokio::test] async fn test_broker_connector_shutdown_success() { let config = BrokerConnectorConfig::default(); let mut connector = BrokerConnector::new(config); connector.initialize().await.expect("init should succeed"); let result = connector.shutdown().await; assert!(result.is_ok()); } #[tokio::test] async fn test_broker_connector_shutdown_without_init() { let config = BrokerConnectorConfig::default(); let mut connector = BrokerConnector::new(config); let result = connector.shutdown().await; assert!(result.is_ok()); } #[tokio::test] async fn test_broker_connector_shutdown_multiple_times() { let config = BrokerConnectorConfig::default(); let mut connector = BrokerConnector::new(config); connector.initialize().await.expect("init should succeed"); assert!(connector.shutdown().await.is_ok()); assert!(connector.shutdown().await.is_ok()); assert!(connector.shutdown().await.is_ok()); } #[tokio::test] async fn test_broker_connector_init_shutdown_cycle() { let config = BrokerConnectorConfig::default(); let mut connector = BrokerConnector::new(config); // Multiple init-shutdown cycles for _ in 0..3 { assert!(connector.initialize().await.is_ok()); assert!(connector.shutdown().await.is_ok()); } } } // ============================================================================ // BrokerConfig Tests // ============================================================================ #[cfg(test)] mod broker_config_tests { use super::*; #[test] fn test_broker_config_default() { let config = BrokerConnectorConfig::default(); assert!(format!("{:?}", config).contains("BrokerConnectorConfig")); } #[test] fn test_broker_config_enabled_flag() { let mut config = BrokerConnectorConfig::default(); config.brokers.interactive_brokers.enabled = true; assert!(config.brokers.interactive_brokers.enabled); config.brokers.interactive_brokers.enabled = false; assert!(!config.brokers.interactive_brokers.enabled); } #[test] fn test_broker_config_fail_on_broker_error_flag() { let mut config = BrokerConnectorConfig::default(); config.fail_on_broker_error = true; assert!(config.fail_on_broker_error); config.fail_on_broker_error = false; assert!(!config.fail_on_broker_error); } #[test] fn test_broker_config_clone() { let config1 = BrokerConnectorConfig::default(); let config2 = config1.clone(); assert_eq!( config1.brokers.interactive_brokers.enabled, config2.brokers.interactive_brokers.enabled ); assert_eq!(config1.fail_on_broker_error, config2.fail_on_broker_error); } } // ============================================================================ // Integration and Workflow Tests // ============================================================================ #[cfg(test)] mod broker_connector_integration_tests { use super::*; #[tokio::test] async fn test_broker_connector_complete_workflow() { let config = BrokerConnectorConfig::default(); let mut connector = BrokerConnector::new(config); // Initialize assert!(connector.initialize().await.is_ok()); // Get connected brokers (empty -- no broker enabled by default) let brokers = connector.get_connected_brokers().await; assert!(brokers.is_empty()); // Submit orders (should fail -- no broker connected) let order1 = make_test_order("ES.FUT"); let order2 = make_test_order("NQ.FUT"); assert!(connector.submit_order(&order1).await.is_err()); assert!(connector.submit_order(&order2).await.is_err()); // Cancel orders (should fail -- no broker connected) assert!(connector.cancel_order("ORD_001").await.is_err()); assert!(connector.cancel_order("ORD_002").await.is_err()); // Shutdown assert!(connector.shutdown().await.is_ok()); } #[tokio::test] async fn test_broker_connector_high_volume_orders() { let config = BrokerConnectorConfig::default(); let mut connector = BrokerConnector::new(config); connector.initialize().await.expect("init should succeed"); // Submit 100 orders (all should fail -- no broker connected) for i in 0..100 { let order = make_test_order(&format!("SYM_{:04}", i)); let result = connector.submit_order(&order).await; assert!(result.is_err()); } connector.shutdown().await.expect("shutdown should succeed"); } #[tokio::test] async fn test_broker_connector_concurrent_operations() { let config = BrokerConnectorConfig::default(); let connector = std::sync::Arc::new(BrokerConnector::new(config)); let submit_handles: Vec<_> = (0..10) .map(|i| { let connector_clone = connector.clone(); tokio::spawn(async move { let order = make_test_order(&format!("SYM_{}", i)); let _ = connector_clone.submit_order(&order).await; }) }) .collect(); let cancel_handles: Vec<_> = (0..10) .map(|i| { let connector_clone = connector.clone(); tokio::spawn(async move { let _ = connector_clone.cancel_order(&format!("ORD_{}", i)).await; }) }) .collect(); let broker_handles: Vec<_> = (0..5) .map(|_| { let connector_clone = connector.clone(); tokio::spawn(async move { connector_clone.get_connected_brokers().await }) }) .collect(); // All operations should complete without panicking for handle in submit_handles { handle.await.expect("submit task should not panic"); } for handle in cancel_handles { handle.await.expect("cancel task should not panic"); } for handle in broker_handles { let brokers = handle.await.expect("broker list task should not panic"); assert!(brokers.is_empty()); } } #[tokio::test] async fn test_broker_connector_stress_test() { let config = BrokerConnectorConfig::default(); let connector = std::sync::Arc::new(BrokerConnector::new(config)); // Simulate high concurrent load let mut handles = vec![]; for i in 0..50 { let connector_clone = connector.clone(); let handle = tokio::spawn(async move { match i % 3 { 0 => { let order = make_test_order(&format!("SYM_{}", i)); let _ = connector_clone.submit_order(&order).await; } 1 => { let _ = connector_clone.cancel_order(&format!("ORD_{}", i)).await; } _ => { connector_clone.get_connected_brokers().await; } } }); handles.push(handle); } for handle in handles { handle.await.expect("stress test task should not panic"); } } } // ============================================================================ // Edge Cases and Error Conditions // ============================================================================ #[cfg(test)] mod broker_connector_edge_cases { use super::*; #[tokio::test] async fn test_broker_connector_operations_before_init() { let config = BrokerConnectorConfig::default(); let connector = BrokerConnector::new(config); // Operations should return errors (no broker connected) even without init let order = make_test_order("ES.FUT"); assert!(connector.submit_order(&order).await.is_err()); assert!(connector.cancel_order("ORD_123").await.is_err()); assert!(connector.get_connected_brokers().await.is_empty()); } #[tokio::test] async fn test_broker_connector_operations_after_shutdown() { let config = BrokerConnectorConfig::default(); let mut connector = BrokerConnector::new(config); connector.initialize().await.expect("init should succeed"); connector.shutdown().await.expect("shutdown should succeed"); // Operations should return errors after shutdown let order = make_test_order("ES.FUT"); assert!(connector.submit_order(&order).await.is_err()); assert!(connector.cancel_order("ORD_123").await.is_err()); } #[tokio::test] async fn test_broker_connector_different_order_sides() { let config = BrokerConnectorConfig::default(); let connector = BrokerConnector::new(config); let mut buy_order = make_test_order("ES.FUT"); buy_order.side = OrderSide::Buy; assert!(connector.submit_order(&buy_order).await.is_err()); let mut sell_order = make_test_order("ES.FUT"); sell_order.side = OrderSide::Sell; assert!(connector.submit_order(&sell_order).await.is_err()); } #[tokio::test] async fn test_broker_connector_different_order_types() { let config = BrokerConnectorConfig::default(); let connector = BrokerConnector::new(config); let mut market_order = make_test_order("ES.FUT"); market_order.order_type = OrderType::Market; assert!(connector.submit_order(&market_order).await.is_err()); let mut limit_order = make_test_order("ES.FUT"); limit_order.order_type = OrderType::Limit; limit_order.price = Decimal::from(5000); assert!(connector.submit_order(&limit_order).await.is_err()); } #[tokio::test] async fn test_broker_connector_order_with_metadata() { let config = BrokerConnectorConfig::default(); let connector = BrokerConnector::new(config); let mut order = make_test_order("ES.FUT"); order.metadata.insert("strategy".to_owned(), "mean_reversion".to_owned()); order.metadata.insert("signal_strength".to_owned(), "0.85".to_owned()); assert!(connector.submit_order(&order).await.is_err()); } }