//! Integration tests for Trading Agent Service gRPC methods //! //! Tests all 14 gRPC endpoints end-to-end with database persistence. use sqlx::PgPool; use tonic::Request; use trading_agent_service::proto::trading_agent::trading_agent_service_server::TradingAgentService; use trading_agent_service::proto::trading_agent::*; use trading_agent_service::service::TradingAgentServiceImpl; // Don't import the internal UniverseCriteria - use the proto-generated one from trading_agent::* // which is already imported above /// Helper to create test database pool async fn create_test_pool() -> PgPool { let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| { "postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string() }); PgPool::connect(&database_url) .await .expect("Failed to connect to test database") } /// Helper to create service instance async fn create_service(pool: PgPool) -> TradingAgentServiceImpl { // Create regime orchestrator let orchestrator = ml::regime::orchestrator::RegimeOrchestrator::new(pool.clone()) .await .expect("Failed to create RegimeOrchestrator"); let orchestrator = std::sync::Arc::new(tokio::sync::Mutex::new(orchestrator)); TradingAgentServiceImpl::new(pool, orchestrator) } // ============================================================================== // Universe Management Tests // ============================================================================== #[tokio::test] async fn test_select_universe_success() { let pool = create_test_pool().await; let service = create_service(pool).await; let request = Request::new(SelectUniverseRequest { criteria: Some(UniverseCriteria { min_liquidity_score: 0.5, min_volatility: 0.0, max_volatility: 0.8, allowed_types: vec![InstrumentType::Futures as i32], exchanges: vec!["CME".to_string()], min_ml_confidence: 0.0, }), max_instruments: Some(10), force_refresh: true, }); let response = service.select_universe(request).await; assert!(response.is_ok(), "SelectUniverse should succeed"); let response = response.unwrap().into_inner(); assert!( !response.instruments.is_empty(), "Should return instruments" ); assert!( !response.universe_id.is_empty(), "Should return universe ID" ); assert!(response.metrics.is_some(), "Should return metrics"); } #[tokio::test] async fn test_get_universe_success() { let pool = create_test_pool().await; let service = create_service(pool.clone()).await; // First create a universe let select_request = Request::new(SelectUniverseRequest { criteria: Some(UniverseCriteria { min_liquidity_score: 0.5, min_volatility: 0.0, max_volatility: 0.8, allowed_types: vec![InstrumentType::Futures as i32], exchanges: vec!["CME".to_string()], min_ml_confidence: 0.0, }), max_instruments: Some(10), force_refresh: true, }); let select_response = service .select_universe(select_request) .await .expect("Failed to select universe") .into_inner(); let universe_id = select_response.universe_id; // Now get the universe let get_request = Request::new(GetUniverseRequest { universe_id: Some(universe_id.clone()), }); let response = service.get_universe(get_request).await; assert!(response.is_ok(), "GetUniverse should succeed"); let response = response.unwrap().into_inner(); assert_eq!( response.universe_id, universe_id, "Should return same universe ID" ); assert!( !response.instruments.is_empty(), "Should return instruments" ); assert!(response.criteria.is_some(), "Should return criteria"); } #[tokio::test] async fn test_get_universe_not_found() { let pool = create_test_pool().await; let service = create_service(pool).await; let request = Request::new(GetUniverseRequest { universe_id: Some("nonexistent_universe_123".to_string()), }); let response = service.get_universe(request).await; assert!( response.is_err(), "GetUniverse should fail for nonexistent ID" ); } #[tokio::test] async fn test_update_universe_criteria_success() { let pool = create_test_pool().await; let service = create_service(pool.clone()).await; // First create a universe let select_request = Request::new(SelectUniverseRequest { criteria: Some(UniverseCriteria { min_liquidity_score: 0.5, min_volatility: 0.0, max_volatility: 0.8, allowed_types: vec![InstrumentType::Futures as i32], exchanges: vec!["CME".to_string()], min_ml_confidence: 0.0, }), max_instruments: Some(10), force_refresh: true, }); let _select_response = service .select_universe(select_request) .await .expect("Failed to select universe") .into_inner(); // Update criteria let update_request = Request::new(UpdateUniverseCriteriaRequest { criteria: Some(UniverseCriteria { min_liquidity_score: 0.7, // Increased threshold min_volatility: 0.0, max_volatility: 0.6, allowed_types: vec![InstrumentType::Futures as i32], exchanges: vec!["CME".to_string()], min_ml_confidence: 0.0, }), }); let response = service.update_universe_criteria(update_request).await; assert!(response.is_ok(), "UpdateUniverseCriteria should succeed"); let response = response.unwrap().into_inner(); assert!(response.success, "Update should succeed"); assert!( !response.universe_id.is_empty(), "Should return new universe ID" ); } // ============================================================================== // Asset Selection Tests // ============================================================================== #[tokio::test] async fn test_get_selected_assets_placeholder() { let pool = create_test_pool().await; let service = create_service(pool).await; let request = Request::new(GetSelectedAssetsRequest { universe_id: Some("test_universe_123".to_string()), }); let response = service.get_selected_assets(request).await; assert!( response.is_ok(), "GetSelectedAssets should succeed (placeholder)" ); } // ============================================================================== // Portfolio Allocation Tests // ============================================================================== #[tokio::test] async fn test_get_allocation_placeholder() { let pool = create_test_pool().await; let service = create_service(pool).await; let request = Request::new(GetAllocationRequest { allocation_id: Some("test_allocation_123".to_string()), }); let response = service.get_allocation(request).await; assert!( response.is_ok(), "GetAllocation should succeed (placeholder)" ); } #[tokio::test] async fn test_rebalance_portfolio_placeholder() { let pool = create_test_pool().await; let service = create_service(pool).await; let request = Request::new(RebalancePortfolioRequest { allocation_id: "test_allocation_123".to_string(), rebalance_threshold: 0.05, force_rebalance: false, }); let response = service.rebalance_portfolio(request).await; assert!( response.is_ok(), "RebalancePortfolio should succeed (placeholder)" ); } // ============================================================================== // Order Generation Tests // ============================================================================== #[tokio::test] async fn test_generate_orders_placeholder() { let pool = create_test_pool().await; let service = create_service(pool).await; let request = Request::new(GenerateOrdersRequest { allocation_id: "test_allocation_123".to_string(), ml_signals: vec![], strategy: Some(OrderGenerationStrategy { mode: OrderGenerationMode::Aggressive as i32, slippage_tolerance: 0.01, use_limit_orders: false, limit_price_offset: 0.0, }), }); let response = service.generate_orders(request).await; assert!( response.is_ok(), "GenerateOrders should succeed (placeholder)" ); } #[tokio::test] async fn test_submit_agent_orders_placeholder() { let pool = create_test_pool().await; let service = create_service(pool).await; let request = Request::new(SubmitAgentOrdersRequest { order_batch_id: "test_batch_123".to_string(), orders: vec![], dry_run: true, }); let response = service.submit_agent_orders(request).await; assert!( response.is_ok(), "SubmitAgentOrders should succeed (placeholder)" ); } // ============================================================================== // Strategy Coordination Tests // ============================================================================== #[tokio::test] async fn test_register_strategy_success() { let pool = create_test_pool().await; let service = create_service(pool).await; let mut parameters = std::collections::HashMap::new(); parameters.insert("lookback_period".to_string(), "20".to_string()); parameters.insert("threshold".to_string(), "0.02".to_string()); let request = Request::new(RegisterStrategyRequest { strategy_name: format!("test_strategy_{}", uuid::Uuid::new_v4()), strategy_type: StrategyType::MlEnsemble as i32, config: Some(StrategyConfig { parameters, target_symbols: vec!["ES.FUT".to_string(), "NQ.FUT".to_string()], max_capital_pct: 0.25, }), auto_enable: true, }); let response = service.register_strategy(request).await; assert!(response.is_ok(), "RegisterStrategy should succeed"); let response = response.unwrap().into_inner(); assert!(response.success, "Registration should succeed"); assert!( !response.strategy_id.is_empty(), "Should return strategy ID" ); } #[tokio::test] async fn test_register_strategy_duplicate_name() { let pool = create_test_pool().await; let service = create_service(pool.clone()).await; let strategy_name = format!("duplicate_test_{}", uuid::Uuid::new_v4()); let mut parameters = std::collections::HashMap::new(); parameters.insert("test_param".to_string(), "value".to_string()); // Register first time let request1 = Request::new(RegisterStrategyRequest { strategy_name: strategy_name.clone(), strategy_type: StrategyType::MeanReversion as i32, config: Some(StrategyConfig { parameters: parameters.clone(), target_symbols: vec!["ES.FUT".to_string()], max_capital_pct: 0.20, }), auto_enable: false, }); service .register_strategy(request1) .await .expect("First registration should succeed"); // Try to register with same name let request2 = Request::new(RegisterStrategyRequest { strategy_name: strategy_name.clone(), strategy_type: StrategyType::Momentum as i32, config: Some(StrategyConfig { parameters: parameters.clone(), target_symbols: vec!["NQ.FUT".to_string()], max_capital_pct: 0.30, }), auto_enable: false, }); let response = service.register_strategy(request2).await; assert!(response.is_err(), "Duplicate name should fail"); } #[tokio::test] async fn test_list_strategies_success() { let pool = create_test_pool().await; let service = create_service(pool.clone()).await; // Register a test strategy first let strategy_name = format!("list_test_{}", uuid::Uuid::new_v4()); let mut parameters = std::collections::HashMap::new(); parameters.insert("param1".to_string(), "value1".to_string()); let register_request = Request::new(RegisterStrategyRequest { strategy_name: strategy_name.clone(), strategy_type: StrategyType::MlEnsemble as i32, config: Some(StrategyConfig { parameters, target_symbols: vec!["ES.FUT".to_string()], max_capital_pct: 0.25, }), auto_enable: true, }); service .register_strategy(register_request) .await .expect("Failed to register test strategy"); // List strategies let list_request = Request::new(ListStrategiesRequest { status_filter: None, }); let response = service.list_strategies(list_request).await; assert!(response.is_ok(), "ListStrategies should succeed"); let response = response.unwrap().into_inner(); assert!( !response.strategies.is_empty(), "Should return at least one strategy" ); // Verify our strategy is in the list let found = response .strategies .iter() .any(|s| s.strategy_name == strategy_name); assert!(found, "Should find registered strategy in list"); } #[tokio::test] async fn test_update_strategy_status_success() { let pool = create_test_pool().await; let service = create_service(pool.clone()).await; // Register a test strategy first let strategy_name = format!("update_test_{}", uuid::Uuid::new_v4()); let mut parameters = std::collections::HashMap::new(); parameters.insert("param1".to_string(), "value1".to_string()); let register_request = Request::new(RegisterStrategyRequest { strategy_name: strategy_name.clone(), strategy_type: StrategyType::Momentum as i32, config: Some(StrategyConfig { parameters, target_symbols: vec!["NQ.FUT".to_string()], max_capital_pct: 0.30, }), auto_enable: true, }); let register_response = service .register_strategy(register_request) .await .expect("Failed to register test strategy") .into_inner(); let strategy_id = register_response.strategy_id; // Update status to paused let update_request = Request::new(UpdateStrategyStatusRequest { strategy_id: strategy_id.clone(), new_status: StrategyStatus::Paused as i32, reason: Some("Testing status update".to_string()), }); let response = service.update_strategy_status(update_request).await; assert!(response.is_ok(), "UpdateStrategyStatus should succeed"); let response = response.unwrap().into_inner(); assert!(response.success, "Status update should succeed"); assert!( response.updated_strategy.is_some(), "Should return updated strategy" ); } #[tokio::test] async fn test_update_strategy_status_not_found() { let pool = create_test_pool().await; let service = create_service(pool).await; let request = Request::new(UpdateStrategyStatusRequest { strategy_id: "nonexistent_strategy_id".to_string(), new_status: StrategyStatus::Paused as i32, reason: None, }); let response = service.update_strategy_status(request).await; assert!( response.is_err(), "UpdateStrategyStatus should fail for nonexistent ID" ); } // ============================================================================== // Agent Monitoring Tests // ============================================================================== #[tokio::test] async fn test_get_agent_status_success() { let pool = create_test_pool().await; let service = create_service(pool).await; let request = Request::new(GetAgentStatusRequest { include_performance: true, include_positions: true, }); let response = service.get_agent_status(request).await; assert!(response.is_ok(), "GetAgentStatus should succeed"); let response = response.unwrap().into_inner(); assert!(response.status.is_some(), "Should return agent status"); } #[tokio::test] async fn test_stream_agent_activity_success() { let pool = create_test_pool().await; let service = create_service(pool).await; let request = Request::new(StreamAgentActivityRequest { activity_types: vec![ActivityType::UniverseSelection as i32], }); let response = service.stream_agent_activity(request).await; assert!(response.is_ok(), "StreamAgentActivity should succeed"); // Verify we get a stream let _stream = response.unwrap().into_inner(); // For now, just verify stream exists (will be closed immediately in placeholder) // In full implementation, this would verify event streaming } #[tokio::test] async fn test_get_agent_performance_success() { let pool = create_test_pool().await; let service = create_service(pool).await; let request = Request::new(GetAgentPerformanceRequest { start_time: Some(chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0) - 86400_000_000_000), // 1 day ago end_time: Some(chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0)), include_strategy_breakdown: true, }); let response = service.get_agent_performance(request).await; assert!(response.is_ok(), "GetAgentPerformance should succeed"); let response = response.unwrap().into_inner(); assert!( response.metrics.is_some(), "Should return performance metrics" ); } // ============================================================================== // Health Check Test // ============================================================================== #[tokio::test] async fn test_health_check_success() { let pool = create_test_pool().await; let service = create_service(pool).await; let request = Request::new(HealthCheckRequest {}); let response = service.health_check(request).await; assert!(response.is_ok(), "HealthCheck should succeed"); let response = response.unwrap().into_inner(); assert!(response.healthy, "Service should be healthy"); assert_eq!(response.message, "Trading Agent Service is healthy"); }