Files
crypto_trader/tests/unit/data/providers/test_ccxt_provider.py

140 lines
4.6 KiB
Python
Raw Normal View History

"""Unit tests for CCXT pricing provider."""
import pytest
from unittest.mock import Mock, patch, MagicMock
from decimal import Decimal
from datetime import datetime
from src.data.providers.ccxt_provider import CCXTProvider
@pytest.fixture
def mock_ccxt_exchange():
"""Create a mock CCXT exchange."""
exchange = Mock()
exchange.markets = {
'BTC/USDT': {},
'ETH/USDT': {},
'BTC/USD': {},
}
exchange.id = 'kraken'
exchange.fetch_ticker = Mock(return_value={
'bid': 50000.0,
'ask': 50001.0,
'last': 50000.5,
'high': 51000.0,
'low': 49000.0,
'quoteVolume': 1000000.0,
'timestamp': 1609459200000,
})
exchange.fetch_ohlcv = Mock(return_value=[
[1609459200000, 50000, 51000, 49000, 50000, 1000],
])
exchange.load_markets = Mock(return_value=exchange.markets)
return exchange
@pytest.fixture
def provider():
"""Create a CCXT provider instance."""
return CCXTProvider(exchange_name='kraken')
class TestCCXTProvider:
"""Tests for CCXTProvider."""
def test_init(self, provider):
"""Test provider initialization."""
assert provider.name == "CCXT Provider"
assert not provider._connected
assert provider.exchange is None
def test_name_property(self, provider):
"""Test name property."""
provider._selected_exchange_id = 'kraken'
assert provider.name == "CCXT-Kraken"
@patch('src.data.providers.ccxt_provider.ccxt')
def test_connect_success(self, mock_ccxt, provider, mock_ccxt_exchange):
"""Test successful connection."""
mock_ccxt.kraken = Mock(return_value=mock_ccxt_exchange)
result = provider.connect()
assert result is True
assert provider._connected is True
assert provider.exchange == mock_ccxt_exchange
assert provider._selected_exchange_id == 'kraken'
@patch('src.data.providers.ccxt_provider.ccxt')
def test_connect_failure(self, mock_ccxt, provider):
"""Test connection failure."""
mock_ccxt.kraken = Mock(side_effect=Exception("Connection failed"))
result = provider.connect()
assert result is False
assert not provider._connected
@patch('src.data.providers.ccxt_provider.ccxt')
def test_get_ticker(self, mock_ccxt, provider, mock_ccxt_exchange):
"""Test getting ticker data."""
mock_ccxt.kraken = Mock(return_value=mock_ccxt_exchange)
provider.connect()
ticker = provider.get_ticker('BTC/USDT')
assert ticker['symbol'] == 'BTC/USDT'
assert isinstance(ticker['bid'], Decimal)
assert isinstance(ticker['last'], Decimal)
assert ticker['last'] > 0
@patch('src.data.providers.ccxt_provider.ccxt')
def test_get_ohlcv(self, mock_ccxt, provider, mock_ccxt_exchange):
"""Test getting OHLCV data."""
mock_ccxt.kraken = Mock(return_value=mock_ccxt_exchange)
provider.connect()
ohlcv = provider.get_ohlcv('BTC/USDT', '1h', limit=10)
assert len(ohlcv) > 0
assert len(ohlcv[0]) == 6 # timestamp, open, high, low, close, volume
@patch('src.data.providers.ccxt_provider.ccxt')
def test_subscribe_ticker(self, mock_ccxt, provider, mock_ccxt_exchange):
"""Test subscribing to ticker updates."""
mock_ccxt.kraken = Mock(return_value=mock_ccxt_exchange)
provider.connect()
callback = Mock()
result = provider.subscribe_ticker('BTC/USDT', callback)
assert result is True
assert 'ticker_BTC/USDT' in provider._subscribers
def test_normalize_symbol(self, provider):
"""Test symbol normalization."""
# Test with exchange
with patch.object(provider, 'exchange') as mock_exchange:
mock_exchange.markets = {'BTC/USDT': {}}
normalized = provider.normalize_symbol('btc-usdt')
assert normalized == 'BTC/USDT'
# Test without exchange
provider.exchange = None
normalized = provider.normalize_symbol('btc-usdt')
assert normalized == 'BTC/USDT'
@patch('src.data.providers.ccxt_provider.ccxt')
def test_disconnect(self, mock_ccxt, provider, mock_ccxt_exchange):
"""Test disconnection."""
mock_ccxt.kraken = Mock(return_value=mock_ccxt_exchange)
provider.connect()
provider.subscribe_ticker('BTC/USDT', Mock())
provider.disconnect()
assert not provider._connected
assert provider.exchange is None
assert len(provider._subscribers) == 0