""" Comprehensive tests for all exchange connectors. Tests the consistency and compatibility across all implemented connectors. """ import asyncio import pytest from unittest.mock import Mock, AsyncMock from ..connectors.binance_connector import BinanceConnector from ..connectors.coinbase_connector import CoinbaseConnector from ..connectors.kraken_connector import KrakenConnector from ..connectors.bybit_connector import BybitConnector from ..connectors.okx_connector import OKXConnector from ..connectors.huobi_connector import HuobiConnector class TestAllConnectors: """Test suite for all exchange connectors.""" @pytest.fixture def all_connectors(self): """Create instances of all connectors for testing.""" return { 'binance': BinanceConnector(), 'coinbase': CoinbaseConnector(use_sandbox=True), 'kraken': KrakenConnector(), 'bybit': BybitConnector(use_testnet=True), 'okx': OKXConnector(use_demo=True), 'huobi': HuobiConnector() } def test_all_connectors_initialization(self, all_connectors): """Test that all connectors initialize correctly.""" expected_names = ['binance', 'coinbase', 'kraken', 'bybit', 'okx', 'huobi'] for name, connector in all_connectors.items(): assert connector.exchange_name == name assert hasattr(connector, 'websocket_url') assert hasattr(connector, 'message_handlers') assert hasattr(connector, 'subscriptions') def test_interface_consistency(self, all_connectors): """Test that all connectors implement the required interface methods.""" required_methods = [ 'connect', 'disconnect', 'subscribe_orderbook', 'subscribe_trades', 'unsubscribe_orderbook', 'unsubscribe_trades', 'get_symbols', 'get_orderbook_snapshot', 'normalize_symbol', 'get_connection_status', 'add_data_callback', 'remove_data_callback' ] for name, connector in all_connectors.items(): for method in required_methods: assert hasattr(connector, method), f"{name} missing method {method}" assert callable(getattr(connector, method)), f"{name}.{method} not callable" def test_symbol_normalization_consistency(self, all_connectors): """Test symbol normalization across all connectors.""" test_symbols = ['BTCUSDT', 'ETHUSDT', 'btcusdt', 'BTC-USDT', 'BTC/USDT'] for name, connector in all_connectors.items(): for symbol in test_symbols: try: normalized = connector.normalize_symbol(symbol) assert isinstance(normalized, str) assert len(normalized) > 0 print(f"{name}: {symbol} -> {normalized}") except Exception as e: print(f"{name} failed to normalize {symbol}: {e}") @pytest.mark.asyncio async def test_subscription_interface(self, all_connectors): """Test subscription interface consistency.""" for name, connector in all_connectors.items(): # Mock the _send_message method connector._send_message = AsyncMock(return_value=True) try: # Test order book subscription await connector.subscribe_orderbook('BTCUSDT') assert 'BTCUSDT' in connector.subscriptions # Test trade subscription await connector.subscribe_trades('ETHUSDT') assert 'ETHUSDT' in connector.subscriptions # Test unsubscription await connector.unsubscribe_orderbook('BTCUSDT') await connector.unsubscribe_trades('ETHUSDT') print(f"✓ {name} subscription interface works") except Exception as e: print(f"✗ {name} subscription interface failed: {e}") def test_message_type_detection(self, all_connectors): """Test message type detection across connectors.""" # Test with generic message structures test_messages = [ {'type': 'test'}, {'event': 'test'}, {'op': 'test'}, {'ch': 'test'}, {'topic': 'test'}, [1, {}, 'test', 'symbol'], # Kraken format {'unknown': 'data'} ] for name, connector in all_connectors.items(): for msg in test_messages: try: msg_type = connector._get_message_type(msg) assert isinstance(msg_type, str) print(f"{name}: {msg} -> {msg_type}") except Exception as e: print(f"{name} failed to detect message type for {msg}: {e}") def test_statistics_interface(self, all_connectors): """Test statistics interface consistency.""" for name, connector in all_connectors.items(): try: stats = connector.get_stats() assert isinstance(stats, dict) assert 'exchange' in stats assert stats['exchange'] == name assert 'connection_status' in stats print(f"✓ {name} statistics interface works") except Exception as e: print(f"✗ {name} statistics interface failed: {e}") def test_callback_system(self, all_connectors): """Test callback system consistency.""" for name, connector in all_connectors.items(): try: # Test adding callback def test_callback(data): pass connector.add_data_callback(test_callback) assert test_callback in connector.data_callbacks # Test removing callback connector.remove_data_callback(test_callback) assert test_callback not in connector.data_callbacks print(f"✓ {name} callback system works") except Exception as e: print(f"✗ {name} callback system failed: {e}") def test_connection_status(self, all_connectors): """Test connection status interface.""" for name, connector in all_connectors.items(): try: status = connector.get_connection_status() assert hasattr(status, 'value') # Should be an enum # Test is_connected property is_connected = connector.is_connected assert isinstance(is_connected, bool) print(f"✓ {name} connection status interface works") except Exception as e: print(f"✗ {name} connection status interface failed: {e}") async def test_connector_compatibility(): """Test compatibility across all connectors.""" print("=== Testing All Exchange Connectors ===") connectors = { 'binance': BinanceConnector(), 'coinbase': CoinbaseConnector(use_sandbox=True), 'kraken': KrakenConnector(), 'bybit': BybitConnector(use_testnet=True), 'okx': OKXConnector(use_demo=True), 'huobi': HuobiConnector() } # Test basic functionality for name, connector in connectors.items(): try: print(f"\nTesting {name.upper()} connector:") # Test initialization assert connector.exchange_name == name print(f" ✓ Initialization: {connector.exchange_name}") # Test symbol normalization btc_symbol = connector.normalize_symbol('BTCUSDT') eth_symbol = connector.normalize_symbol('ETHUSDT') print(f" ✓ Symbol normalization: BTCUSDT -> {btc_symbol}, ETHUSDT -> {eth_symbol}") # Test message type detection test_msg = {'type': 'test'} if name != 'kraken' else [1, {}, 'test', 'symbol'] msg_type = connector._get_message_type(test_msg) print(f" ✓ Message type detection: {msg_type}") # Test statistics stats = connector.get_stats() print(f" ✓ Statistics: {len(stats)} fields") # Test connection status status = connector.get_connection_status() print(f" ✓ Connection status: {status.value}") print(f" ✅ {name.upper()} connector passed all tests") except Exception as e: print(f" ❌ {name.upper()} connector failed: {e}") print("\n=== All Connector Tests Completed ===") return True async def test_multi_connector_data_flow(): """Test data flow across multiple connectors simultaneously.""" print("=== Testing Multi-Connector Data Flow ===") connectors = { 'binance': BinanceConnector(), 'coinbase': CoinbaseConnector(use_sandbox=True), 'kraken': KrakenConnector() } # Set up data collection received_data = {name: [] for name in connectors.keys()} def create_callback(exchange_name): def callback(data): received_data[exchange_name].append(data) print(f"Received data from {exchange_name}: {type(data).__name__}") return callback # Add callbacks to all connectors for name, connector in connectors.items(): connector.add_data_callback(create_callback(name)) connector._send_message = AsyncMock(return_value=True) # Test subscription to same symbol across exchanges symbol = 'BTCUSDT' for name, connector in connectors.items(): try: await connector.subscribe_orderbook(symbol) await connector.subscribe_trades(symbol) print(f"✓ Subscribed to {symbol} on {name}") except Exception as e: print(f"✗ Failed to subscribe to {symbol} on {name}: {e}") print("Multi-connector data flow test completed") return True if __name__ == "__main__": # Run all tests async def run_all_tests(): await test_connector_compatibility() await test_multi_connector_data_flow() print("✅ All connector tests completed successfully") asyncio.run(run_all_tests())