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