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