build 045: register canonical candles feed in acquisition registry

This commit is contained in:
2026-07-15 13:25:04 +03:00
parent 47c75d68b1
commit 0e86cca19d
4 changed files with 1022 additions and 1 deletions

View File

@@ -431,3 +431,260 @@ def test_quote_registry_error_inherits_acquisition_error() -> None:
error = QuoteFeedRegistryError("Registry error.")
assert isinstance(error, MarketDataAcquisitionError)
# Candles Feed Registry tests.
from datetime import datetime
from src.market_data.acquisition.exceptions import CandleFeedRegistryError
from src.market_data.acquisition.models.candle import Candle
from src.market_data.acquisition.protocol import CandlesFeedProtocol
from src.market_data.acquisition.registry import CandlesFeedRegistry
def _candle() -> Candle:
return Candle(
symbol="BTC/USD_LEVERAGE",
interval="1m",
open_time=datetime.now(timezone.utc),
open_price=Decimal("64100.00"),
high_price=Decimal("64200.00"),
low_price=Decimal("64000.00"),
close_price=Decimal("64150.00"),
volume=Decimal("12.5"),
source="rest_klines:bid",
)
class StubCandlesFeed:
def __init__(
self,
*,
candles: tuple[Candle, ...] = (),
) -> None:
self.candles = candles
self.calls: list[tuple[str, str, int, str]] = []
def load_candles(
self,
symbol: str,
*,
interval: str,
limit: int,
price_type: str,
) -> tuple[Candle, ...]:
self.calls.append(
(
symbol,
interval,
limit,
price_type,
)
)
return self.candles
def test_candles_registry_registers_and_returns_feed() -> None:
registry = CandlesFeedRegistry()
feed = StubCandlesFeed()
registry.register("dzengi", feed)
assert registry.get("dzengi") is feed
def test_candles_registry_accepts_candles_feed_protocol() -> None:
registry = CandlesFeedRegistry()
feed = StubCandlesFeed()
assert isinstance(feed, CandlesFeedProtocol)
registry.register("dzengi", feed)
assert registry.get("dzengi") is feed
def test_candles_registry_preserves_feed_identity() -> None:
registry = CandlesFeedRegistry()
candles = (_candle(),)
feed = StubCandlesFeed(candles=candles)
registry.register("dzengi", feed)
registered_feed = registry.get("dzengi")
assert registered_feed is feed
assert registered_feed.load_candles(
"BTC/USD_LEVERAGE",
interval="1m",
limit=100,
price_type="bid",
) is candles
def test_candles_registry_supports_multiple_source_names() -> None:
registry = CandlesFeedRegistry()
dzengi_feed = StubCandlesFeed()
secondary_feed = StubCandlesFeed()
registry.register("dzengi", dzengi_feed)
registry.register("secondary", secondary_feed)
assert registry.get("dzengi") is dzengi_feed
assert registry.get("secondary") is secondary_feed
def test_candles_registry_strips_outer_whitespace() -> None:
registry = CandlesFeedRegistry()
feed = StubCandlesFeed()
registry.register(" dzengi ", feed)
assert registry.get("dzengi") is feed
assert registry.get(" dzengi ") is feed
@pytest.mark.parametrize(
"source_name",
[
"",
" ",
" ",
"\t",
"\n",
],
)
def test_candles_registry_rejects_empty_source_name(
source_name: str,
) -> None:
registry = CandlesFeedRegistry()
with pytest.raises(
CandleFeedRegistryError,
match=r"Имя источника Candles Feed не должно быть пустым",
):
registry.register(source_name, StubCandlesFeed())
@pytest.mark.parametrize(
"source_name",
[
"",
" ",
" ",
"\t",
"\n",
],
)
def test_candles_registry_rejects_empty_source_name_on_get(
source_name: str,
) -> None:
registry = CandlesFeedRegistry()
with pytest.raises(
CandleFeedRegistryError,
match=r"Имя источника Candles Feed не должно быть пустым",
):
registry.get(source_name)
def test_candles_registry_rejects_duplicate_registration() -> None:
registry = CandlesFeedRegistry()
first_feed = StubCandlesFeed()
second_feed = StubCandlesFeed()
registry.register("dzengi", first_feed)
with pytest.raises(
CandleFeedRegistryError,
match=r"уже зарегистрирован",
):
registry.register("dzengi", second_feed)
def test_candles_duplicate_does_not_replace_original_feed() -> None:
registry = CandlesFeedRegistry()
first_feed = StubCandlesFeed()
second_feed = StubCandlesFeed()
registry.register("dzengi", first_feed)
with pytest.raises(CandleFeedRegistryError):
registry.register("dzengi", second_feed)
assert registry.get("dzengi") is first_feed
def test_candles_duplicate_uses_normalized_source_name() -> None:
registry = CandlesFeedRegistry()
first_feed = StubCandlesFeed()
second_feed = StubCandlesFeed()
registry.register("dzengi", first_feed)
with pytest.raises(
CandleFeedRegistryError,
match=r"уже зарегистрирован",
):
registry.register(" dzengi ", second_feed)
def test_candles_registry_keeps_source_name_case_sensitive() -> None:
registry = CandlesFeedRegistry()
lowercase_feed = StubCandlesFeed()
uppercase_feed = StubCandlesFeed()
registry.register("dzengi", lowercase_feed)
registry.register("DZENGI", uppercase_feed)
assert registry.get("dzengi") is lowercase_feed
assert registry.get("DZENGI") is uppercase_feed
def test_candles_registry_rejects_unregistered_source() -> None:
registry = CandlesFeedRegistry()
with pytest.raises(
CandleFeedRegistryError,
match=r"не зарегистрирован",
):
registry.get("dzengi")
def test_candles_registry_rejects_invalid_feed() -> None:
registry = CandlesFeedRegistry()
with pytest.raises(
CandleFeedRegistryError,
match=r"не соответствует CandlesFeedProtocol",
):
registry.register(
"invalid",
InvalidFeed(), # type: ignore[arg-type]
)
def test_candles_registry_does_not_load_feed_during_registration() -> None:
registry = CandlesFeedRegistry()
feed = StubCandlesFeed()
registry.register("dzengi", feed)
assert feed.calls == []
def test_candles_registry_does_not_load_feed_during_get() -> None:
registry = CandlesFeedRegistry()
feed = StubCandlesFeed()
registry.register("dzengi", feed)
result = registry.get("dzengi")
assert result is feed
assert feed.calls == []
def test_candles_registry_error_inherits_acquisition_error() -> None:
error = CandleFeedRegistryError("Registry error.")
assert isinstance(error, MarketDataAcquisitionError)
assert str(error) == "Registry error."