"""Tests for autopilot API endpoints.""" import pytest from unittest.mock import Mock, patch, MagicMock, AsyncMock from fastapi.testclient import TestClient from backend.main import app @pytest.fixture def client(): """Test client fixture.""" return TestClient(app) @pytest.fixture def mock_autopilot(): """Mock autopilot instance.""" autopilot = Mock() autopilot.symbol = "BTC/USD" autopilot.is_running = False autopilot.get_status.return_value = { "symbol": "BTC/USD", "running": False, "interval": 60.0, "has_market_data": True, "market_data_length": 100, "headlines_count": 5, "last_sentiment_score": 0.5, "last_pattern": "head_and_shoulders", "last_signal": None, } autopilot.last_signal = None autopilot.analyze_once.return_value = None return autopilot @pytest.fixture def mock_intelligent_autopilot(): """Mock intelligent autopilot instance.""" autopilot = Mock() autopilot.symbol = "BTC/USD" autopilot.is_running = False autopilot.enable_auto_execution = False autopilot.get_status.return_value = { "symbol": "BTC/USD", "timeframe": "1h", "running": False, "selected_strategy": None, "trades_today": 0, "max_trades_per_day": 10, "min_confidence_threshold": 0.75, "enable_auto_execution": False, "last_analysis": None, "model_info": {}, } return autopilot class TestUnifiedAutopilotEndpoints: """Tests for unified autopilot endpoints.""" @patch('backend.api.autopilot.get_autopilot_mode_info') def test_get_modes(self, mock_get_mode_info, client): """Test getting autopilot mode information.""" mock_get_mode_info.return_value = { "modes": { "pattern": {"name": "Pattern-Based Autopilot"}, "intelligent": {"name": "ML-Based Autopilot"}, }, "comparison": {}, } response = client.get("/api/autopilot/modes") assert response.status_code == 200 data = response.json() assert "modes" in data assert "pattern" in data["modes"] assert "intelligent" in data["modes"] @patch('backend.api.autopilot.get_autopilot') @patch('backend.api.autopilot.run_autopilot_loop') def test_start_unified_pattern_mode( self, mock_run_loop, mock_get_autopilot, client, mock_autopilot ): """Test starting unified autopilot in pattern mode.""" mock_get_autopilot.return_value = mock_autopilot response = client.post( "/api/autopilot/start-unified", json={ "symbol": "BTC/USD", "mode": "pattern", "auto_execute": False, "interval": 60.0, "pattern_order": 5, "auto_fetch_news": True, }, ) assert response.status_code == 200 data = response.json() assert data["status"] == "started" assert data["mode"] == "pattern" assert data["symbol"] == "BTC/USD" assert data["auto_execute"] is False mock_get_autopilot.assert_called_once() @patch('backend.api.autopilot.get_intelligent_autopilot') def test_start_unified_intelligent_mode( self, mock_get_intelligent, client, mock_intelligent_autopilot ): """Test starting unified autopilot in intelligent mode.""" mock_get_intelligent.return_value = mock_intelligent_autopilot response = client.post( "/api/autopilot/start-unified", json={ "symbol": "BTC/USD", "mode": "intelligent", "auto_execute": True, "exchange_id": 1, "timeframe": "1h", "interval": 60.0, "paper_trading": True, }, ) assert response.status_code == 200 data = response.json() assert data["status"] == "started" assert data["mode"] == "intelligent" assert data["symbol"] == "BTC/USD" assert data["auto_execute"] is True assert mock_intelligent_autopilot.enable_auto_execution is True mock_get_intelligent.assert_called_once() def test_start_unified_invalid_mode(self, client): """Test starting unified autopilot with invalid mode.""" response = client.post( "/api/autopilot/start-unified", json={ "symbol": "BTC/USD", "mode": "invalid_mode", "auto_execute": False, }, ) assert response.status_code == 400 assert "Invalid mode" in response.json()["detail"] @patch('backend.api.autopilot.get_autopilot') def test_stop_unified_pattern_mode( self, mock_get_autopilot, client, mock_autopilot ): """Test stopping unified autopilot in pattern mode.""" mock_get_autopilot.return_value = mock_autopilot response = client.post( "/api/autopilot/stop-unified?symbol=BTC/USD&mode=pattern" ) assert response.status_code == 200 data = response.json() assert data["status"] == "stopped" assert data["symbol"] == "BTC/USD" assert data["mode"] == "pattern" mock_autopilot.stop.assert_called_once() @patch('backend.api.autopilot.get_intelligent_autopilot') def test_stop_unified_intelligent_mode( self, mock_get_intelligent, client, mock_intelligent_autopilot ): """Test stopping unified autopilot in intelligent mode.""" mock_get_intelligent.return_value = mock_intelligent_autopilot response = client.post( "/api/autopilot/stop-unified?symbol=BTC/USD&mode=intelligent&timeframe=1h" ) assert response.status_code == 200 data = response.json() assert data["status"] == "stopped" assert data["symbol"] == "BTC/USD" assert data["mode"] == "intelligent" mock_intelligent_autopilot.stop.assert_called_once() def test_stop_unified_invalid_mode(self, client): """Test stopping unified autopilot with invalid mode.""" response = client.post( "/api/autopilot/stop-unified?symbol=BTC/USD&mode=invalid_mode" ) assert response.status_code == 400 assert "Invalid mode" in response.json()["detail"] @patch('backend.api.autopilot.get_autopilot') def test_get_unified_status_pattern_mode( self, mock_get_autopilot, client, mock_autopilot ): """Test getting unified autopilot status in pattern mode.""" mock_get_autopilot.return_value = mock_autopilot response = client.get( "/api/autopilot/status-unified/BTC/USD?mode=pattern" ) assert response.status_code == 200 data = response.json() assert data["symbol"] == "BTC/USD" assert data["mode"] == "pattern" assert "running" in data @patch('backend.api.autopilot.get_intelligent_autopilot') def test_get_unified_status_intelligent_mode( self, mock_get_intelligent, client, mock_intelligent_autopilot ): """Test getting unified autopilot status in intelligent mode.""" mock_get_intelligent.return_value = mock_intelligent_autopilot response = client.get( "/api/autopilot/status-unified/BTC/USD?mode=intelligent&timeframe=1h" ) assert response.status_code == 200 data = response.json() assert data["symbol"] == "BTC/USD" assert data["mode"] == "intelligent" assert "running" in data def test_get_unified_status_invalid_mode(self, client): """Test getting unified autopilot status with invalid mode.""" response = client.get( "/api/autopilot/status-unified/BTC/USD?mode=invalid_mode" ) assert response.status_code == 400 assert "Invalid mode" in response.json()["detail"] class TestModeSelection: """Tests for mode selection logic.""" @patch('backend.api.autopilot.get_autopilot_mode_info') def test_mode_info_structure(self, mock_get_mode_info): """Test that mode info has correct structure.""" from src.autopilot import get_autopilot_mode_info mode_info = get_autopilot_mode_info() assert "modes" in mode_info assert "pattern" in mode_info["modes"] assert "intelligent" in mode_info["modes"] assert "comparison" in mode_info # Check pattern mode structure pattern = mode_info["modes"]["pattern"] assert "name" in pattern assert "description" in pattern assert "how_it_works" in pattern assert "best_for" in pattern assert "tradeoffs" in pattern assert "features" in pattern assert "requirements" in pattern # Check intelligent mode structure intelligent = mode_info["modes"]["intelligent"] assert "name" in intelligent assert "description" in intelligent assert "how_it_works" in intelligent assert "best_for" in intelligent assert "tradeoffs" in intelligent assert "features" in intelligent assert "requirements" in intelligent # Check comparison structure comparison = mode_info["comparison"] assert "transparency" in comparison assert "adaptability" in comparison assert "setup_time" in comparison assert "resource_usage" in comparison class TestAutoExecution: """Tests for auto-execution functionality.""" @patch('backend.api.autopilot.get_intelligent_autopilot') def test_auto_execute_enabled( self, mock_get_intelligent, client, mock_intelligent_autopilot ): """Test that auto-execute is set when enabled.""" mock_get_intelligent.return_value = mock_intelligent_autopilot response = client.post( "/api/autopilot/start-unified", json={ "symbol": "BTC/USD", "mode": "intelligent", "auto_execute": True, "exchange_id": 1, "timeframe": "1h", }, ) assert response.status_code == 200 assert mock_intelligent_autopilot.enable_auto_execution is True @patch('backend.api.autopilot.get_intelligent_autopilot') def test_auto_execute_disabled( self, mock_get_intelligent, client, mock_intelligent_autopilot ): """Test that auto-execute is not set when disabled.""" mock_get_intelligent.return_value = mock_intelligent_autopilot response = client.post( "/api/autopilot/start-unified", json={ "symbol": "BTC/USD", "mode": "intelligent", "auto_execute": False, "exchange_id": 1, "timeframe": "1h", }, ) assert response.status_code == 200 # Note: enable_auto_execution may have a default value, so we check it's not True # The actual behavior depends on the implementation class TestBackwardCompatibility: """Tests for backward compatibility with old endpoints.""" @patch('backend.api.autopilot.get_autopilot') @patch('backend.api.autopilot.run_autopilot_loop') def test_old_start_endpoint_still_works( self, mock_run_loop, mock_get_autopilot, client, mock_autopilot ): """Test that old /start endpoint still works (deprecated but functional).""" mock_get_autopilot.return_value = mock_autopilot response = client.post( "/api/autopilot/start", json={ "symbol": "BTC/USD", "interval": 60.0, "pattern_order": 5, "auto_fetch_news": True, }, ) assert response.status_code == 200 data = response.json() assert data["status"] == "started" assert data["symbol"] == "BTC/USD" @patch('backend.api.autopilot.get_intelligent_autopilot') def test_old_intelligent_start_endpoint_still_works( self, mock_get_intelligent, client, mock_intelligent_autopilot ): """Test that old /intelligent/start endpoint still works (deprecated but functional).""" mock_get_intelligent.return_value = mock_intelligent_autopilot response = client.post( "/api/autopilot/intelligent/start", json={ "symbol": "BTC/USD", "exchange_id": 1, "timeframe": "1h", "interval": 60.0, "paper_trading": True, }, ) assert response.status_code == 200 data = response.json() assert data["status"] == "started" assert data["symbol"] == "BTC/USD"