#![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 edge case tests for trading engine managers //! //! This test suite covers error paths, boundary conditions, and edge cases //! to improve overall test coverage toward 95%. use chrono::{Duration, Utc}; use common::{OrderId, OrderSide, OrderStatus, OrderType, TimeInForce}; use rust_decimal::Decimal; use std::collections::HashMap; use trading_engine::trading::{ account_manager::AccountManager, engine::AccountInfo, order_manager::OrderManager, position_manager::PositionManager, }; use trading_engine::trading_operations::{ExecutionResult, LiquidityFlag, TradingOrder}; // ============================================================================ // Order Manager Edge Cases // ============================================================================ #[tokio::test] async fn test_order_manager_zero_price_limit_order() { let manager = OrderManager::new(); let mut order = create_test_order("zero-price", "BTCUSD", 100, 0); order.order_type = OrderType::Limit; order.price = Decimal::ZERO; let result = manager.validate_order(&order).await; assert!(result.is_err()); assert!(result.unwrap_err().contains("price")); } #[tokio::test] async fn test_order_manager_market_order_with_zero_price() { let manager = OrderManager::new(); let mut order = create_test_order("market-zero", "BTCUSD", 100, 0); order.order_type = OrderType::Market; order.price = Decimal::ZERO; // Market orders can have zero price let result = manager.validate_order(&order).await; assert!(result.is_ok()); } #[tokio::test] async fn test_order_manager_execution_overfill() { let manager = OrderManager::new(); let mut order = create_test_order("overfill", "BTCUSD", 100, 50000); order.status = OrderStatus::Submitted; manager.add_order(order.clone()).await; // Execute more than ordered quantity let execution = ExecutionResult { order_id: order.id, symbol: "BTCUSD".to_string(), side: OrderSide::Buy, executed_quantity: Decimal::from(150), // More than 100 ordered execution_price: Decimal::from(50000), execution_time: Utc::now(), commission: Decimal::from(10), liquidity_flag: LiquidityFlag::Maker, }; let result = manager.process_execution(&execution).await; assert!(result.is_ok()); let updated = manager.get_order(&order.id).await.unwrap(); assert_eq!(updated.status, OrderStatus::Filled); assert_eq!(updated.fill_quantity, Decimal::from(150)); } #[tokio::test] async fn test_order_manager_multiple_partial_fills() { let manager = OrderManager::new(); let mut order = create_test_order("multi-partial", "BTCUSD", 1000, 50000); order.status = OrderStatus::Submitted; manager.add_order(order.clone()).await; // Five partial fills at different prices let fills = vec![ (100, 50000), (200, 50010), (150, 49990), (300, 50020), (250, 50005), ]; for (qty, price) in fills { let execution = ExecutionResult { order_id: order.id, symbol: "BTCUSD".to_string(), side: OrderSide::Buy, executed_quantity: Decimal::from(qty), execution_price: Decimal::from(price), execution_time: Utc::now(), commission: Decimal::from(5), liquidity_flag: LiquidityFlag::Maker, }; manager.process_execution(&execution).await.unwrap(); } let updated = manager.get_order(&order.id).await.unwrap(); assert_eq!(updated.status, OrderStatus::Filled); assert_eq!(updated.fill_quantity, Decimal::from(1000)); // Verify average price is weighted correctly // (100*50000 + 200*50010 + 150*49990 + 300*50020 + 250*50005) / 1000 let expected_avg = Decimal::from(50009); assert_eq!(updated.average_fill_price, Some(expected_avg)); } #[tokio::test] async fn test_order_manager_cleanup_boundary() { let manager = OrderManager::new(); // Add order exactly at the cutoff time (24 hours ago) let mut boundary_order = create_test_order("boundary", "BTCUSD", 100, 50000); boundary_order.status = OrderStatus::Filled; boundary_order.created_at = Utc::now() - Duration::hours(24); let boundary_id = boundary_order.id; manager.add_order(boundary_order).await; // Cleanup with 24 hour window manager.cleanup_old_orders(24).await; // Order should be kept (created_at > cutoff_time means keep) assert!(manager.get_order(&boundary_id).await.is_some()); } #[tokio::test] async fn test_order_manager_get_orders_with_filter() { let manager = OrderManager::new(); let statuses = vec![ OrderStatus::Created, OrderStatus::Submitted, OrderStatus::PartiallyFilled, OrderStatus::Filled, OrderStatus::Cancelled, OrderStatus::Rejected, ]; for (i, status) in statuses.iter().enumerate() { let mut order = create_test_order(&format!("filter-{}", i), "BTCUSD", 100, 50000); order.status = status.clone(); manager.add_order(order).await; } // Test filtering by each status for status in &statuses { let filtered = manager.get_orders(Some(status.clone())).await; assert_eq!(filtered.len(), 1); assert_eq!(filtered[0].status, *status); } // Test no filter returns all let all_orders = manager.get_orders(None).await; assert_eq!(all_orders.len(), 6); } // ============================================================================ // Position Manager Edge Cases // ============================================================================ #[tokio::test] async fn test_position_manager_flip_from_long_to_short() { let manager = PositionManager::new(); // Open long position let buy_exec = ExecutionResult { order_id: OrderId::new(), symbol: "BTCUSD".to_string(), side: OrderSide::Buy, executed_quantity: Decimal::from(100), execution_price: Decimal::from(50000), execution_time: Utc::now(), commission: Decimal::from(10), liquidity_flag: LiquidityFlag::Maker, }; manager.update_position(&buy_exec).unwrap(); // Sell more than position (flip to short) let sell_exec = ExecutionResult { order_id: OrderId::new(), symbol: "BTCUSD".to_string(), side: OrderSide::Sell, executed_quantity: Decimal::from(150), execution_price: Decimal::from(51000), execution_time: Utc::now(), commission: Decimal::from(15), liquidity_flag: LiquidityFlag::Taker, }; manager.update_position(&sell_exec).unwrap(); let position = manager.get_position("BTCUSD").unwrap(); assert_eq!(position.quantity, Decimal::from(-50)); // Net short 50 assert_eq!(position.avg_cost, Decimal::from(51000)); // New cost basis at flip price // Check realized P&L from closing long position // 100 shares * (51000 - 50000) = 100,000 assert_eq!(position.realized_pnl, Decimal::from(100000)); } #[tokio::test] async fn test_position_manager_flip_from_short_to_long() { let manager = PositionManager::new(); // Open short position let sell_exec = ExecutionResult { order_id: OrderId::new(), symbol: "ETHUSD".to_string(), side: OrderSide::Sell, executed_quantity: Decimal::from(100), execution_price: Decimal::from(3000), execution_time: Utc::now(), commission: Decimal::from(10), liquidity_flag: LiquidityFlag::Maker, }; manager.update_position(&sell_exec).unwrap(); // Buy more than short position (flip to long) let buy_exec = ExecutionResult { order_id: OrderId::new(), symbol: "ETHUSD".to_string(), side: OrderSide::Buy, executed_quantity: Decimal::from(150), execution_price: Decimal::from(2950), execution_time: Utc::now(), commission: Decimal::from(15), liquidity_flag: LiquidityFlag::Taker, }; manager.update_position(&buy_exec).unwrap(); let position = manager.get_position("ETHUSD").unwrap(); assert_eq!(position.quantity, Decimal::from(50)); // Net long 50 assert_eq!(position.avg_cost, Decimal::from(2950)); // New cost basis // Check realized P&L from closing short // 100 shares * (3000 - 2950) = 5,000 profit assert_eq!(position.realized_pnl, Decimal::from(5000)); } #[tokio::test] async fn test_position_manager_close_nonexistent_position() { let manager = PositionManager::new(); let result = manager.close_position("NONEXISTENT"); assert!(result.is_ok()); assert!(result.unwrap().is_none()); } #[tokio::test] async fn test_position_manager_concentration_risk_empty() { let manager = PositionManager::new(); let risk = manager.calculate_concentration_risk(); assert!(risk.is_empty()); } #[tokio::test] async fn test_position_manager_concentration_risk_calculation() { let manager = PositionManager::new(); // Add three positions with different values let symbols = vec![ ("BTCUSD", 100, 50000), // $5M ("ETHUSD", 1000, 3000), // $3M ("SOLUSD", 10000, 200), // $2M ]; for (symbol, qty, price) in symbols { let exec = ExecutionResult { order_id: OrderId::new(), symbol: symbol.to_string(), side: OrderSide::Buy, executed_quantity: Decimal::from(qty), execution_price: Decimal::from(price), execution_time: Utc::now(), commission: Decimal::ZERO, liquidity_flag: LiquidityFlag::Maker, }; manager.update_position(&exec).unwrap(); // Update market values manager .update_market_values(symbol, Decimal::from(price)) .unwrap(); } let risk = manager.calculate_concentration_risk(); // Total portfolio = $10M // BTCUSD: 50% concentration // ETHUSD: 30% concentration // SOLUSD: 20% concentration assert_eq!(risk.len(), 3); assert!((risk["BTCUSD"] - 0.5).abs() < 0.01); assert!((risk["ETHUSD"] - 0.3).abs() < 0.01); assert!((risk["SOLUSD"] - 0.2).abs() < 0.01); } #[tokio::test] async fn test_position_manager_update_market_values_nonexistent() { let manager = PositionManager::new(); let result = manager.update_market_values("NONEXISTENT", Decimal::from(100)); assert!(result.is_err()); assert!(result.unwrap_err().contains("not found")); } // ============================================================================ // Account Manager Edge Cases // ============================================================================ #[tokio::test] async fn test_account_manager_nonexistent_account() { let manager = AccountManager::new(); let result = manager.get_account_info("NONEXISTENT").await; assert!(result.is_err()); assert!(result.unwrap_err().contains("not found")); } #[tokio::test] async fn test_account_manager_insufficient_buying_power() { let manager = AccountManager::new(); // Create order that exceeds buying power let order = TradingOrder { id: OrderId::new(), symbol: "BTCUSD".to_string(), side: OrderSide::Buy, order_type: OrderType::Limit, quantity: Decimal::from(10), // 10 BTC price: Decimal::from(100000), // at $100k each = $1M total 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, }; let result = manager.check_buying_power(&order).await; assert!(result.is_err()); assert!(result.unwrap_err().contains("Insufficient buying power")); } #[tokio::test] async fn test_account_manager_sell_order_no_buying_power_check() { let manager = AccountManager::new(); // Sell orders shouldn't require buying power check (for long positions) let order = TradingOrder { id: OrderId::new(), symbol: "BTCUSD".to_string(), side: OrderSide::Sell, order_type: OrderType::Limit, quantity: Decimal::from(100), price: Decimal::from(100000), 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, }; let result = manager.check_buying_power(&order).await; assert!(result.is_ok()); // Sell orders pass } #[tokio::test] async fn test_account_manager_update_from_execution_buy() { let manager = AccountManager::new(); let execution = ExecutionResult { order_id: OrderId::new(), symbol: "BTCUSD".to_string(), side: OrderSide::Buy, executed_quantity: Decimal::from(1), execution_price: Decimal::from(50000), execution_time: Utc::now(), commission: Decimal::from(25), liquidity_flag: LiquidityFlag::Maker, }; let result = manager.update_from_execution(&execution).await; assert!(result.is_ok()); let account = manager.get_account_info("DEMO_ACCOUNT").await.unwrap(); // Buy: cash -= (1 * 50000) + 25 = 50025 // Initial: $50,000 - $50,025 = -$25 assert_eq!( account.cash_balance, Decimal::from(50000) - Decimal::from(50025) ); } #[tokio::test] async fn test_account_manager_margin_call_check() { let manager = AccountManager::new(); // Get current account let mut account = manager.get_account_info("DEMO_ACCOUNT").await.unwrap(); // Set up account with margin call condition account.total_value = Decimal::from(80000); account.maintenance_margin = Decimal::from(100000); // Margin > value manager.update_account_info(account).await.unwrap(); let is_margin_call = manager.check_margin_call("DEMO_ACCOUNT").await.unwrap(); assert!(is_margin_call); } #[tokio::test] async fn test_account_manager_no_margin_call() { let manager = AccountManager::new(); // Default demo account has no margin requirement let is_margin_call = manager.check_margin_call("DEMO_ACCOUNT").await.unwrap(); assert!(!is_margin_call); } #[tokio::test] async fn test_account_manager_add_duplicate_account() { let manager = AccountManager::new(); let account = AccountInfo { account_id: "TEST_ACCOUNT".to_string(), total_value: Decimal::from(50000), cash_balance: Decimal::from(50000), buying_power: Decimal::from(50000), maintenance_margin: Decimal::ZERO, day_trading_buying_power: Decimal::from(100000), }; // Add once let result1 = manager.add_account(account.clone()).await; assert!(result1.is_ok()); // Try to add same account again let result2 = manager.add_account(account).await; assert!(result2.is_err()); assert!(result2.unwrap_err().contains("already exists")); } #[tokio::test] async fn test_account_manager_remove_nonexistent_account() { let manager = AccountManager::new(); let result = manager.remove_account("NONEXISTENT").await; assert!(result.is_err()); assert!(result.unwrap_err().contains("not found")); } #[tokio::test] async fn test_account_manager_remove_demo_account_protected() { let manager = AccountManager::new(); // Demo account should be protected from deletion let result = manager.remove_account("DEMO_ACCOUNT").await; assert!(result.is_err()); assert!(result.unwrap_err().contains("Cannot remove")); } // ============================================================================ // Helper Functions // ============================================================================ fn create_test_order(id: &str, symbol: &str, quantity: i64, price: i64) -> TradingOrder { TradingOrder { id: id.to_string().into(), symbol: symbol.to_string(), side: OrderSide::Buy, order_type: OrderType::Limit, quantity: Decimal::from(quantity), price: Decimal::from(price), time_in_force: TimeInForce::GoodTillCancel, 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, } }