
✅ Coinbase Pro (completed in task 12) ✅ Kraken (completed in task 12) ✅ Bybit (completed in this task) ✅ OKX (completed in this task) ✅ Huobi (completed in this task)
271 lines
10 KiB
Python
271 lines
10 KiB
Python
"""
|
|
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()) |