Persist job and ingestion warnings/errors for 7 days, surface a dismissible top-nav badge, treat stale OHLCV as a warning (e.g. ticker renames), and show market bar age on the ticker freshness chip.
90 lines
3.0 KiB
Python
90 lines
3.0 KiB
Python
"""Tests for the ingestion service — honest reporting of empty provider fetches."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import date, timedelta
|
|
|
|
import pytest
|
|
|
|
from app.models.ticker import Ticker
|
|
from app.providers.protocol import OHLCVData
|
|
from app.services import ingestion_service as svc
|
|
from tests.conftest import MockMarketDataProvider, _test_session_factory # type: ignore
|
|
|
|
|
|
@pytest.fixture
|
|
async def session():
|
|
async with _test_session_factory() as s:
|
|
yield s
|
|
|
|
|
|
async def _add_ticker(session, symbol: str) -> None:
|
|
session.add(Ticker(symbol=symbol))
|
|
await session.commit()
|
|
|
|
|
|
def _bars(symbol: str, n: int) -> list[OHLCVData]:
|
|
today = date.today()
|
|
return [
|
|
OHLCVData(ticker=symbol, date=today - timedelta(days=i),
|
|
open=100.0, high=101.0, low=99.0, close=100.0, volume=1000)
|
|
for i in range(n)
|
|
]
|
|
|
|
|
|
async def test_empty_fetch_on_new_ticker_reports_no_data(session):
|
|
# A non-US symbol (e.g. RHM/Rheinmetall) Alpaca doesn't cover → empty bars.
|
|
# This must NOT report success; it surfaces as no_data.
|
|
await _add_ticker(session, "RHM")
|
|
result = await svc.fetch_and_ingest(session, MockMarketDataProvider(ohlcv_data=[]), "RHM")
|
|
|
|
assert result.status == "no_data"
|
|
assert result.records_ingested == 0
|
|
assert "provider" in (result.message or "").lower()
|
|
|
|
|
|
async def test_happy_path_ingests_bars(session):
|
|
await _add_ticker(session, "AAA")
|
|
result = await svc.fetch_and_ingest(session, MockMarketDataProvider(ohlcv_data=_bars("AAA", 3)), "AAA")
|
|
|
|
assert result.status == "complete"
|
|
assert result.records_ingested == 3
|
|
|
|
|
|
async def test_empty_fetch_with_existing_history_is_up_to_date(session):
|
|
# Covered ticker, just no new bars in the window → complete, not no_data.
|
|
await _add_ticker(session, "BBB")
|
|
await svc.fetch_and_ingest(session, MockMarketDataProvider(ohlcv_data=_bars("BBB", 2)), "BBB")
|
|
|
|
result = await svc.fetch_and_ingest(session, MockMarketDataProvider(ohlcv_data=[]), "BBB")
|
|
|
|
assert result.status == "complete"
|
|
assert result.records_ingested == 0
|
|
|
|
|
|
async def test_empty_fetch_with_stale_history_reports_stale(session):
|
|
# Provider returns nothing but last stored bar is weeks old — classic
|
|
# rename/delist case (e.g. SATS after it became ECHO). Must not look like success.
|
|
await _add_ticker(session, "SATS")
|
|
today = date.today()
|
|
old = [
|
|
OHLCVData(
|
|
ticker="SATS",
|
|
date=today - timedelta(days=20 + i),
|
|
open=100.0,
|
|
high=101.0,
|
|
low=99.0,
|
|
close=100.0,
|
|
volume=1000,
|
|
)
|
|
for i in range(3)
|
|
]
|
|
await svc.fetch_and_ingest(session, MockMarketDataProvider(ohlcv_data=old), "SATS")
|
|
|
|
result = await svc.fetch_and_ingest(session, MockMarketDataProvider(ohlcv_data=[]), "SATS")
|
|
|
|
assert result.status == "stale"
|
|
assert result.records_ingested == 0
|
|
assert result.last_date is not None
|
|
assert "renamed" in (result.message or "").lower() or "halted" in (result.message or "").lower()
|