Files
gogo2/COBY/tests/test_bybit_connector.py
Dobromir Popov 7339972eab 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)
2025-08-04 23:41:42 +03:00

321 lines
11 KiB
Python

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