From 225f07bc4bc8d8dcc8f91a0cde8903e1dcab1126 Mon Sep 17 00:00:00 2001 From: Sergey Date: Tue, 14 Jul 2026 18:56:40 +0300 Subject: [PATCH] build 042: remove MarketPriceCache compatibility facade --- app/src/integrations/exchange/market_cache.py | 69 ----------------- .../exchange/market_data_runner.py | 13 +++- .../integrations/exchange/market_stream.py | 10 ++- app/src/integrations/exchange/service.py | 20 +++-- app/src/storage/quote_store.py | 11 +++ .../exchange/test_market_cache.py | 74 ------------------- .../exchange/test_market_data_runner.py | 11 +-- .../exchange/test_market_stream.py | 18 ++--- .../exchange/test_service_execution_quote.py | 28 +++++-- .../exchange/test_service_quote.py | 48 ++++++++---- app/tests/unit/storage/test_quote_store.py | 31 ++++++++ 11 files changed, 140 insertions(+), 193 deletions(-) delete mode 100644 app/src/integrations/exchange/market_cache.py delete mode 100644 app/tests/unit/integrations/exchange/test_market_cache.py diff --git a/app/src/integrations/exchange/market_cache.py b/app/src/integrations/exchange/market_cache.py deleted file mode 100644 index daac1a8..0000000 --- a/app/src/integrations/exchange/market_cache.py +++ /dev/null @@ -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() diff --git a/app/src/integrations/exchange/market_data_runner.py b/app/src/integrations/exchange/market_data_runner.py index 8e5204d..94acc1a 100644 --- a/app/src/integrations/exchange/market_data_runner.py +++ b/app/src/integrations/exchange/market_data_runner.py @@ -9,7 +9,6 @@ from dataclasses import dataclass from typing import Callable 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.ws_client import ExchangeWebSocketClient 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 ( MarketDataAcquisitionError, ) +from src.storage.quote_store import ( + QUOTE_RUNTIME_SOURCE_NAME, + get_quote_store, +) from src.trading.journal.service import JournalService @@ -178,7 +181,10 @@ class MarketDataRunner: runtime_key=context.runtime_key, cache_symbol=cache_symbol, ): - MarketPriceCache.clear(cache_symbol) + get_quote_store().clear( + source_name=QUOTE_RUNTIME_SOURCE_NAME, + symbol=cache_symbol, + ) try: market_status = ExchangeService().get_symbol_market_status(symbol) @@ -371,7 +377,8 @@ class MarketDataRunner: valid_payload_count += 1 - MarketPriceCache.set_quote( + get_quote_store().set( + QUOTE_RUNTIME_SOURCE_NAME, quote, runtime_key=context.runtime_key, ) diff --git a/app/src/integrations/exchange/market_stream.py b/app/src/integrations/exchange/market_stream.py index be578fc..3bebbfd 100644 --- a/app/src/integrations/exchange/market_stream.py +++ b/app/src/integrations/exchange/market_stream.py @@ -4,7 +4,6 @@ from __future__ import annotations import asyncio 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.ws_client import ExchangeWebSocketClient 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 ( MarketDataAcquisitionError, ) +from src.storage.quote_store import ( + QUOTE_RUNTIME_SOURCE_NAME, + get_quote_store, +) from src.trading.journal.service import JournalService -# запускает постоянный websocket-поток рынка и обновляет MarketPriceCache +# запускает постоянный websocket-поток рынка и обновляет Quote Store async def start_market_stream() -> None: settings = load_settings() journal = JournalService() @@ -52,7 +55,8 @@ async def start_market_stream() -> None: if quote.symbol.strip().upper() != symbol.strip().upper(): continue - MarketPriceCache.set_quote( + get_quote_store().set( + QUOTE_RUNTIME_SOURCE_NAME, quote, runtime_key="default", ) diff --git a/app/src/integrations/exchange/service.py b/app/src/integrations/exchange/service.py index 5389033..32f1352 100644 --- a/app/src/integrations/exchange/service.py +++ b/app/src/integrations/exchange/service.py @@ -12,7 +12,6 @@ from src.core.numbers import safe_float from src.core.types import NumericLike from src.integrations.exchange.balance_parser import parse_account_balances from src.integrations.exchange.exceptions import ExchangeError -from src.integrations.exchange.market_cache import MarketPriceCache from src.integrations.exchange.mock_data import ( mock_balance_summary, mock_exchange_health, @@ -71,6 +70,10 @@ from src.storage.instrument_store import ( InMemoryInstrumentStore, InstrumentStoreProtocol, ) +from src.storage.quote_store import ( + QUOTE_RUNTIME_SOURCE_NAME, + get_quote_store, +) from src.trading.journal.service import JournalService @@ -787,7 +790,8 @@ class ExchangeService: if not validation.is_valid: raise ExchangeError(validation.message) - cached_quote = MarketPriceCache.get_quote( + cached_quote = get_quote_store().get( + QUOTE_RUNTIME_SOURCE_NAME, validation.normalized_symbol, runtime_key=normalized_runtime_key, ) @@ -800,7 +804,8 @@ class ExchangeService: return cached_quote quote = self._get_fresh_quote(validation.normalized_symbol) - MarketPriceCache.set_quote( + get_quote_store().set( + QUOTE_RUNTIME_SOURCE_NAME, quote, runtime_key=normalized_runtime_key, ) @@ -827,7 +832,8 @@ class ExchangeService: validation.normalized_symbol ) - MarketPriceCache.set_quote( + get_quote_store().set( + QUOTE_RUNTIME_SOURCE_NAME, quote, runtime_key=normalized_runtime_key, ) @@ -855,7 +861,8 @@ class ExchangeService: if not validation.is_valid: raise ExchangeError(validation.message) - quote = MarketPriceCache.get_quote( + quote = get_quote_store().get( + QUOTE_RUNTIME_SOURCE_NAME, validation.normalized_symbol, runtime_key=normalized_runtime_key, ) @@ -873,7 +880,8 @@ class ExchangeService: quote = self._get_fresh_quote( validation.normalized_symbol ) - MarketPriceCache.set_quote( + get_quote_store().set( + QUOTE_RUNTIME_SOURCE_NAME, quote, runtime_key=normalized_runtime_key, ) diff --git a/app/src/storage/quote_store.py b/app/src/storage/quote_store.py index caca39b..5e341a5 100644 --- a/app/src/storage/quote_store.py +++ b/app/src/storage/quote_store.py @@ -213,3 +213,14 @@ class InMemoryQuoteStore: ) 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 + diff --git a/app/tests/unit/integrations/exchange/test_market_cache.py b/app/tests/unit/integrations/exchange/test_market_cache.py deleted file mode 100644 index 665bb7d..0000000 --- a/app/tests/unit/integrations/exchange/test_market_cache.py +++ /dev/null @@ -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 diff --git a/app/tests/unit/integrations/exchange/test_market_data_runner.py b/app/tests/unit/integrations/exchange/test_market_data_runner.py index 9e954e4..4b6b50e 100644 --- a/app/tests/unit/integrations/exchange/test_market_data_runner.py +++ b/app/tests/unit/integrations/exchange/test_market_data_runner.py @@ -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( diff --git a/app/tests/unit/integrations/exchange/test_market_stream.py b/app/tests/unit/integrations/exchange/test_market_stream.py index a25485c..87ba186 100644 --- a/app/tests/unit/integrations/exchange/test_market_stream.py +++ b/app/tests/unit/integrations/exchange/test_market_stream.py @@ -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): diff --git a/app/tests/unit/integrations/exchange/test_service_execution_quote.py b/app/tests/unit/integrations/exchange/test_service_execution_quote.py index f04edc6..7cb8068 100644 --- a/app/tests/unit/integrations/exchange/test_service_execution_quote.py +++ b/app/tests/unit/integrations/exchange/test_service_execution_quote.py @@ -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", diff --git a/app/tests/unit/integrations/exchange/test_service_quote.py b/app/tests/unit/integrations/exchange/test_service_quote.py index ad00149..d03c9eb 100644 --- a/app/tests/unit/integrations/exchange/test_service_quote.py +++ b/app/tests/unit/integrations/exchange/test_service_quote.py @@ -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", ) diff --git a/app/tests/unit/storage/test_quote_store.py b/app/tests/unit/storage/test_quote_store.py index 7e41f68..dfe1f78 100644 --- a/app/tests/unit/storage/test_quote_store.py +++ b/app/tests/unit/storage/test_quote_store.py @@ -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