"""Tests for async database cache.""" import pytest import tempfile import os from exchange_data_manager.candles.models import Candle from exchange_data_manager.cache.async_database import AsyncDatabaseCache @pytest.fixture async def async_db_cache(): """Create a temporary async database for testing.""" with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f: db_path = f.name cache = AsyncDatabaseCache(db_path=db_path) await cache.initialize() yield cache # Cleanup if os.path.exists(db_path): os.unlink(db_path) class TestAsyncDatabaseCache: """Tests for AsyncDatabaseCache class.""" @pytest.mark.asyncio async def test_put_and_get_candles(self, async_db_cache): """Test storing and retrieving candles asynchronously.""" candles = [ Candle(time=1709337600, open=50000.0, high=50100.0, low=49900.0, close=50050.0, volume=10.0), Candle(time=1709337660, open=50050.0, high=50200.0, low=50000.0, close=50150.0, volume=15.0), ] await async_db_cache.put("binance", "BTC/USDT", "1m", candles) result, gaps = await async_db_cache.get("binance", "BTC/USDT", "1m") assert len(result) == 2 assert result[0].time == 1709337600 assert result[1].time == 1709337660 @pytest.mark.asyncio async def test_get_empty_returns_empty_list(self, async_db_cache): """Test that getting non-existent data returns empty list.""" result, gaps = await async_db_cache.get("binance", "BTC/USDT", "1m") assert result == [] @pytest.mark.asyncio async def test_get_with_time_range(self, async_db_cache): """Test getting candles within a time range.""" candles = [ Candle(time=1709337600, open=50000.0, high=50000.0, low=50000.0, close=50000.0, volume=1.0), Candle(time=1709337660, open=50000.0, high=50000.0, low=50000.0, close=50000.0, volume=1.0), Candle(time=1709337720, open=50000.0, high=50000.0, low=50000.0, close=50000.0, volume=1.0), Candle(time=1709337780, open=50000.0, high=50000.0, low=50000.0, close=50000.0, volume=1.0), ] await async_db_cache.put("binance", "BTC/USDT", "1m", candles) result, gaps = await async_db_cache.get( "binance", "BTC/USDT", "1m", start=1709337660, end=1709337720, ) assert len(result) == 2 assert result[0].time == 1709337660 assert result[1].time == 1709337720 @pytest.mark.asyncio async def test_upsert_updates_existing(self, async_db_cache): """Test that storing duplicate timestamps updates existing records.""" candles1 = [ Candle(time=1709337600, open=50000.0, high=50100.0, low=49900.0, close=50050.0, volume=10.0), ] await async_db_cache.put("binance", "BTC/USDT", "1m", candles1) # Store updated candle with same timestamp candles2 = [ Candle(time=1709337600, open=51000.0, high=51100.0, low=50900.0, close=51050.0, volume=20.0), ] await async_db_cache.put("binance", "BTC/USDT", "1m", candles2) result, gaps = await async_db_cache.get("binance", "BTC/USDT", "1m") # Should only have one candle with updated values assert len(result) == 1 assert result[0].open == 51000.0 assert result[0].volume == 20.0 @pytest.mark.asyncio async def test_delete_all(self, async_db_cache): """Test deleting all data.""" candles = [ Candle(time=1709337600, open=50000.0, high=50000.0, low=50000.0, close=50000.0, volume=1.0), ] await async_db_cache.put("binance", "BTC/USDT", "1m", candles) await async_db_cache.put("binance", "ETH/USDT", "1m", candles) await async_db_cache.delete() result1, _ = await async_db_cache.get("binance", "BTC/USDT", "1m") result2, _ = await async_db_cache.get("binance", "ETH/USDT", "1m") assert result1 == [] assert result2 == [] @pytest.mark.asyncio async def test_delete_filtered(self, async_db_cache): """Test deleting filtered data.""" candles = [ Candle(time=1709337600, open=50000.0, high=50000.0, low=50000.0, close=50000.0, volume=1.0), ] await async_db_cache.put("binance", "BTC/USDT", "1m", candles) await async_db_cache.put("binance", "ETH/USDT", "1m", candles) # Only delete BTC await async_db_cache.delete(exchange="binance", symbol="BTC/USDT") result1, _ = await async_db_cache.get("binance", "BTC/USDT", "1m") result2, _ = await async_db_cache.get("binance", "ETH/USDT", "1m") assert result1 == [] assert len(result2) == 1 @pytest.mark.asyncio async def test_stats(self, async_db_cache): """Test database statistics.""" candles = [ Candle(time=1709337600, open=50000.0, high=50000.0, low=50000.0, close=50000.0, volume=1.0), Candle(time=1709337660, open=50000.0, high=50000.0, low=50000.0, close=50000.0, volume=1.0), ] await async_db_cache.put("binance", "BTC/USDT", "1m", candles) stats = await async_db_cache.stats() assert stats["total_candles"] == 2 assert stats["num_entries"] == 1 @pytest.mark.asyncio async def test_get_time_range(self, async_db_cache): """Test getting the time range of cached data.""" candles = [ Candle(time=1709337600, open=50000.0, high=50000.0, low=50000.0, close=50000.0, volume=1.0), Candle(time=1709337660, open=50000.0, high=50000.0, low=50000.0, close=50000.0, volume=1.0), Candle(time=1709337720, open=50000.0, high=50000.0, low=50000.0, close=50000.0, volume=1.0), ] await async_db_cache.put("binance", "BTC/USDT", "1m", candles) time_range = await async_db_cache.get_time_range("binance", "BTC/USDT", "1m") assert time_range is not None assert time_range[0] == 1709337600 # min assert time_range[1] == 1709337720 # max @pytest.mark.asyncio async def test_get_time_range_no_data(self, async_db_cache): """Test getting time range when no data exists.""" time_range = await async_db_cache.get_time_range("binance", "BTC/USDT", "1m") assert time_range is None @pytest.mark.asyncio async def test_single_candle_gap_detection(self, async_db_cache): """Test that a single missing candle is detected as a gap.""" # Candles with 1m interval but missing the second one candles = [ Candle(time=1709337600, open=50000.0, high=50000.0, low=50000.0, close=50000.0, volume=1.0), # Missing: time=1709337660 Candle(time=1709337720, open=50000.0, high=50000.0, low=50000.0, close=50000.0, volume=1.0), ] await async_db_cache.put("binance", "BTC/USDT", "1m", candles) result, gaps = await async_db_cache.get( "binance", "BTC/USDT", "1m", start=1709337600, end=1709337720, ) assert len(result) == 2 # Should detect the gap for the single missing candle assert len(gaps) == 1 assert gaps[0] == (1709337660, 1709337660) # Single candle gap @pytest.mark.asyncio async def test_lazy_initialization(self): """Test that database is lazily initialized on first operation.""" with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f: db_path = f.name # Don't call initialize explicitly cache = AsyncDatabaseCache(db_path=db_path) assert cache._initialized is False # First operation should initialize candles = [ Candle(time=1709337600, open=50000.0, high=50000.0, low=50000.0, close=50000.0, volume=1.0), ] await cache.put("binance", "BTC/USDT", "1m", candles) assert cache._initialized is True # Cleanup os.unlink(db_path)