exchange-data-manager/tests/test_completeness.py

300 lines
8.3 KiB
Python

"""Tests for completeness validation."""
import pytest
from unittest.mock import patch
from exchange_data_manager.candles.models import Candle, CandleRequest, RequestMode
from exchange_data_manager.cache.completeness import (
check_completeness,
find_missing_ranges,
CompletenessResult,
)
def make_candle(time: int, **kwargs) -> Candle:
"""Helper to create candles with defaults."""
return Candle(
time=time,
open=kwargs.get("open", 50000.0),
high=kwargs.get("high", 50100.0),
low=kwargs.get("low", 49900.0),
close=kwargs.get("close", 50050.0),
volume=kwargs.get("volume", 10.0),
)
class TestCheckCompletenessRangeMode:
"""Tests for RANGE mode completeness."""
def test_complete_range(self):
"""Test complete data for RANGE mode."""
# 3 candles at 1m intervals
candles = [
make_candle(1709337600), # Second 0
make_candle(1709337660), # Second 60
make_candle(1709337720), # Second 120
]
request = CandleRequest(
exchange="binance",
symbol="BTC/USDT",
timeframe="1m",
start=1709337600,
end=1709337720,
)
result = check_completeness(candles, request)
assert result.is_complete is True
assert result.actual_count == 3
def test_missing_at_start(self):
"""Test incomplete when missing data at start."""
# Missing first 3 candles - larger than interval + tolerance
candles = [
make_candle(1709337780), # Missing 0:00, 0:01, 0:02
make_candle(1709337840),
]
request = CandleRequest(
exchange="binance",
symbol="BTC/USDT",
timeframe="1m",
start=1709337600,
end=1709337840,
)
result = check_completeness(candles, request)
assert result.is_complete is False
assert result.missing_start_ms == 1709337600000
assert "start" in result.reason.lower()
def test_missing_at_end(self):
"""Test incomplete when missing data at end."""
# Missing last 3 candles - larger than interval + tolerance
candles = [
make_candle(1709337600),
make_candle(1709337660),
# Missing 0:02, 0:03, 0:04
]
request = CandleRequest(
exchange="binance",
symbol="BTC/USDT",
timeframe="1m",
start=1709337600,
end=1709337840, # 4 minutes later
)
result = check_completeness(candles, request)
assert result.is_complete is False
assert result.missing_end_ms == 1709337840000
assert "end" in result.reason.lower()
def test_empty_candles(self):
"""Test that empty candles returns incomplete."""
request = CandleRequest(
exchange="binance",
symbol="BTC/USDT",
timeframe="1m",
start=1709337600,
end=1709337720,
)
result = check_completeness([], request)
assert result.is_complete is False
assert result.missing_start_ms == 1709337600000
class TestCheckCompletenessLastNMode:
"""Tests for LAST_N mode completeness."""
def test_enough_candles(self):
"""Test complete when have enough candles."""
candles = [make_candle(1709337600 + i * 60) for i in range(100)]
request = CandleRequest(
exchange="binance",
symbol="BTC/USDT",
timeframe="1m",
limit=100,
)
result = check_completeness(candles, request)
assert result.is_complete is True
assert result.actual_count == 100
def test_not_enough_candles(self):
"""Test incomplete when don't have enough candles."""
candles = [make_candle(1709337600 + i * 60) for i in range(50)]
request = CandleRequest(
exchange="binance",
symbol="BTC/USDT",
timeframe="1m",
limit=100,
)
result = check_completeness(candles, request)
assert result.is_complete is False
assert result.expected_count == 100
assert result.actual_count == 50
def test_more_than_enough_candles(self):
"""Test complete when have more than enough candles."""
candles = [make_candle(1709337600 + i * 60) for i in range(150)]
request = CandleRequest(
exchange="binance",
symbol="BTC/USDT",
timeframe="1m",
limit=100,
)
result = check_completeness(candles, request)
assert result.is_complete is True
class TestCheckCompletenessSinceMode:
"""Tests for SINCE mode completeness."""
@patch("exchange_data_manager.cache.completeness.now_millis")
def test_complete_to_now(self, mock_now):
"""Test complete when data reaches current time."""
mock_now.return_value = 1709337720000 # 2 minutes after start
candles = [
make_candle(1709337600),
make_candle(1709337660),
make_candle(1709337720),
]
request = CandleRequest(
exchange="binance",
symbol="BTC/USDT",
timeframe="1m",
start=1709337600,
)
result = check_completeness(candles, request)
assert result.is_complete is True
class TestCheckCompletenessOpenMode:
"""Tests for OPEN mode completeness."""
@patch("exchange_data_manager.cache.completeness.now_millis")
def test_recent_data_complete(self, mock_now):
"""Test complete when data is recent."""
mock_now.return_value = 1709337720000
candles = [
make_candle(1709337660), # 1 minute ago
make_candle(1709337720), # Current
]
request = CandleRequest(
exchange="binance",
symbol="BTC/USDT",
timeframe="1m",
)
result = check_completeness(candles, request)
assert result.is_complete is True
@patch("exchange_data_manager.cache.completeness.now_millis")
def test_stale_data_incomplete(self, mock_now):
"""Test incomplete when data is stale."""
mock_now.return_value = 1709341200000 # Much later
candles = [
make_candle(1709337600), # Old data
make_candle(1709337660),
]
request = CandleRequest(
exchange="binance",
symbol="BTC/USDT",
timeframe="1m",
)
result = check_completeness(candles, request)
assert result.is_complete is False
assert "stale" in result.reason.lower()
class TestFindMissingRanges:
"""Tests for find_missing_ranges function."""
def test_no_missing(self):
"""Test no missing ranges when data is complete."""
candles = [
make_candle(1709337600),
make_candle(1709337660),
make_candle(1709337720),
]
request = CandleRequest(
exchange="binance",
symbol="BTC/USDT",
timeframe="1m",
start=1709337600,
end=1709337720,
)
missing = find_missing_ranges(candles, request)
assert len(missing) == 0
def test_gap_in_middle(self):
"""Test detects gap in middle of data."""
candles = [
make_candle(1709337600),
# Gap: 1709337660
make_candle(1709337720),
]
request = CandleRequest(
exchange="binance",
symbol="BTC/USDT",
timeframe="1m",
start=1709337600,
end=1709337720,
)
missing = find_missing_ranges(candles, request)
assert len(missing) == 1
assert missing[0][0] == 1709337660000
def test_multiple_gaps(self):
"""Test detects multiple gaps."""
candles = [
make_candle(1709337600),
# Gap: 1709337660
make_candle(1709337720),
# Gap: 1709337780
make_candle(1709337840),
]
request = CandleRequest(
exchange="binance",
symbol="BTC/USDT",
timeframe="1m",
start=1709337600,
end=1709337840,
)
missing = find_missing_ranges(candles, request)
assert len(missing) == 2