build 042: remove MarketPriceCache compatibility facade
This commit is contained in:
@@ -1,74 +0,0 @@
|
||||
# app/tests/unit/integrations/exchange/test_market_cache.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from decimal import Decimal
|
||||
from typing import Iterator
|
||||
|
||||
import pytest
|
||||
|
||||
from src.integrations.exchange.market_cache import MarketPriceCache
|
||||
from src.market_data.acquisition.models.quote import Quote
|
||||
from src.storage.quote_store import InMemoryQuoteStore, QuoteStoreProtocol
|
||||
|
||||
|
||||
def _quote(symbol: str = "BTC/USD_LEVERAGE") -> Quote:
|
||||
return Quote(
|
||||
symbol=symbol,
|
||||
last_price=Decimal("100"),
|
||||
bid_price=Decimal("99"),
|
||||
ask_price=Decimal("101"),
|
||||
exchange_timestamp=None,
|
||||
received_at=datetime.now(timezone.utc),
|
||||
source="test",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def reset_market_price_cache() -> Iterator[None]:
|
||||
original_store = MarketPriceCache._store
|
||||
MarketPriceCache._store = InMemoryQuoteStore()
|
||||
yield
|
||||
MarketPriceCache._store = original_store
|
||||
|
||||
|
||||
def test_market_price_cache_uses_quote_store_protocol() -> None:
|
||||
assert isinstance(MarketPriceCache._store, QuoteStoreProtocol)
|
||||
|
||||
|
||||
def test_set_and_get_quote_preserve_identity() -> None:
|
||||
quote = _quote()
|
||||
MarketPriceCache.set_quote(quote, runtime_key="auto")
|
||||
assert MarketPriceCache.get_quote(quote.symbol, runtime_key="auto") is quote
|
||||
|
||||
|
||||
def test_symbol_and_runtime_key_are_normalized() -> None:
|
||||
quote = _quote("BTC/USD_LEVERAGE")
|
||||
MarketPriceCache.set_quote(quote, runtime_key=" AUTO ")
|
||||
assert MarketPriceCache.get_quote(" btc/usd_leverage ", runtime_key="auto") is quote
|
||||
|
||||
|
||||
def test_runtime_keys_are_isolated() -> None:
|
||||
auto = _quote()
|
||||
debug = Quote(
|
||||
symbol=auto.symbol, last_price=Decimal("200"), bid_price=Decimal("199"),
|
||||
ask_price=Decimal("201"), exchange_timestamp=None,
|
||||
received_at=datetime.now(timezone.utc), source="debug",
|
||||
)
|
||||
MarketPriceCache.set_quote(auto, runtime_key="auto")
|
||||
MarketPriceCache.set_quote(debug, runtime_key="debug_auto")
|
||||
assert MarketPriceCache.get_quote(auto.symbol, runtime_key="auto") is auto
|
||||
assert MarketPriceCache.get_quote(auto.symbol, runtime_key="debug_auto") is debug
|
||||
|
||||
|
||||
def test_clear_targeted_and_all() -> None:
|
||||
btc = _quote("BTC/USD_LEVERAGE")
|
||||
eth = _quote("ETH/USD_LEVERAGE")
|
||||
MarketPriceCache.set_quote(btc, runtime_key="auto")
|
||||
MarketPriceCache.set_quote(eth, runtime_key="auto")
|
||||
MarketPriceCache.clear(btc.symbol, runtime_key="auto")
|
||||
assert MarketPriceCache.get_quote(btc.symbol, runtime_key="auto") is None
|
||||
assert MarketPriceCache.get_quote(eth.symbol, runtime_key="auto") is eth
|
||||
MarketPriceCache.clear()
|
||||
assert MarketPriceCache.get_quote(eth.symbol, runtime_key="auto") is None
|
||||
@@ -205,14 +205,15 @@ def test_run_websocket_uses_canonical_quote_adapter(
|
||||
mapped_documents.append(document)
|
||||
return quote
|
||||
|
||||
class Cache:
|
||||
@classmethod
|
||||
def set_quote(
|
||||
cls,
|
||||
class Store:
|
||||
def set(
|
||||
self,
|
||||
source_name: str,
|
||||
value: Quote,
|
||||
*,
|
||||
runtime_key: str = "default",
|
||||
) -> None:
|
||||
assert source_name == runner_module.QUOTE_RUNTIME_SOURCE_NAME
|
||||
stored_quotes.append((value, runtime_key))
|
||||
|
||||
context = MarketRuntimeContext(
|
||||
@@ -247,7 +248,7 @@ def test_run_websocket_uses_canonical_quote_adapter(
|
||||
)
|
||||
monkeypatch.setattr(runner_module, "ExchangeWebSocketClient", Client)
|
||||
monkeypatch.setattr(runner_module, "DzengiWebSocketQuoteAdapter", Adapter)
|
||||
monkeypatch.setattr(runner_module, "MarketPriceCache", Cache)
|
||||
monkeypatch.setattr(runner_module, "get_quote_store", lambda: Store())
|
||||
|
||||
asyncio.run(
|
||||
MarketDataRunner._run_websocket(
|
||||
|
||||
@@ -357,14 +357,15 @@ def test_start_market_stream_maps_message_to_quote(
|
||||
mapped_documents.append(document)
|
||||
return quote
|
||||
|
||||
class Cache:
|
||||
@classmethod
|
||||
def set_quote(
|
||||
cls,
|
||||
class Store:
|
||||
def set(
|
||||
self,
|
||||
source_name: str,
|
||||
value: Quote,
|
||||
*,
|
||||
runtime_key: str = "default",
|
||||
) -> None:
|
||||
assert source_name == stream_module.QUOTE_RUNTIME_SOURCE_NAME
|
||||
stored_quotes.append((value, runtime_key))
|
||||
|
||||
monkeypatch.setattr(stream_module, "load_settings", lambda: settings)
|
||||
@@ -379,7 +380,7 @@ def test_start_market_stream_maps_message_to_quote(
|
||||
monkeypatch.setattr(stream_module, "ExchangeService", lambda: service)
|
||||
monkeypatch.setattr(stream_module, "ExchangeWebSocketClient", Client)
|
||||
monkeypatch.setattr(stream_module, "DzengiWebSocketQuoteAdapter", Adapter)
|
||||
monkeypatch.setattr(stream_module, "MarketPriceCache", Cache)
|
||||
monkeypatch.setattr(stream_module, "get_quote_store", lambda: Store())
|
||||
monkeypatch.setattr(stream_module.asyncio, "sleep", _raise_stop_stream)
|
||||
|
||||
with pytest.raises(StopStream):
|
||||
@@ -416,9 +417,8 @@ def test_start_market_stream_skips_invalid_adapter_message(
|
||||
del document
|
||||
raise QuoteSchemaError("Invalid message.")
|
||||
|
||||
class Cache:
|
||||
@classmethod
|
||||
def set_quote(cls, *args: object, **kwargs: object) -> None:
|
||||
class Store:
|
||||
def set(self, *args: object, **kwargs: object) -> None:
|
||||
nonlocal cache_calls
|
||||
cache_calls += 1
|
||||
|
||||
@@ -434,7 +434,7 @@ def test_start_market_stream_skips_invalid_adapter_message(
|
||||
monkeypatch.setattr(stream_module, "ExchangeService", lambda: service)
|
||||
monkeypatch.setattr(stream_module, "ExchangeWebSocketClient", Client)
|
||||
monkeypatch.setattr(stream_module, "DzengiWebSocketQuoteAdapter", Adapter)
|
||||
monkeypatch.setattr(stream_module, "MarketPriceCache", Cache)
|
||||
monkeypatch.setattr(stream_module, "get_quote_store", lambda: Store())
|
||||
monkeypatch.setattr(stream_module.asyncio, "sleep", _raise_stop_stream)
|
||||
|
||||
with pytest.raises(StopStream):
|
||||
|
||||
@@ -10,9 +10,10 @@ from typing import cast
|
||||
import pytest
|
||||
|
||||
from src.core.config import Settings
|
||||
from src.integrations.exchange.market_cache import MarketPriceCache
|
||||
import src.integrations.exchange.service as service_module
|
||||
from src.integrations.exchange.service import ExchangeService
|
||||
from src.market_data.acquisition.models.quote import Quote
|
||||
from src.storage.quote_store import QUOTE_RUNTIME_SOURCE_NAME
|
||||
|
||||
|
||||
def _service() -> ExchangeService:
|
||||
@@ -55,7 +56,13 @@ def test_execution_snapshot_reads_canonical_cached_quote(
|
||||
quote = _quote()
|
||||
|
||||
monkeypatch.setattr(service, "validate_symbol", lambda _: _valid_validation())
|
||||
monkeypatch.setattr(MarketPriceCache, "get_quote", lambda *_, **__: quote)
|
||||
class Store:
|
||||
def get(self, source_name, symbol, *, runtime_key="default"):
|
||||
assert source_name == QUOTE_RUNTIME_SOURCE_NAME
|
||||
del symbol, runtime_key
|
||||
return quote
|
||||
|
||||
monkeypatch.setattr(service_module, "get_quote_store", lambda: Store())
|
||||
monkeypatch.setattr(
|
||||
service,
|
||||
"_get_fresh_quote",
|
||||
@@ -89,13 +96,18 @@ def test_execution_snapshot_uses_fresh_quote_for_stale_cache(
|
||||
stored: list[tuple[Quote, str]] = []
|
||||
|
||||
monkeypatch.setattr(service, "validate_symbol", lambda _: _valid_validation())
|
||||
monkeypatch.setattr(MarketPriceCache, "get_quote", lambda *_, **__: stale)
|
||||
class Store:
|
||||
def get(self, source_name, symbol, *, runtime_key="default"):
|
||||
assert source_name == QUOTE_RUNTIME_SOURCE_NAME
|
||||
del symbol, runtime_key
|
||||
return stale
|
||||
|
||||
def set(self, source_name, quote, *, runtime_key="default"):
|
||||
assert source_name == QUOTE_RUNTIME_SOURCE_NAME
|
||||
stored.append((quote, runtime_key))
|
||||
|
||||
monkeypatch.setattr(service_module, "get_quote_store", lambda: Store())
|
||||
monkeypatch.setattr(service, "_get_fresh_quote", lambda _: fresh)
|
||||
monkeypatch.setattr(
|
||||
MarketPriceCache,
|
||||
"set_quote",
|
||||
lambda quote, *, runtime_key: stored.append((quote, runtime_key)),
|
||||
)
|
||||
|
||||
result = service.get_execution_snapshot(
|
||||
"BTC/USD_LEVERAGE",
|
||||
|
||||
@@ -12,10 +12,14 @@ import pytest
|
||||
import src.integrations.exchange.service as service_module
|
||||
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
|
||||
import src.storage.quote_store as quote_store_module
|
||||
from src.storage.quote_store import (
|
||||
InMemoryQuoteStore,
|
||||
QUOTE_RUNTIME_SOURCE_NAME,
|
||||
get_quote_store,
|
||||
)
|
||||
|
||||
|
||||
def _service(*, exchange_enabled: bool = True) -> ExchangeService:
|
||||
@@ -46,11 +50,13 @@ def _quote(*, age_seconds: float = 0.0) -> Quote:
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def reset_store():
|
||||
original = MarketPriceCache._store
|
||||
MarketPriceCache._store = InMemoryQuoteStore()
|
||||
def reset_store(monkeypatch: pytest.MonkeyPatch):
|
||||
monkeypatch.setattr(
|
||||
quote_store_module,
|
||||
"_SHARED_QUOTE_STORE",
|
||||
InMemoryQuoteStore(),
|
||||
)
|
||||
yield
|
||||
MarketPriceCache._store = original
|
||||
|
||||
|
||||
def test_get_quote_returns_fresh_cached_identity(
|
||||
@@ -58,7 +64,8 @@ def test_get_quote_returns_fresh_cached_identity(
|
||||
) -> None:
|
||||
service = _service()
|
||||
quote = _quote()
|
||||
MarketPriceCache.set_quote(quote, runtime_key="auto")
|
||||
get_quote_store().set(
|
||||
QUOTE_RUNTIME_SOURCE_NAME,quote, runtime_key="auto")
|
||||
monkeypatch.setattr(
|
||||
service,
|
||||
"validate_symbol",
|
||||
@@ -83,7 +90,8 @@ def test_get_quote_refreshes_stale_quote(
|
||||
service = _service()
|
||||
stale = _quote(age_seconds=10)
|
||||
fresh = _quote()
|
||||
MarketPriceCache.set_quote(stale, runtime_key="auto")
|
||||
get_quote_store().set(
|
||||
QUOTE_RUNTIME_SOURCE_NAME,stale, runtime_key="auto")
|
||||
monkeypatch.setattr(
|
||||
service,
|
||||
"validate_symbol",
|
||||
@@ -98,7 +106,8 @@ def test_get_quote_refreshes_stale_quote(
|
||||
result = service.get_quote(fresh.symbol, runtime_key="auto")
|
||||
|
||||
assert result is fresh
|
||||
assert MarketPriceCache.get_quote(fresh.symbol, runtime_key="auto") is fresh
|
||||
assert get_quote_store().get(
|
||||
QUOTE_RUNTIME_SOURCE_NAME,fresh.symbol, runtime_key="auto") is fresh
|
||||
|
||||
|
||||
def test_get_quote_wraps_acquisition_error(
|
||||
@@ -125,7 +134,8 @@ def test_refresh_quote_cache_forces_fresh_quote_and_preserves_identity(
|
||||
service = _service()
|
||||
cached = _quote()
|
||||
fresh = _quote()
|
||||
MarketPriceCache.set_quote(cached, runtime_key="auto")
|
||||
get_quote_store().set(
|
||||
QUOTE_RUNTIME_SOURCE_NAME,cached, runtime_key="auto")
|
||||
requested_symbols: list[str] = []
|
||||
|
||||
monkeypatch.setattr(
|
||||
@@ -147,7 +157,8 @@ def test_refresh_quote_cache_forces_fresh_quote_and_preserves_identity(
|
||||
assert requested_symbols == [" btc/usd_leverage "]
|
||||
assert result is fresh
|
||||
assert result is not cached
|
||||
assert MarketPriceCache.get_quote(fresh.symbol, runtime_key="auto") is fresh
|
||||
assert get_quote_store().get(
|
||||
QUOTE_RUNTIME_SOURCE_NAME,fresh.symbol, runtime_key="auto") is fresh
|
||||
|
||||
|
||||
def test_refresh_quote_cache_uses_default_runtime_key(
|
||||
@@ -169,7 +180,8 @@ def test_refresh_quote_cache_uses_default_runtime_key(
|
||||
result = service.refresh_quote_cache(fresh.symbol)
|
||||
|
||||
assert result is fresh
|
||||
assert MarketPriceCache.get_quote(fresh.symbol, runtime_key="auto") is fresh
|
||||
assert get_quote_store().get(
|
||||
QUOTE_RUNTIME_SOURCE_NAME,fresh.symbol, runtime_key="auto") is fresh
|
||||
|
||||
|
||||
def test_refresh_quote_cache_normalizes_runtime_key(
|
||||
@@ -195,7 +207,8 @@ def test_refresh_quote_cache_normalizes_runtime_key(
|
||||
|
||||
assert result is fresh
|
||||
assert (
|
||||
MarketPriceCache.get_quote(
|
||||
get_quote_store().get(
|
||||
QUOTE_RUNTIME_SOURCE_NAME,
|
||||
fresh.symbol,
|
||||
runtime_key="debug_auto",
|
||||
)
|
||||
@@ -234,7 +247,8 @@ def test_refresh_quote_cache_does_not_update_cache_on_acquisition_error(
|
||||
service = _service()
|
||||
cached = _quote()
|
||||
original = RuntimeError("unavailable")
|
||||
MarketPriceCache.set_quote(cached, runtime_key="auto")
|
||||
get_quote_store().set(
|
||||
QUOTE_RUNTIME_SOURCE_NAME,cached, runtime_key="auto")
|
||||
monkeypatch.setattr(
|
||||
service,
|
||||
"validate_symbol",
|
||||
@@ -255,7 +269,8 @@ def test_refresh_quote_cache_does_not_update_cache_on_acquisition_error(
|
||||
service.refresh_quote_cache(cached.symbol, runtime_key="auto")
|
||||
|
||||
assert exc_info.value.__cause__ is original
|
||||
assert MarketPriceCache.get_quote(cached.symbol, runtime_key="auto") is cached
|
||||
assert get_quote_store().get(
|
||||
QUOTE_RUNTIME_SOURCE_NAME,cached.symbol, runtime_key="auto") is cached
|
||||
|
||||
|
||||
def test_refresh_quote_cache_uses_and_stores_mock_quote(
|
||||
@@ -286,7 +301,8 @@ def test_refresh_quote_cache_uses_and_stores_mock_quote(
|
||||
|
||||
assert result is mock
|
||||
assert (
|
||||
MarketPriceCache.get_quote(
|
||||
get_quote_store().get(
|
||||
QUOTE_RUNTIME_SOURCE_NAME,
|
||||
mock.symbol,
|
||||
runtime_key="debug_auto",
|
||||
)
|
||||
|
||||
@@ -10,9 +10,12 @@ import pytest
|
||||
|
||||
from src.market_data.acquisition.models.quote import Quote
|
||||
from src.storage.exceptions import QuoteStoreError, StorageError
|
||||
import src.storage.quote_store as quote_store_module
|
||||
from src.storage.quote_store import (
|
||||
InMemoryQuoteStore,
|
||||
QUOTE_RUNTIME_SOURCE_NAME,
|
||||
QuoteStoreProtocol,
|
||||
get_quote_store,
|
||||
)
|
||||
|
||||
|
||||
@@ -446,3 +449,31 @@ def test_quote_store_error_inherits_storage_error() -> None:
|
||||
error = QuoteStoreError("test")
|
||||
|
||||
assert isinstance(error, StorageError)
|
||||
|
||||
|
||||
|
||||
|
||||
def test_shared_quote_store_matches_protocol() -> None:
|
||||
assert isinstance(get_quote_store(), QuoteStoreProtocol)
|
||||
|
||||
|
||||
def test_shared_quote_store_returns_same_instance() -> None:
|
||||
assert get_quote_store() is get_quote_store()
|
||||
|
||||
|
||||
def test_runtime_source_name_preserves_compatibility_namespace() -> None:
|
||||
assert QUOTE_RUNTIME_SOURCE_NAME == "legacy-market-price-cache"
|
||||
|
||||
|
||||
def test_shared_quote_store_can_be_replaced_in_test(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
replacement = InMemoryQuoteStore()
|
||||
|
||||
monkeypatch.setattr(
|
||||
quote_store_module,
|
||||
"_SHARED_QUOTE_STORE",
|
||||
replacement,
|
||||
)
|
||||
|
||||
assert get_quote_store() is replacement
|
||||
|
||||
Reference in New Issue
Block a user