caching
This commit is contained in:
347
COBY/tests/test_redis_manager.py
Normal file
347
COBY/tests/test_redis_manager.py
Normal file
@ -0,0 +1,347 @@
|
||||
"""
|
||||
Tests for Redis caching system.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import asyncio
|
||||
from datetime import datetime, timezone
|
||||
from ..caching.redis_manager import RedisManager
|
||||
from ..caching.cache_keys import CacheKeys
|
||||
from ..caching.data_serializer import DataSerializer
|
||||
from ..models.core import OrderBookSnapshot, HeatmapData, PriceLevel, HeatmapPoint
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def redis_manager():
|
||||
"""Create and initialize Redis manager for testing"""
|
||||
manager = RedisManager()
|
||||
await manager.initialize()
|
||||
yield manager
|
||||
await manager.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cache_keys():
|
||||
"""Create cache keys helper"""
|
||||
return CacheKeys()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def data_serializer():
|
||||
"""Create data serializer"""
|
||||
return DataSerializer()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_orderbook():
|
||||
"""Create sample order book for testing"""
|
||||
return OrderBookSnapshot(
|
||||
symbol="BTCUSDT",
|
||||
exchange="binance",
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
bids=[
|
||||
PriceLevel(price=50000.0, size=1.5),
|
||||
PriceLevel(price=49999.0, size=2.0)
|
||||
],
|
||||
asks=[
|
||||
PriceLevel(price=50001.0, size=1.0),
|
||||
PriceLevel(price=50002.0, size=1.5)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_heatmap():
|
||||
"""Create sample heatmap for testing"""
|
||||
heatmap = HeatmapData(
|
||||
symbol="BTCUSDT",
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
bucket_size=1.0
|
||||
)
|
||||
|
||||
# Add some sample points
|
||||
heatmap.data = [
|
||||
HeatmapPoint(price=50000.0, volume=1.5, intensity=0.8, side='bid'),
|
||||
HeatmapPoint(price=50001.0, volume=1.0, intensity=0.6, side='ask'),
|
||||
HeatmapPoint(price=49999.0, volume=2.0, intensity=1.0, side='bid'),
|
||||
HeatmapPoint(price=50002.0, volume=1.5, intensity=0.7, side='ask')
|
||||
]
|
||||
|
||||
return heatmap
|
||||
|
||||
|
||||
class TestCacheKeys:
|
||||
"""Test cases for CacheKeys"""
|
||||
|
||||
def test_orderbook_key_generation(self, cache_keys):
|
||||
"""Test order book key generation"""
|
||||
key = cache_keys.orderbook_key("BTCUSDT", "binance")
|
||||
assert key == "ob:binance:BTCUSDT"
|
||||
|
||||
def test_heatmap_key_generation(self, cache_keys):
|
||||
"""Test heatmap key generation"""
|
||||
# Exchange-specific heatmap
|
||||
key1 = cache_keys.heatmap_key("BTCUSDT", 1.0, "binance")
|
||||
assert key1 == "hm:binance:BTCUSDT:1.0"
|
||||
|
||||
# Consolidated heatmap
|
||||
key2 = cache_keys.heatmap_key("BTCUSDT", 1.0)
|
||||
assert key2 == "hm:consolidated:BTCUSDT:1.0"
|
||||
|
||||
def test_ttl_determination(self, cache_keys):
|
||||
"""Test TTL determination for different key types"""
|
||||
ob_key = cache_keys.orderbook_key("BTCUSDT", "binance")
|
||||
hm_key = cache_keys.heatmap_key("BTCUSDT", 1.0)
|
||||
|
||||
assert cache_keys.get_ttl(ob_key) == cache_keys.ORDERBOOK_TTL
|
||||
assert cache_keys.get_ttl(hm_key) == cache_keys.HEATMAP_TTL
|
||||
|
||||
def test_key_parsing(self, cache_keys):
|
||||
"""Test cache key parsing"""
|
||||
ob_key = cache_keys.orderbook_key("BTCUSDT", "binance")
|
||||
parsed = cache_keys.parse_key(ob_key)
|
||||
|
||||
assert parsed['type'] == 'orderbook'
|
||||
assert parsed['exchange'] == 'binance'
|
||||
assert parsed['symbol'] == 'BTCUSDT'
|
||||
|
||||
|
||||
class TestDataSerializer:
|
||||
"""Test cases for DataSerializer"""
|
||||
|
||||
def test_simple_data_serialization(self, data_serializer):
|
||||
"""Test serialization of simple data types"""
|
||||
test_data = {
|
||||
'string': 'test',
|
||||
'number': 42,
|
||||
'float': 3.14,
|
||||
'boolean': True,
|
||||
'list': [1, 2, 3],
|
||||
'nested': {'key': 'value'}
|
||||
}
|
||||
|
||||
# Serialize and deserialize
|
||||
serialized = data_serializer.serialize(test_data)
|
||||
deserialized = data_serializer.deserialize(serialized)
|
||||
|
||||
assert deserialized == test_data
|
||||
|
||||
def test_orderbook_serialization(self, data_serializer, sample_orderbook):
|
||||
"""Test order book serialization"""
|
||||
# Serialize and deserialize
|
||||
serialized = data_serializer.serialize(sample_orderbook)
|
||||
deserialized = data_serializer.deserialize(serialized)
|
||||
|
||||
assert isinstance(deserialized, OrderBookSnapshot)
|
||||
assert deserialized.symbol == sample_orderbook.symbol
|
||||
assert deserialized.exchange == sample_orderbook.exchange
|
||||
assert len(deserialized.bids) == len(sample_orderbook.bids)
|
||||
assert len(deserialized.asks) == len(sample_orderbook.asks)
|
||||
|
||||
def test_heatmap_serialization(self, data_serializer, sample_heatmap):
|
||||
"""Test heatmap serialization"""
|
||||
# Test specialized heatmap serialization
|
||||
serialized = data_serializer.serialize_heatmap(sample_heatmap)
|
||||
deserialized = data_serializer.deserialize_heatmap(serialized)
|
||||
|
||||
assert isinstance(deserialized, HeatmapData)
|
||||
assert deserialized.symbol == sample_heatmap.symbol
|
||||
assert deserialized.bucket_size == sample_heatmap.bucket_size
|
||||
assert len(deserialized.data) == len(sample_heatmap.data)
|
||||
|
||||
# Check first point
|
||||
original_point = sample_heatmap.data[0]
|
||||
deserialized_point = deserialized.data[0]
|
||||
assert deserialized_point.price == original_point.price
|
||||
assert deserialized_point.volume == original_point.volume
|
||||
assert deserialized_point.side == original_point.side
|
||||
|
||||
|
||||
class TestRedisManager:
|
||||
"""Test cases for RedisManager"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_basic_set_get(self, redis_manager):
|
||||
"""Test basic set and get operations"""
|
||||
# Set a simple value
|
||||
key = "test:basic"
|
||||
value = {"test": "data", "number": 42}
|
||||
|
||||
success = await redis_manager.set(key, value, ttl=60)
|
||||
assert success is True
|
||||
|
||||
# Get the value back
|
||||
retrieved = await redis_manager.get(key)
|
||||
assert retrieved == value
|
||||
|
||||
# Clean up
|
||||
await redis_manager.delete(key)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_orderbook_caching(self, redis_manager, sample_orderbook):
|
||||
"""Test order book caching"""
|
||||
# Cache order book
|
||||
success = await redis_manager.cache_orderbook(sample_orderbook)
|
||||
assert success is True
|
||||
|
||||
# Retrieve order book
|
||||
retrieved = await redis_manager.get_orderbook(
|
||||
sample_orderbook.symbol,
|
||||
sample_orderbook.exchange
|
||||
)
|
||||
|
||||
assert retrieved is not None
|
||||
assert isinstance(retrieved, OrderBookSnapshot)
|
||||
assert retrieved.symbol == sample_orderbook.symbol
|
||||
assert retrieved.exchange == sample_orderbook.exchange
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_heatmap_caching(self, redis_manager, sample_heatmap):
|
||||
"""Test heatmap caching"""
|
||||
# Cache heatmap
|
||||
success = await redis_manager.set_heatmap(
|
||||
sample_heatmap.symbol,
|
||||
sample_heatmap,
|
||||
exchange="binance"
|
||||
)
|
||||
assert success is True
|
||||
|
||||
# Retrieve heatmap
|
||||
retrieved = await redis_manager.get_heatmap(
|
||||
sample_heatmap.symbol,
|
||||
exchange="binance"
|
||||
)
|
||||
|
||||
assert retrieved is not None
|
||||
assert isinstance(retrieved, HeatmapData)
|
||||
assert retrieved.symbol == sample_heatmap.symbol
|
||||
assert len(retrieved.data) == len(sample_heatmap.data)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_multi_operations(self, redis_manager):
|
||||
"""Test multi-get and multi-set operations"""
|
||||
# Prepare test data
|
||||
test_data = {
|
||||
"test:multi1": {"value": 1},
|
||||
"test:multi2": {"value": 2},
|
||||
"test:multi3": {"value": 3}
|
||||
}
|
||||
|
||||
# Multi-set
|
||||
success = await redis_manager.mset(test_data, ttl=60)
|
||||
assert success is True
|
||||
|
||||
# Multi-get
|
||||
keys = list(test_data.keys())
|
||||
values = await redis_manager.mget(keys)
|
||||
|
||||
assert len(values) == 3
|
||||
assert all(v is not None for v in values)
|
||||
|
||||
# Verify values
|
||||
for i, key in enumerate(keys):
|
||||
assert values[i] == test_data[key]
|
||||
|
||||
# Clean up
|
||||
for key in keys:
|
||||
await redis_manager.delete(key)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_key_expiration(self, redis_manager):
|
||||
"""Test key expiration"""
|
||||
key = "test:expiration"
|
||||
value = {"expires": "soon"}
|
||||
|
||||
# Set with short TTL
|
||||
success = await redis_manager.set(key, value, ttl=1)
|
||||
assert success is True
|
||||
|
||||
# Should exist immediately
|
||||
exists = await redis_manager.exists(key)
|
||||
assert exists is True
|
||||
|
||||
# Wait for expiration
|
||||
await asyncio.sleep(2)
|
||||
|
||||
# Should not exist after expiration
|
||||
exists = await redis_manager.exists(key)
|
||||
assert exists is False
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cache_miss(self, redis_manager):
|
||||
"""Test cache miss behavior"""
|
||||
# Try to get non-existent key
|
||||
value = await redis_manager.get("test:nonexistent")
|
||||
assert value is None
|
||||
|
||||
# Check statistics
|
||||
stats = redis_manager.get_stats()
|
||||
assert stats['misses'] > 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_health_check(self, redis_manager):
|
||||
"""Test Redis health check"""
|
||||
health = await redis_manager.health_check()
|
||||
|
||||
assert isinstance(health, dict)
|
||||
assert 'redis_ping' in health
|
||||
assert 'total_keys' in health
|
||||
assert 'hit_rate' in health
|
||||
|
||||
# Should be able to ping
|
||||
assert health['redis_ping'] is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_statistics_tracking(self, redis_manager):
|
||||
"""Test statistics tracking"""
|
||||
# Reset stats
|
||||
redis_manager.reset_stats()
|
||||
|
||||
# Perform some operations
|
||||
await redis_manager.set("test:stats1", {"data": 1})
|
||||
await redis_manager.set("test:stats2", {"data": 2})
|
||||
await redis_manager.get("test:stats1")
|
||||
await redis_manager.get("test:nonexistent")
|
||||
|
||||
# Check statistics
|
||||
stats = redis_manager.get_stats()
|
||||
|
||||
assert stats['sets'] >= 2
|
||||
assert stats['gets'] >= 2
|
||||
assert stats['hits'] >= 1
|
||||
assert stats['misses'] >= 1
|
||||
assert stats['total_operations'] >= 4
|
||||
|
||||
# Clean up
|
||||
await redis_manager.delete("test:stats1")
|
||||
await redis_manager.delete("test:stats2")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run simple tests
|
||||
async def simple_test():
|
||||
manager = RedisManager()
|
||||
await manager.initialize()
|
||||
|
||||
# Test basic operations
|
||||
success = await manager.set("test", {"simple": "test"}, ttl=60)
|
||||
print(f"Set operation: {'SUCCESS' if success else 'FAILED'}")
|
||||
|
||||
value = await manager.get("test")
|
||||
print(f"Get operation: {'SUCCESS' if value else 'FAILED'}")
|
||||
|
||||
# Test ping
|
||||
ping_result = await manager.ping()
|
||||
print(f"Ping test: {'SUCCESS' if ping_result else 'FAILED'}")
|
||||
|
||||
# Get statistics
|
||||
stats = manager.get_stats()
|
||||
print(f"Statistics: {stats}")
|
||||
|
||||
# Clean up
|
||||
await manager.delete("test")
|
||||
await manager.close()
|
||||
|
||||
print("Simple Redis test completed")
|
||||
|
||||
asyncio.run(simple_test())
|
Reference in New Issue
Block a user