Files
gogo2/COBY/tests/test_redis_manager.py
Dobromir Popov ff75af566c caching
2025-08-04 17:55:00 +03:00

347 lines
11 KiB
Python

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