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

380 lines
13 KiB
Python
Raw Normal View History

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