build 042: remove MarketPriceCache compatibility facade

This commit is contained in:
2026-07-14 18:56:40 +03:00
parent b2f6377143
commit 225f07bc4b
11 changed files with 140 additions and 193 deletions

View File

@@ -1,69 +0,0 @@
# app/src/integrations/exchange/market_cache.py
from __future__ import annotations
from src.market_data.acquisition.models.quote import Quote
from src.storage.quote_store import InMemoryQuoteStore, QuoteStoreProtocol
_MARKET_PRICE_CACHE_SOURCE_NAME = "legacy-market-price-cache"
class MarketPriceCache:
# Временный compatibility facade над каноническим Quote Store.
_store: QuoteStoreProtocol = InMemoryQuoteStore()
@classmethod
def set_quote(
cls,
quote: Quote,
*,
runtime_key: str = "default",
) -> None:
cls._store.set(
_MARKET_PRICE_CACHE_SOURCE_NAME,
quote,
runtime_key=cls._normalize_runtime_key(runtime_key),
)
@classmethod
def get_quote(
cls,
symbol: str,
*,
runtime_key: str = "default",
) -> Quote | None:
return cls._store.get(
_MARKET_PRICE_CACHE_SOURCE_NAME,
cls._normalize_symbol(symbol),
runtime_key=cls._normalize_runtime_key(runtime_key),
)
@classmethod
def clear(
cls,
symbol: str | None = None,
*,
runtime_key: str | None = None,
) -> None:
cls._store.clear(
source_name=_MARKET_PRICE_CACHE_SOURCE_NAME,
symbol=(
cls._normalize_symbol(symbol)
if symbol is not None
else None
),
runtime_key=(
cls._normalize_runtime_key(runtime_key)
if runtime_key is not None
else None
),
)
@staticmethod
def _normalize_symbol(symbol: str) -> str:
return str(symbol).strip().upper()
@staticmethod
def _normalize_runtime_key(runtime_key: str) -> str:
return str(runtime_key).strip().lower()

View File

