Files
crypto_trader/tests/unit/backend/api/test_autopilot.py

380 lines
13 KiB
Python

"""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"