//! Integration tests for strategy coordination module use std::collections::HashMap; use trading_agent_service::strategies::{ StrategyConfig, StrategyCoordinator, StrategyStatus, StrategyType, }; fn get_test_database_url() -> String { std::env::var("DATABASE_URL").unwrap_or_else(|_| { "postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string() }) } async fn setup_test_db() -> sqlx::PgPool { let pool = sqlx::PgPool::connect(&get_test_database_url()) .await .expect("Failed to connect to database"); // Run migrations sqlx::migrate!("../../migrations") .run(&pool) .await .expect("Failed to run migrations"); pool } #[tokio::test] async fn test_register_strategy() { let pool = setup_test_db().await; let coordinator = StrategyCoordinator::new(pool); // Create a strategy config let mut parameters = HashMap::new(); parameters.insert("risk_limit".to_string(), 0.05); parameters.insert("rebalance_threshold".to_string(), 0.02); let config = StrategyConfig { strategy_id: String::new(), // Will be generated strategy_name: "test_equal_weight_001".to_string(), strategy_type: StrategyType::EqualWeight, parameters, status: StrategyStatus::Active, created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), }; let result = coordinator.register_strategy(config).await; assert!(result.is_ok(), "Strategy registration should succeed"); let strategy_id = result.expect("Strategy ID should be returned"); assert!(!strategy_id.is_empty(), "Strategy ID should not be empty"); // Verify it's a valid UUID assert!( uuid::Uuid::parse_str(&strategy_id).is_ok(), "Strategy ID should be valid UUID" ); } #[tokio::test] async fn test_register_duplicate_strategy_name() { let pool = setup_test_db().await; let coordinator = StrategyCoordinator::new(pool); let mut parameters = HashMap::new(); parameters.insert("risk_limit".to_string(), 0.05); let config1 = StrategyConfig { strategy_id: String::new(), strategy_name: "duplicate_test_strategy".to_string(), strategy_type: StrategyType::RiskParity, parameters: parameters.clone(), status: StrategyStatus::Active, created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), }; // Register first strategy let result1 = coordinator.register_strategy(config1).await; assert!(result1.is_ok()); // Try to register duplicate name let config2 = StrategyConfig { strategy_id: String::new(), strategy_name: "duplicate_test_strategy".to_string(), strategy_type: StrategyType::MLOptimized, parameters, status: StrategyStatus::Active, created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), }; let result2 = coordinator.register_strategy(config2).await; assert!(result2.is_err(), "Duplicate strategy name should fail"); } #[tokio::test] async fn test_list_strategies() { let pool = setup_test_db().await; let coordinator = StrategyCoordinator::new(pool); // Register multiple strategies for i in 0..3 { let mut parameters = HashMap::new(); parameters.insert("risk_limit".to_string(), 0.05); let config = StrategyConfig { strategy_id: String::new(), strategy_name: format!("test_strategy_list_{}", i), strategy_type: StrategyType::EqualWeight, parameters, status: StrategyStatus::Active, created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), }; coordinator .register_strategy(config) .await .expect("Failed to register strategy"); } // List strategies let result = coordinator.list_strategies().await; assert!(result.is_ok(), "List strategies should succeed"); let strategies = result.expect("Strategies list should be returned"); assert!(strategies.len() >= 3, "Should have at least 3 strategies"); // Verify all strategies have required fields for strategy in &strategies { assert!(!strategy.strategy_id.is_empty()); assert!(!strategy.strategy_name.is_empty()); } } #[tokio::test] async fn test_update_strategy_status() { let pool = setup_test_db().await; let coordinator = StrategyCoordinator::new(pool); // Register a strategy let mut parameters = HashMap::new(); parameters.insert("risk_limit".to_string(), 0.05); let config = StrategyConfig { strategy_id: String::new(), strategy_name: "test_status_update".to_string(), strategy_type: StrategyType::Momentum, parameters, status: StrategyStatus::Active, created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), }; let strategy_id = coordinator .register_strategy(config) .await .expect("Failed to register"); // Update status to Paused let result = coordinator .update_status(&strategy_id, StrategyStatus::Paused) .await; assert!(result.is_ok(), "Status update should succeed"); // Verify status changed let strategies = coordinator.list_strategies().await.expect("Failed to list"); let updated_strategy = strategies.iter().find(|s| s.strategy_id == strategy_id); assert!(updated_strategy.is_some()); assert_eq!(updated_strategy.unwrap().status, StrategyStatus::Paused); } #[tokio::test] async fn test_update_nonexistent_strategy_status() { let pool = setup_test_db().await; let coordinator = StrategyCoordinator::new(pool); // Try to update non-existent strategy let fake_id = uuid::Uuid::new_v4().to_string(); let result = coordinator .update_status(&fake_id, StrategyStatus::Stopped) .await; assert!( result.is_err(), "Updating non-existent strategy should fail" ); } #[tokio::test] async fn test_get_active_strategies() { let pool = setup_test_db().await; let coordinator = StrategyCoordinator::new(pool); // Register strategies with different statuses let statuses = [ StrategyStatus::Active, StrategyStatus::Paused, StrategyStatus::Active, StrategyStatus::Stopped, StrategyStatus::Active, ]; for (i, status) in statuses.iter().enumerate() { let mut parameters = HashMap::new(); parameters.insert("risk_limit".to_string(), 0.05); let config = StrategyConfig { strategy_id: String::new(), strategy_name: format!("test_active_filter_{}", i), strategy_type: StrategyType::EqualWeight, parameters, status: status.clone(), created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), }; coordinator .register_strategy(config) .await .expect("Failed to register"); } // Get only active strategies let result = coordinator.get_active_strategies().await; assert!(result.is_ok(), "Get active strategies should succeed"); let active_strategies = result.expect("Active strategies should be returned"); // Should have at least 3 active strategies from this test let test_active_count = active_strategies .iter() .filter(|s| s.strategy_name.starts_with("test_active_filter_")) .count(); assert_eq!( test_active_count, 3, "Should have exactly 3 active strategies from this test" ); // All returned strategies should be active for strategy in &active_strategies { assert_eq!(strategy.status, StrategyStatus::Active); } } #[tokio::test] async fn test_get_strategy_by_id() { let pool = setup_test_db().await; let coordinator = StrategyCoordinator::new(pool); // Register a strategy let mut parameters = HashMap::new(); parameters.insert("risk_limit".to_string(), 0.08); parameters.insert("lookback_period".to_string(), 20.0); let config = StrategyConfig { strategy_id: String::new(), strategy_name: "test_get_by_id".to_string(), strategy_type: StrategyType::MeanReversion, parameters: parameters.clone(), status: StrategyStatus::Active, created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), }; let strategy_id = coordinator .register_strategy(config) .await .expect("Failed to register"); // Get strategy by ID let result = coordinator.get_strategy(&strategy_id).await; assert!(result.is_ok(), "Get strategy by ID should succeed"); let strategy = result.expect("Strategy should be returned"); assert_eq!(strategy.strategy_id, strategy_id); assert_eq!(strategy.strategy_name, "test_get_by_id"); assert_eq!(strategy.strategy_type, StrategyType::MeanReversion); assert_eq!(strategy.parameters.get("risk_limit"), Some(&0.08)); } #[tokio::test] async fn test_get_nonexistent_strategy() { let pool = setup_test_db().await; let coordinator = StrategyCoordinator::new(pool); let fake_id = uuid::Uuid::new_v4().to_string(); let result = coordinator.get_strategy(&fake_id).await; assert!(result.is_err(), "Getting non-existent strategy should fail"); } #[tokio::test] async fn test_strategy_performance() { let pool = setup_test_db().await; let coordinator = StrategyCoordinator::new(pool); let mut parameters = HashMap::new(); parameters.insert("risk_limit".to_string(), 0.05); let config = StrategyConfig { strategy_id: String::new(), strategy_name: "test_performance".to_string(), strategy_type: StrategyType::EqualWeight, parameters, status: StrategyStatus::Active, created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), }; let start = std::time::Instant::now(); let result = coordinator.register_strategy(config).await; let duration = start.elapsed(); assert!(result.is_ok(), "Strategy registration should succeed"); // Performance target: < 50ms assert!( duration.as_millis() < 50, "Strategy registration took {}ms (target: <50ms)", duration.as_millis() ); println!( "Strategy registration completed in {}ms", duration.as_millis() ); } #[tokio::test] async fn test_list_strategies_performance() { let pool = setup_test_db().await; let coordinator = StrategyCoordinator::new(pool); let start = std::time::Instant::now(); let result = coordinator.list_strategies().await; let duration = start.elapsed(); assert!(result.is_ok(), "List strategies should succeed"); // Performance target: < 50ms assert!( duration.as_millis() < 50, "List strategies took {}ms (target: <50ms)", duration.as_millis() ); println!("List strategies completed in {}ms", duration.as_millis()); } #[tokio::test] async fn test_update_status_performance() { let pool = setup_test_db().await; let coordinator = StrategyCoordinator::new(pool); // Register a strategy first let mut parameters = HashMap::new(); parameters.insert("risk_limit".to_string(), 0.05); let config = StrategyConfig { strategy_id: String::new(), strategy_name: "test_update_performance".to_string(), strategy_type: StrategyType::RiskParity, parameters, status: StrategyStatus::Active, created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), }; let strategy_id = coordinator .register_strategy(config) .await .expect("Failed to register"); // Measure update performance let start = std::time::Instant::now(); let result = coordinator .update_status(&strategy_id, StrategyStatus::Paused) .await; let duration = start.elapsed(); assert!(result.is_ok(), "Status update should succeed"); // Performance target: < 50ms assert!( duration.as_millis() < 50, "Status update took {}ms (target: <50ms)", duration.as_millis() ); println!("Status update completed in {}ms", duration.as_millis()); } #[tokio::test] async fn test_all_strategy_types() { let pool = setup_test_db().await; let coordinator = StrategyCoordinator::new(pool); let strategy_types = [ StrategyType::EqualWeight, StrategyType::RiskParity, StrategyType::MLOptimized, StrategyType::MeanVariance, StrategyType::Momentum, StrategyType::MeanReversion, ]; for (i, strategy_type) in strategy_types.iter().enumerate() { let mut parameters = HashMap::new(); parameters.insert("risk_limit".to_string(), 0.05); let config = StrategyConfig { strategy_id: String::new(), strategy_name: format!("test_type_{}_{:?}", i, strategy_type), strategy_type: strategy_type.clone(), parameters, status: StrategyStatus::Active, created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), }; let result = coordinator.register_strategy(config).await; assert!( result.is_ok(), "Should be able to register {:?} strategy", strategy_type ); } } #[tokio::test] async fn test_empty_parameters() { let pool = setup_test_db().await; let coordinator = StrategyCoordinator::new(pool); let config = StrategyConfig { strategy_id: String::new(), strategy_name: "test_empty_params".to_string(), strategy_type: StrategyType::EqualWeight, parameters: HashMap::new(), // Empty parameters status: StrategyStatus::Active, created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), }; let result = coordinator.register_strategy(config).await; assert!(result.is_ok(), "Empty parameters should be allowed"); } #[tokio::test] async fn test_complex_parameters() { let pool = setup_test_db().await; let coordinator = StrategyCoordinator::new(pool); let mut parameters = HashMap::new(); parameters.insert("risk_limit".to_string(), 0.05); parameters.insert("rebalance_threshold".to_string(), 0.02); parameters.insert("lookback_period".to_string(), 20.0); parameters.insert("volatility_target".to_string(), 0.15); parameters.insert("max_position_size".to_string(), 0.10); let config = StrategyConfig { strategy_id: String::new(), strategy_name: "test_complex_params".to_string(), strategy_type: StrategyType::MLOptimized, parameters: parameters.clone(), status: StrategyStatus::Active, created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), }; let strategy_id = coordinator .register_strategy(config) .await .expect("Failed to register"); // Verify parameters were stored correctly let strategy = coordinator .get_strategy(&strategy_id) .await .expect("Failed to get strategy"); assert_eq!(strategy.parameters.len(), 5); assert_eq!(strategy.parameters.get("risk_limit"), Some(&0.05)); assert_eq!(strategy.parameters.get("lookback_period"), Some(&20.0)); }