214 lines
7.9 KiB
Python
214 lines
7.9 KiB
Python
"""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)
|