✅ Binance (completed previously)
✅ 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)
This commit is contained in:
321
COBY/tests/test_bybit_connector.py
Normal file
321
COBY/tests/test_bybit_connector.py
Normal file
@ -0,0 +1,321 @@
|
||||
"""
|
||||
Unit tests for Bybit exchange connector.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import pytest
|
||||
from unittest.mock import Mock, AsyncMock, patch
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from ..connectors.bybit_connector import BybitConnector
|
||||
from ..models.core import OrderBookSnapshot, TradeEvent, PriceLevel
|
||||
|
||||
|
||||
class TestBybitConnector:
|
||||
"""Test suite for Bybit connector."""
|
||||
|
||||
@pytest.fixture
|
||||
def connector(self):
|
||||
"""Create connector instance for testing."""
|
||||
return BybitConnector(use_testnet=True)
|
||||
|
||||
def test_initialization(self, connector):
|
||||
"""Test connector initializes correctly."""
|
||||
assert connector.exchange_name == "bybit"
|
||||
assert connector.use_testnet is True
|
||||
assert connector.TESTNET_URL in connector.websocket_url
|
||||
assert 'orderbook' in connector.message_handlers
|
||||
assert 'publicTrade' in connector.message_handlers
|
||||
|
||||
def test_symbol_normalization(self, connector):
|
||||
"""Test symbol normalization to Bybit format."""
|
||||
# Test standard conversions (Bybit uses same format as Binance)
|
||||
assert connector.normalize_symbol('BTCUSDT') == 'BTCUSDT'
|
||||
assert connector.normalize_symbol('ETHUSDT') == 'ETHUSDT'
|
||||
assert connector.normalize_symbol('btcusdt') == 'BTCUSDT'
|
||||
|
||||
# Test with separators
|
||||
assert connector.normalize_symbol('BTC-USDT') == 'BTCUSDT'
|
||||
assert connector.normalize_symbol('BTC/USDT') == 'BTCUSDT'
|
||||
|
||||
def test_message_type_detection(self, connector):
|
||||
"""Test message type detection."""
|
||||
# Test orderbook message
|
||||
orderbook_message = {
|
||||
'topic': 'orderbook.50.BTCUSDT',
|
||||
'data': {'b': [], 'a': []}
|
||||
}
|
||||
assert connector._get_message_type(orderbook_message) == 'orderbook'
|
||||
|
||||
# Test trade message
|
||||
trade_message = {
|
||||
'topic': 'publicTrade.BTCUSDT',
|
||||
'data': []
|
||||
}
|
||||
assert connector._get_message_type(trade_message) == 'publicTrade'
|
||||
|
||||
# Test operation message
|
||||
op_message = {'op': 'subscribe', 'success': True}
|
||||
assert connector._get_message_type(op_message) == 'subscribe'
|
||||
|
||||
# Test response message
|
||||
response_message = {'success': True, 'ret_msg': 'OK'}
|
||||
assert connector._get_message_type(response_message) == 'response'
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_subscription_methods(self, connector):
|
||||
"""Test subscription and unsubscription methods."""
|
||||
# Mock the _send_message method
|
||||
connector._send_message = AsyncMock(return_value=True)
|
||||
|
||||
# Test order book subscription
|
||||
await connector.subscribe_orderbook('BTCUSDT')
|
||||
|
||||
# Verify subscription was tracked
|
||||
assert 'BTCUSDT' in connector.subscriptions
|
||||
assert 'orderbook' in connector.subscriptions['BTCUSDT']
|
||||
assert 'orderbook.50.BTCUSDT' in connector.subscribed_topics
|
||||
|
||||
# Verify correct message was sent
|
||||
connector._send_message.assert_called()
|
||||
call_args = connector._send_message.call_args[0][0]
|
||||
assert call_args['op'] == 'subscribe'
|
||||
assert 'orderbook.50.BTCUSDT' in call_args['args']
|
||||
|
||||
# Test trade subscription
|
||||
await connector.subscribe_trades('ETHUSDT')
|
||||
|
||||
assert 'ETHUSDT' in connector.subscriptions
|
||||
assert 'trades' in connector.subscriptions['ETHUSDT']
|
||||
assert 'publicTrade.ETHUSDT' in connector.subscribed_topics
|
||||
|
||||
# Test unsubscription
|
||||
await connector.unsubscribe_orderbook('BTCUSDT')
|
||||
|
||||
# Verify unsubscription
|
||||
if 'BTCUSDT' in connector.subscriptions:
|
||||
assert 'orderbook' not in connector.subscriptions['BTCUSDT']
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_orderbook_snapshot_parsing(self, connector):
|
||||
"""Test parsing order book snapshot data."""
|
||||
# Mock order book data from Bybit
|
||||
mock_data = {
|
||||
'u': 12345,
|
||||
'ts': 1609459200000,
|
||||
'b': [
|
||||
['50000.00', '1.5'],
|
||||
['49999.00', '2.0']
|
||||
],
|
||||
'a': [
|
||||
['50001.00', '1.2'],
|
||||
['50002.00', '0.8']
|
||||
]
|
||||
}
|
||||
|
||||
# Parse the data
|
||||
orderbook = connector._parse_orderbook_snapshot(mock_data, 'BTCUSDT')
|
||||
|
||||
# Verify parsing
|
||||
assert isinstance(orderbook, OrderBookSnapshot)
|
||||
assert orderbook.symbol == 'BTCUSDT'
|
||||
assert orderbook.exchange == 'bybit'
|
||||
assert orderbook.sequence_id == 12345
|
||||
|
||||
# Verify bids
|
||||
assert len(orderbook.bids) == 2
|
||||
assert orderbook.bids[0].price == 50000.00
|
||||
assert orderbook.bids[0].size == 1.5
|
||||
|
||||
# Verify asks
|
||||
assert len(orderbook.asks) == 2
|
||||
assert orderbook.asks[0].price == 50001.00
|
||||
assert orderbook.asks[0].size == 1.2
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_orderbook_update_handling(self, connector):
|
||||
"""Test handling order book update messages."""
|
||||
# Mock callback
|
||||
callback_called = False
|
||||
received_data = None
|
||||
|
||||
def mock_callback(data):
|
||||
nonlocal callback_called, received_data
|
||||
callback_called = True
|
||||
received_data = data
|
||||
|
||||
connector.add_data_callback(mock_callback)
|
||||
|
||||
# Mock Bybit orderbook update message
|
||||
update_message = {
|
||||
'topic': 'orderbook.50.BTCUSDT',
|
||||
'ts': 1609459200000,
|
||||
'data': {
|
||||
'u': 12345,
|
||||
'b': [['50000.00', '1.5']],
|
||||
'a': [['50001.00', '1.2']]
|
||||
}
|
||||
}
|
||||
|
||||
# Handle the message
|
||||
await connector._handle_orderbook_update(update_message)
|
||||
|
||||
# Verify callback was called
|
||||
assert callback_called
|
||||
assert isinstance(received_data, OrderBookSnapshot)
|
||||
assert received_data.symbol == 'BTCUSDT'
|
||||
assert received_data.exchange == 'bybit'
|
||||
assert received_data.sequence_id == 12345
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_trade_handling(self, connector):
|
||||
"""Test handling trade messages."""
|
||||
# Mock callback
|
||||
callback_called = False
|
||||
received_trades = []
|
||||
|
||||
def mock_callback(data):
|
||||
nonlocal callback_called
|
||||
callback_called = True
|
||||
received_trades.append(data)
|
||||
|
||||
connector.add_data_callback(mock_callback)
|
||||
|
||||
# Mock Bybit trade message
|
||||
trade_message = {
|
||||
'topic': 'publicTrade.BTCUSDT',
|
||||
'ts': 1609459200000,
|
||||
'data': [
|
||||
{
|
||||
'T': 1609459200000,
|
||||
'p': '50000.50',
|
||||
'v': '0.1',
|
||||
'S': 'Buy',
|
||||
'i': '12345'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Handle the message
|
||||
await connector._handle_trade_update(trade_message)
|
||||
|
||||
# Verify callback was called
|
||||
assert callback_called
|
||||
assert len(received_trades) == 1
|
||||
|
||||
trade = received_trades[0]
|
||||
assert isinstance(trade, TradeEvent)
|
||||
assert trade.symbol == 'BTCUSDT'
|
||||
assert trade.exchange == 'bybit'
|
||||
assert trade.price == 50000.50
|
||||
assert trade.size == 0.1
|
||||
assert trade.side == 'buy'
|
||||
assert trade.trade_id == '12345'
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_symbols(self, connector):
|
||||
"""Test getting available symbols."""
|
||||
# Mock HTTP response
|
||||
mock_response_data = {
|
||||
'retCode': 0,
|
||||
'result': {
|
||||
'list': [
|
||||
{
|
||||
'symbol': 'BTCUSDT',
|
||||
'status': 'Trading'
|
||||
},
|
||||
{
|
||||
'symbol': 'ETHUSDT',
|
||||
'status': 'Trading'
|
||||
},
|
||||
{
|
||||
'symbol': 'DISABLEDUSDT',
|
||||
'status': 'Closed'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
with patch('aiohttp.ClientSession.get') as mock_get:
|
||||
mock_response = AsyncMock()
|
||||
mock_response.status = 200
|
||||
mock_response.json = AsyncMock(return_value=mock_response_data)
|
||||
mock_get.return_value.__aenter__.return_value = mock_response
|
||||
|
||||
symbols = await connector.get_symbols()
|
||||
|
||||
# Should only return trading symbols
|
||||
assert 'BTCUSDT' in symbols
|
||||
assert 'ETHUSDT' in symbols
|
||||
assert 'DISABLEDUSDT' not in symbols
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_orderbook_snapshot(self, connector):
|
||||
"""Test getting order book snapshot from REST API."""
|
||||
# Mock HTTP response
|
||||
mock_orderbook = {
|
||||
'retCode': 0,
|
||||
'result': {
|
||||
'ts': 1609459200000,
|
||||
'u': 12345,
|
||||
'b': [['50000.00', '1.5']],
|
||||
'a': [['50001.00', '1.2']]
|
||||
}
|
||||
}
|
||||
|
||||
with patch('aiohttp.ClientSession.get') as mock_get:
|
||||
mock_response = AsyncMock()
|
||||
mock_response.status = 200
|
||||
mock_response.json = AsyncMock(return_value=mock_orderbook)
|
||||
mock_get.return_value.__aenter__.return_value = mock_response
|
||||
|
||||
orderbook = await connector.get_orderbook_snapshot('BTCUSDT')
|
||||
|
||||
assert isinstance(orderbook, OrderBookSnapshot)
|
||||
assert orderbook.symbol == 'BTCUSDT'
|
||||
assert orderbook.exchange == 'bybit'
|
||||
assert len(orderbook.bids) == 1
|
||||
assert len(orderbook.asks) == 1
|
||||
|
||||
def test_statistics(self, connector):
|
||||
"""Test getting connector statistics."""
|
||||
# Add some test data
|
||||
connector.subscribed_topics.add('orderbook.50.BTCUSDT')
|
||||
|
||||
stats = connector.get_bybit_stats()
|
||||
|
||||
assert stats['exchange'] == 'bybit'
|
||||
assert 'orderbook.50.BTCUSDT' in stats['subscribed_topics']
|
||||
assert stats['use_testnet'] is True
|
||||
assert 'authenticated' in stats
|
||||
|
||||
|
||||
async def test_bybit_integration():
|
||||
"""Integration test for Bybit connector."""
|
||||
connector = BybitConnector(use_testnet=True)
|
||||
|
||||
try:
|
||||
# Test basic functionality
|
||||
assert connector.exchange_name == "bybit"
|
||||
|
||||
# Test symbol normalization
|
||||
assert connector.normalize_symbol('BTCUSDT') == 'BTCUSDT'
|
||||
assert connector.normalize_symbol('btc-usdt') == 'BTCUSDT'
|
||||
|
||||
# Test message type detection
|
||||
test_message = {'topic': 'orderbook.50.BTCUSDT', 'data': {}}
|
||||
assert connector._get_message_type(test_message) == 'orderbook'
|
||||
|
||||
print("✓ Bybit connector integration test passed")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Bybit connector integration test failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run integration test
|
||||
success = asyncio.run(test_bybit_integration())
|
||||
if not success:
|
||||
exit(1)
|
Reference in New Issue
Block a user