@@ -9,7 +9,6 @@ from dataclasses import dataclass
from typing import Callable from typing import Callable
from src.core.types import JsonDict from src.core.types import JsonDict
from src.integrations.exchange.market_cache import MarketPriceCache
from src.integrations.exchange.service import ExchangeService from src.integrations.exchange.service import ExchangeService
from src.integrations.exchange.ws_client import ExchangeWebSocketClient from src.integrations.exchange.ws_client import ExchangeWebSocketClient
from src.market_data.acquisition.adapters.dzengi.websocket import ( from src.market_data.acquisition.adapters.dzengi.websocket import (
@@ -18,6 +17,10 @@ from src.market_data.acquisition.adapters.dzengi.websocket import (
from src.market_data.acquisition.exceptions import ( from src.market_data.acquisition.exceptions import (
MarketDataAcquisitionError, MarketDataAcquisitionError,
) )
from src.storage.quote_store import (
QUOTE_RUNTIME_SOURCE_NAME,
get_quote_store,
)
from src.trading.journal.service import JournalService from src.trading.journal.service import JournalService
@@ -178,7 +181,10 @@ class MarketDataRunner:
runtime_key=context.runtime_key, runtime_key=context.runtime_key,
cache_symbol=cache_symbol, cache_symbol=cache_symbol,
): ):
MarketPriceCache.clear(cache_symbol) get_quote_store().clear(
source_name=QUOTE_RUNTIME_SOURCE_NAME,
symbol=cache_symbol,
)
try: try:
market_status = ExchangeService().get_symbol_market_status(symbol) market_status = ExchangeService().get_symbol_market_status(symbol)
@@ -371,7 +377,8 @@ class MarketDataRunner:
valid_payload_count += 1 valid_payload_count += 1
MarketPriceCache.set_quote( get_quote_store().set(
QUOTE_RUNTIME_SOURCE_NAME,
quote, quote,
runtime_key=context.runtime_key, runtime_key=context.runtime_key,
) )

View File

@@ -4,7 +4,6 @@ from __future__ import annotations
import asyncio import asyncio
from src.core.config import load_settings from src.core.config import load_settings
from src.integrations.exchange.market_cache import MarketPriceCache
from src.integrations.exchange.service import ExchangeService from src.integrations.exchange.service import ExchangeService
from src.integrations.exchange.ws_client import ExchangeWebSocketClient from src.integrations.exchange.ws_client import ExchangeWebSocketClient
from src.market_data.acquisition.adapters.dzengi.websocket import ( from src.market_data.acquisition.adapters.dzengi.websocket import (
@@ -13,10 +12,14 @@ from src.market_data.acquisition.adapters.dzengi.websocket import (
from src.market_data.acquisition.exceptions import ( from src.market_data.acquisition.exceptions import (
MarketDataAcquisitionError, MarketDataAcquisitionError,
) )
from src.storage.quote_store import (
QUOTE_RUNTIME_SOURCE_NAME,
get_quote_store,
)
from src.trading.journal.service import JournalService from src.trading.journal.service import JournalService
# запускает постоянный websocket-поток рынка и обновляет MarketPriceCache # запускает постоянный websocket-поток рынка и обновляет Quote Store
async def start_market_stream() -> None: async def start_market_stream() -> None:
settings = load_settings() settings = load_settings()
journal = JournalService() journal = JournalService()
@@ -52,7 +55,8 @@ async def start_market_stream() -> None:
if quote.symbol.strip().upper() != symbol.strip().upper(): if quote.symbol.strip().upper() != symbol.strip().upper():
continue continue
MarketPriceCache.set_quote( get_quote_store().set(
QUOTE_RUNTIME_SOURCE_NAME,
quote, quote,
runtime_key="default", runtime_key="default",
) )

View File

@@ -12,7 +12,6 @@ from src.core.numbers import safe_float
from src.core.types import NumericLike from src.core.types import NumericLike
from src.integrations.exchange.balance_parser import parse_account_balances from src.integrations.exchange.balance_parser import parse_account_balances
from src.integrations.exchange.exceptions import ExchangeError from src.integrations.exchange.exceptions import ExchangeError
from src.integrations.exchange.market_cache import MarketPriceCache
from src.integrations.exchange.mock_data import ( from src.integrations.exchange.mock_data import (
mock_balance_summary, mock_balance_summary,
mock_exchange_health, mock_exchange_health,
@@ -71,6 +70,10 @@ from src.storage.instrument_store import (
InMemoryInstrumentStore, InMemoryInstrumentStore,
InstrumentStoreProtocol, InstrumentStoreProtocol,
) )
from src.storage.quote_store import (
QUOTE_RUNTIME_SOURCE_NAME,
get_quote_store,
)
from src.trading.journal.service import JournalService from src.trading.journal.service import JournalService
@@ -787,7 +790,8 @@ class ExchangeService:
if not validation.is_valid: if not validation.is_valid:
raise ExchangeError(validation.message) raise ExchangeError(validation.message)
cached_quote = MarketPriceCache.get_quote( cached_quote = get_quote_store().get(
QUOTE_RUNTIME_SOURCE_NAME,
validation.normalized_symbol, validation.normalized_symbol,
runtime_key=normalized_runtime_key, runtime_key=normalized_runtime_key,
) )
@@ -800,7 +804,8 @@ class ExchangeService:
return cached_quote return cached_quote
quote = self._get_fresh_quote(validation.normalized_symbol) quote = self._get_fresh_quote(validation.normalized_symbol)
MarketPriceCache.set_quote( get_quote_store().set(
QUOTE_RUNTIME_SOURCE_NAME,
quote, quote,
runtime_key=normalized_runtime_key, runtime_key=normalized_runtime_key,
) )
@@ -827,7 +832,8 @@ class ExchangeService:
validation.normalized_symbol validation.normalized_symbol
) )
MarketPriceCache.set_quote( get_quote_store().set(
QUOTE_RUNTIME_SOURCE_NAME,
quote, quote,
runtime_key=normalized_runtime_key, runtime_key=normalized_runtime_key,
) )
@@ -855,7 +861,8 @@ class ExchangeService:
if not validation.is_valid: if not validation.is_valid:
raise ExchangeError(validation.message) raise ExchangeError(validation.message)
quote = MarketPriceCache.get_quote( quote = get_quote_store().get(
QUOTE_RUNTIME_SOURCE_NAME,
validation.normalized_symbol, validation.normalized_symbol,
runtime_key=normalized_runtime_key, runtime_key=normalized_runtime_key,
) )
@@ -873,7 +880,8 @@ class ExchangeService:
quote = self._get_fresh_quote( quote = self._get_fresh_quote(
validation.normalized_symbol validation.normalized_symbol
) )
MarketPriceCache.set_quote( get_quote_store().set(
QUOTE_RUNTIME_SOURCE_NAME,
quote, quote,
runtime_key=normalized_runtime_key, runtime_key=normalized_runtime_key,
) )

View File

@@ -213,3 +213,14 @@ class InMemoryQuoteStore:
) )
return normalized_symbol return normalized_symbol
QUOTE_RUNTIME_SOURCE_NAME = "legacy-market-price-cache"
_SHARED_QUOTE_STORE: QuoteStoreProtocol = InMemoryQuoteStore()
def get_quote_store() -> QuoteStoreProtocol:
"""Вернуть общий runtime-экземпляр канонического Quote Store."""
return _SHARED_QUOTE_STORE

View File

@@ -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

View File

@@ -205,14 +205,15 @@ def test_run_websocket_uses_canonical_quote_adapter(
mapped_documents.append(document) mapped_documents.append(document)
return quote return quote
class Cache: class Store:
@classmethod def set(
def set_quote( self,
cls, source_name: str,
value: Quote, value: Quote,
*, *,
runtime_key: str = "default", runtime_key: str = "default",
) -> None: ) -> None:
assert source_name == runner_module.QUOTE_RUNTIME_SOURCE_NAME
stored_quotes.append((value, runtime_key)) stored_quotes.append((value, runtime_key))
context = MarketRuntimeContext( context = MarketRuntimeContext(
@@ -247,7 +248,7 @@ def test_run_websocket_uses_canonical_quote_adapter(
) )
monkeypatch.setattr(runner_module, "ExchangeWebSocketClient", Client) monkeypatch.setattr(runner_module, "ExchangeWebSocketClient", Client)
monkeypatch.setattr(runner_module, "DzengiWebSocketQuoteAdapter", Adapter) monkeypatch.setattr(runner_module, "DzengiWebSocketQuoteAdapter", Adapter)
monkeypatch.setattr(runner_module, "MarketPriceCache", Cache) monkeypatch.setattr(runner_module, "get_quote_store", lambda: Store())
asyncio.run( asyncio.run(
MarketDataRunner._run_websocket( MarketDataRunner._run_websocket(

View File

@@ -357,14 +357,15 @@ def test_start_market_stream_maps_message_to_quote(
mapped_documents.append(document) mapped_documents.append(document)
return quote return quote
class Cache: class Store:
@classmethod def set(
def set_quote( self,
cls, source_name: str,
value: Quote, value: Quote,
*, *,
runtime_key: str = "default", runtime_key: str = "default",
) -> None: ) -> None:
assert source_name == stream_module.QUOTE_RUNTIME_SOURCE_NAME
stored_quotes.append((value, runtime_key)) stored_quotes.append((value, runtime_key))
monkeypatch.setattr(stream_module, "load_settings", lambda: settings) 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, "ExchangeService", lambda: service)
monkeypatch.setattr(stream_module, "ExchangeWebSocketClient", Client) monkeypatch.setattr(stream_module, "ExchangeWebSocketClient", Client)
monkeypatch.setattr(stream_module, "DzengiWebSocketQuoteAdapter", Adapter) 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) monkeypatch.setattr(stream_module.asyncio, "sleep", _raise_stop_stream)
with pytest.raises(StopStream): with pytest.raises(StopStream):
@@ -416,9 +417,8 @@ def test_start_market_stream_skips_invalid_adapter_message(
del document del document
raise QuoteSchemaError("Invalid message.") raise QuoteSchemaError("Invalid message.")
class Cache: class Store:
@classmethod def set(self, *args: object, **kwargs: object) -> None:
def set_quote(cls, *args: object, **kwargs: object) -> None:
nonlocal cache_calls nonlocal cache_calls
cache_calls += 1 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, "ExchangeService", lambda: service)
monkeypatch.setattr(stream_module, "ExchangeWebSocketClient", Client) monkeypatch.setattr(stream_module, "ExchangeWebSocketClient", Client)
monkeypatch.setattr(stream_module, "DzengiWebSocketQuoteAdapter", Adapter) 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) monkeypatch.setattr(stream_module.asyncio, "sleep", _raise_stop_stream)
with pytest.raises(StopStream): with pytest.raises(StopStream):

View File

@@ -10,9 +10,10 @@ from typing import cast
import pytest import pytest
from src.core.config import Settings 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.integrations.exchange.service import ExchangeService
from src.market_data.acquisition.models.quote import Quote from src.market_data.acquisition.models.quote import Quote
from src.storage.quote_store import QUOTE_RUNTIME_SOURCE_NAME
def _service() -> ExchangeService: def _service() -> ExchangeService:
@@ -55,7 +56,13 @@ def test_execution_snapshot_reads_canonical_cached_quote(
quote = _quote() quote = _quote()
monkeypatch.setattr(service, "validate_symbol", lambda _: _valid_validation()) 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( monkeypatch.setattr(
service, service,
"_get_fresh_quote", "_get_fresh_quote",
@@ -89,13 +96,18 @@ def test_execution_snapshot_uses_fresh_quote_for_stale_cache(
stored: list[tuple[Quote, str]] = [] stored: list[tuple[Quote, str]] = []
monkeypatch.setattr(service, "validate_symbol", lambda _: _valid_validation()) 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(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( result = service.get_execution_snapshot(
"BTC/USD_LEVERAGE", "BTC/USD_LEVERAGE",

View File

@@ -12,10 +12,14 @@ import pytest
import src.integrations.exchange.service as service_module import src.integrations.exchange.service as service_module
from src.core.config import Settings from src.core.config import Settings
from src.integrations.exchange.exceptions import ExchangeError from src.integrations.exchange.exceptions import ExchangeError
from src.integrations.exchange.market_cache import MarketPriceCache
from src.integrations.exchange.service import ExchangeService from src.integrations.exchange.service import ExchangeService
from src.market_data.acquisition.models.quote import Quote 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: def _service(*, exchange_enabled: bool = True) -> ExchangeService:
@@ -46,11 +50,13 @@ def _quote(*, age_seconds: float = 0.0) -> Quote:
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def reset_store(): def reset_store(monkeypatch: pytest.MonkeyPatch):
original = MarketPriceCache._store monkeypatch.setattr(
MarketPriceCache._store = InMemoryQuoteStore() quote_store_module,
"_SHARED_QUOTE_STORE",
InMemoryQuoteStore(),
)
yield yield
MarketPriceCache._store = original
def test_get_quote_returns_fresh_cached_identity( def test_get_quote_returns_fresh_cached_identity(
@@ -58,7 +64,8 @@ def test_get_quote_returns_fresh_cached_identity(
) -> None: ) -> None:
service = _service() service = _service()
quote = _quote() quote = _quote()
MarketPriceCache.set_quote(quote, runtime_key="auto") get_quote_store().set(
QUOTE_RUNTIME_SOURCE_NAME,quote, runtime_key="auto")
monkeypatch.setattr( monkeypatch.setattr(
service, service,
"validate_symbol", "validate_symbol",
@@ -83,7 +90,8 @@ def test_get_quote_refreshes_stale_quote(
service = _service() service = _service()
stale = _quote(age_seconds=10) stale = _quote(age_seconds=10)
fresh = _quote() fresh = _quote()
MarketPriceCache.set_quote(stale, runtime_key="auto") get_quote_store().set(
QUOTE_RUNTIME_SOURCE_NAME,stale, runtime_key="auto")
monkeypatch.setattr( monkeypatch.setattr(
service, service,
"validate_symbol", "validate_symbol",
@@ -98,7 +106,8 @@ def test_get_quote_refreshes_stale_quote(
result = service.get_quote(fresh.symbol, runtime_key="auto") result = service.get_quote(fresh.symbol, runtime_key="auto")
assert result is fresh 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( def test_get_quote_wraps_acquisition_error(
@@ -125,7 +134,8 @@ def test_refresh_quote_cache_forces_fresh_quote_and_preserves_identity(
service = _service() service = _service()
cached = _quote() cached = _quote()
fresh = _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] = [] requested_symbols: list[str] = []
monkeypatch.setattr( monkeypatch.setattr(
@@ -147,7 +157,8 @@ def test_refresh_quote_cache_forces_fresh_quote_and_preserves_identity(
assert requested_symbols == [" btc/usd_leverage "] assert requested_symbols == [" btc/usd_leverage "]
assert result is fresh assert result is fresh
assert result is not cached 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( 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) result = service.refresh_quote_cache(fresh.symbol)
assert result is fresh 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( 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 result is fresh
assert ( assert (
MarketPriceCache.get_quote( get_quote_store().get(
QUOTE_RUNTIME_SOURCE_NAME,
fresh.symbol, fresh.symbol,
runtime_key="debug_auto", runtime_key="debug_auto",
) )
@@ -234,7 +247,8 @@ def test_refresh_quote_cache_does_not_update_cache_on_acquisition_error(
service = _service() service = _service()
cached = _quote() cached = _quote()
original = RuntimeError("unavailable") original = RuntimeError("unavailable")
MarketPriceCache.set_quote(cached, runtime_key="auto") get_quote_store().set(
QUOTE_RUNTIME_SOURCE_NAME,cached, runtime_key="auto")
monkeypatch.setattr( monkeypatch.setattr(
service, service,
"validate_symbol", "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") service.refresh_quote_cache(cached.symbol, runtime_key="auto")
assert exc_info.value.__cause__ is original 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( 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 result is mock
assert ( assert (
MarketPriceCache.get_quote( get_quote_store().get(
QUOTE_RUNTIME_SOURCE_NAME,
mock.symbol, mock.symbol,
runtime_key="debug_auto", runtime_key="debug_auto",
) )

View File

@@ -10,9 +10,12 @@ import pytest
from src.market_data.acquisition.models.quote import Quote from src.market_data.acquisition.models.quote import Quote
from src.storage.exceptions import QuoteStoreError, StorageError from src.storage.exceptions import QuoteStoreError, StorageError
import src.storage.quote_store as quote_store_module
from src.storage.quote_store import ( from src.storage.quote_store import (
InMemoryQuoteStore, InMemoryQuoteStore,
QUOTE_RUNTIME_SOURCE_NAME,
QuoteStoreProtocol, QuoteStoreProtocol,
get_quote_store,
) )
@@ -446,3 +449,31 @@ def test_quote_store_error_inherits_storage_error() -> None:
error = QuoteStoreError("test") error = QuoteStoreError("test")
assert isinstance(error, StorageError) 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