build 045: register canonical candles feed in acquisition registry
This commit is contained in:
@@ -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."
|
||||
|
||||
Reference in New Issue
Block a user