Files
dzentra_bot/app/tests/unit/integrations/exchange/test_service_quote.py

119 lines
3.3 KiB
Python

# app/tests/unit/integrations/exchange/test_service_quote.py
from __future__ import annotations
from datetime import datetime, timedelta, timezone
from decimal import Decimal
from types import SimpleNamespace
from typing import cast
import pytest
from src.core.config import Settings
from src.integrations.exchange.exceptions import ExchangeError
from src.integrations.exchange.market_cache import MarketPriceCache
from src.integrations.exchange.service import ExchangeService
from src.market_data.acquisition.models.quote import Quote
from src.storage.quote_store import InMemoryQuoteStore
def _service(*, exchange_enabled: bool = True) -> ExchangeService:
service = object.__new__(ExchangeService)
service.settings = cast(
Settings,
SimpleNamespace(
exchange_enabled=exchange_enabled,
default_symbol="BTC/USD_LEVERAGE",
),
)
return service
def _quote(*, age_seconds: float = 0.0) -> Quote:
return Quote(
symbol="BTC/USD_LEVERAGE",
last_price=Decimal("100"),
bid_price=Decimal("99"),
ask_price=Decimal("101"),
exchange_timestamp=None,
received_at=(
datetime.now(timezone.utc)
- timedelta(seconds=age_seconds)
),
source="test",
)
@pytest.fixture(autouse=True)
def reset_store():
original = MarketPriceCache._store
MarketPriceCache._store = InMemoryQuoteStore()
yield
MarketPriceCache._store = original
def test_get_quote_returns_fresh_cached_identity(
monkeypatch: pytest.MonkeyPatch,
) -> None:
service = _service()
quote = _quote()
MarketPriceCache.set_quote(quote, runtime_key="auto")
monkeypatch.setattr(
service,
"validate_symbol",
lambda _: SimpleNamespace(
is_valid=True,
normalized_symbol=quote.symbol,
message="",
),
)
monkeypatch.setattr(
service,
"_get_fresh_quote",
lambda _: (_ for _ in ()).throw(AssertionError("REST not expected")),
)
assert service.get_quote(quote.symbol, runtime_key="auto") is quote
def test_get_quote_refreshes_stale_quote(
monkeypatch: pytest.MonkeyPatch,
) -> None:
service = _service()
stale = _quote(age_seconds=10)
fresh = _quote()
MarketPriceCache.set_quote(stale, runtime_key="auto")
monkeypatch.setattr(
service,
"validate_symbol",
lambda _: SimpleNamespace(
is_valid=True,
normalized_symbol=fresh.symbol,
message="",
),
)
monkeypatch.setattr(service, "_get_fresh_quote", lambda _: fresh)
result = service.get_quote(fresh.symbol, runtime_key="auto")
assert result is fresh
assert MarketPriceCache.get_quote(fresh.symbol, runtime_key="auto") is fresh
def test_get_quote_wraps_acquisition_error(
monkeypatch: pytest.MonkeyPatch,
) -> None:
service = _service()
original = RuntimeError("unavailable")
monkeypatch.setattr(
service,
"_load_quote_via_acquisition",
lambda _: (_ for _ in ()).throw(original),
)
monkeypatch.setattr(service, "_log_exchange_error", lambda **_: None)
with pytest.raises(ExchangeError) as exc_info:
service._get_fresh_quote("BTC/USD_LEVERAGE")
assert exc_info.value.__cause__ is original