Build 060.20_1: align Trade Stream state ownership
This commit is contained in:
@@ -21,22 +21,22 @@ from src.market_data.acquisition.models.trade import (
|
||||
Trade,
|
||||
TradeAggressorSide,
|
||||
)
|
||||
from src.market_data.acquisition.runtime.trade.trade_runtime_registry import (
|
||||
TradeRuntimeRegistry,
|
||||
from src.market_data.acquisition.consistency.trade_stream_state_store import (
|
||||
TradeStreamStateStore,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def runtime() -> TradeRuntimeRegistry:
|
||||
return TradeRuntimeRegistry()
|
||||
def state_store() -> TradeStreamStateStore:
|
||||
return TradeStreamStateStore()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def controller(
|
||||
runtime: TradeRuntimeRegistry,
|
||||
state_store: TradeStreamStateStore,
|
||||
) -> TradeStreamConsistencyController:
|
||||
return TradeStreamConsistencyController(
|
||||
runtime=runtime,
|
||||
state_store=state_store,
|
||||
)
|
||||
|
||||
|
||||
@@ -68,32 +68,32 @@ def _trade(
|
||||
|
||||
def test_creates_state_for_first_symbol(
|
||||
controller: TradeStreamConsistencyController,
|
||||
runtime: TradeRuntimeRegistry,
|
||||
state_store: TradeStreamStateStore,
|
||||
) -> None:
|
||||
trade = _trade(symbol="BTCUSD")
|
||||
|
||||
result = controller.accept(trade)
|
||||
|
||||
assert result == trade
|
||||
assert runtime.is_registered("consistency:BTCUSD")
|
||||
assert state_store.contains("BTCUSD")
|
||||
|
||||
|
||||
def test_registers_trade_stream_state(
|
||||
controller: TradeStreamConsistencyController,
|
||||
runtime: TradeRuntimeRegistry,
|
||||
state_store: TradeStreamStateStore,
|
||||
) -> None:
|
||||
controller.accept(
|
||||
_trade(symbol="BTCUSD"),
|
||||
)
|
||||
|
||||
state = runtime.get("consistency:BTCUSD")
|
||||
state = state_store.get("BTCUSD")
|
||||
|
||||
assert isinstance(state, TradeStreamState)
|
||||
|
||||
|
||||
def test_reuses_state_for_same_symbol(
|
||||
controller: TradeStreamConsistencyController,
|
||||
runtime: TradeRuntimeRegistry,
|
||||
state_store: TradeStreamStateStore,
|
||||
) -> None:
|
||||
first_trade = _trade(
|
||||
symbol="BTCUSD",
|
||||
@@ -105,10 +105,10 @@ def test_reuses_state_for_same_symbol(
|
||||
)
|
||||
|
||||
controller.accept(first_trade)
|
||||
first_state = runtime.get("consistency:BTCUSD")
|
||||
first_state = state_store.get("BTCUSD")
|
||||
|
||||
result = controller.accept(second_trade)
|
||||
second_state = runtime.get("consistency:BTCUSD")
|
||||
second_state = state_store.get("BTCUSD")
|
||||
|
||||
assert result == second_trade
|
||||
assert second_state is first_state
|
||||
@@ -116,7 +116,7 @@ def test_reuses_state_for_same_symbol(
|
||||
|
||||
def test_keeps_independent_state_per_symbol(
|
||||
controller: TradeStreamConsistencyController,
|
||||
runtime: TradeRuntimeRegistry,
|
||||
state_store: TradeStreamStateStore,
|
||||
) -> None:
|
||||
btc_trade = _trade(
|
||||
symbol="BTCUSD",
|
||||
@@ -130,8 +130,8 @@ def test_keeps_independent_state_per_symbol(
|
||||
btc_result = controller.accept(btc_trade)
|
||||
eth_result = controller.accept(eth_trade)
|
||||
|
||||
btc_state = runtime.get("consistency:BTCUSD")
|
||||
eth_state = runtime.get("consistency:ETHUSD")
|
||||
btc_state = state_store.get("BTCUSD")
|
||||
eth_state = state_store.get("ETHUSD")
|
||||
|
||||
assert btc_result == btc_trade
|
||||
assert eth_result == eth_trade
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
# app/tests/unit/market_data/acquisition/consistency/test_trade_stream_state_store.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from src.market_data.acquisition.consistency.trade_stream_state import (
|
||||
TradeStreamState,
|
||||
)
|
||||
from src.market_data.acquisition.consistency.trade_stream_state_store import (
|
||||
TradeStreamStateStore,
|
||||
)
|
||||
from src.market_data.acquisition.consistency.trade_stream_state_store_exceptions import (
|
||||
TradeStreamStateNotFoundError,
|
||||
)
|
||||
from src.market_data.acquisition.consistency.trade_stream_state_store_protocol import (
|
||||
TradeStreamStateStoreProtocol,
|
||||
)
|
||||
|
||||
|
||||
def test_store_implements_protocol() -> None:
|
||||
store = TradeStreamStateStore()
|
||||
|
||||
assert isinstance(
|
||||
store,
|
||||
TradeStreamStateStoreProtocol,
|
||||
)
|
||||
|
||||
|
||||
def test_store_is_empty_after_creation() -> None:
|
||||
store = TradeStreamStateStore()
|
||||
|
||||
assert store.contains("BTCUSD") is False
|
||||
|
||||
|
||||
def test_get_or_create_creates_trade_stream_state() -> None:
|
||||
store = TradeStreamStateStore()
|
||||
|
||||
state = store.get_or_create("BTCUSD")
|
||||
|
||||
assert isinstance(
|
||||
state,
|
||||
TradeStreamState,
|
||||
)
|
||||
assert state.symbol == "BTCUSD"
|
||||
assert store.contains("BTCUSD") is True
|
||||
|
||||
|
||||
def test_get_or_create_returns_same_instance_for_same_symbol() -> None:
|
||||
store = TradeStreamStateStore()
|
||||
|
||||
first_state = store.get_or_create("BTCUSD")
|
||||
second_state = store.get_or_create("BTCUSD")
|
||||
|
||||
assert second_state is first_state
|
||||
|
||||
|
||||
def test_get_or_create_returns_independent_states_for_different_symbols() -> None:
|
||||
store = TradeStreamStateStore()
|
||||
|
||||
btc_state = store.get_or_create("BTCUSD")
|
||||
eth_state = store.get_or_create("ETHUSD")
|
||||
|
||||
assert btc_state is not eth_state
|
||||
assert btc_state.symbol == "BTCUSD"
|
||||
assert eth_state.symbol == "ETHUSD"
|
||||
|
||||
|
||||
def test_get_returns_existing_state() -> None:
|
||||
store = TradeStreamStateStore()
|
||||
|
||||
created_state = store.get_or_create("BTCUSD")
|
||||
returned_state = store.get("BTCUSD")
|
||||
|
||||
assert returned_state is created_state
|
||||
|
||||
|
||||
def test_get_raises_not_found_error_for_missing_symbol() -> None:
|
||||
store = TradeStreamStateStore()
|
||||
|
||||
with pytest.raises(
|
||||
TradeStreamStateNotFoundError,
|
||||
match="BTCUSD",
|
||||
):
|
||||
store.get("BTCUSD")
|
||||
|
||||
|
||||
def test_remove_deletes_existing_state() -> None:
|
||||
store = TradeStreamStateStore()
|
||||
store.get_or_create("BTCUSD")
|
||||
|
||||
store.remove("BTCUSD")
|
||||
|
||||
assert store.contains("BTCUSD") is False
|
||||
|
||||
with pytest.raises(
|
||||
TradeStreamStateNotFoundError,
|
||||
match="BTCUSD",
|
||||
):
|
||||
store.get("BTCUSD")
|
||||
|
||||
|
||||
def test_remove_does_not_delete_other_symbol_state() -> None:
|
||||
store = TradeStreamStateStore()
|
||||
|
||||
store.get_or_create("BTCUSD")
|
||||
eth_state = store.get_or_create("ETHUSD")
|
||||
|
||||
store.remove("BTCUSD")
|
||||
|
||||
assert store.contains("BTCUSD") is False
|
||||
assert store.get("ETHUSD") is eth_state
|
||||
|
||||
|
||||
def test_remove_raises_not_found_error_for_missing_symbol() -> None:
|
||||
store = TradeStreamStateStore()
|
||||
|
||||
with pytest.raises(
|
||||
TradeStreamStateNotFoundError,
|
||||
match="BTCUSD",
|
||||
):
|
||||
store.remove("BTCUSD")
|
||||
|
||||
|
||||
def test_clear_removes_all_states() -> None:
|
||||
store = TradeStreamStateStore()
|
||||
|
||||
store.get_or_create("BTCUSD")
|
||||
store.get_or_create("ETHUSD")
|
||||
|
||||
store.clear()
|
||||
|
||||
assert store.contains("BTCUSD") is False
|
||||
assert store.contains("ETHUSD") is False
|
||||
|
||||
|
||||
def test_clear_is_idempotent_for_empty_store() -> None:
|
||||
store = TradeStreamStateStore()
|
||||
|
||||
store.clear()
|
||||
store.clear()
|
||||
|
||||
assert store.contains("BTCUSD") is False
|
||||
@@ -1,126 +0,0 @@
|
||||
# app/tests/unit/market_data/acquisition/runtime/trade/test_trade_runtime_registry.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from src.market_data.acquisition.runtime.trade.trade_runtime_exceptions import (
|
||||
InvalidRuntimeComponentError,
|
||||
RuntimeAlreadyRegisteredError,
|
||||
RuntimeNotRegisteredError,
|
||||
)
|
||||
from src.market_data.acquisition.runtime.trade.trade_runtime_registry import (
|
||||
TradeRuntimeRegistry,
|
||||
)
|
||||
|
||||
|
||||
class DummyComponent:
|
||||
"""Тестовый Runtime-компонент."""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def component() -> DummyComponent:
|
||||
"""Создаёт тестовый Runtime-компонент."""
|
||||
return DummyComponent()
|
||||
|
||||
|
||||
def test_registry_is_empty_after_creation() -> None:
|
||||
registry = TradeRuntimeRegistry()
|
||||
|
||||
assert registry.is_registered("component") is False
|
||||
|
||||
|
||||
def test_is_registered_returns_false_for_unknown_key() -> None:
|
||||
registry = TradeRuntimeRegistry()
|
||||
|
||||
assert registry.is_registered("unknown") is False
|
||||
|
||||
|
||||
def test_register_component(component: DummyComponent) -> None:
|
||||
registry = TradeRuntimeRegistry()
|
||||
|
||||
registry.register("component", component)
|
||||
|
||||
assert registry.is_registered("component") is True
|
||||
|
||||
|
||||
def test_get_returns_registered_component(
|
||||
component: DummyComponent,
|
||||
) -> None:
|
||||
registry = TradeRuntimeRegistry()
|
||||
|
||||
registry.register("component", component)
|
||||
|
||||
assert registry.get("component") is component
|
||||
|
||||
|
||||
def test_is_registered_returns_true_after_registration(
|
||||
component: DummyComponent,
|
||||
) -> None:
|
||||
registry = TradeRuntimeRegistry()
|
||||
|
||||
registry.register("component", component)
|
||||
|
||||
assert registry.is_registered("component") is True
|
||||
|
||||
|
||||
def test_register_none_raises_invalid_runtime_component_error() -> None:
|
||||
registry = TradeRuntimeRegistry()
|
||||
|
||||
with pytest.raises(InvalidRuntimeComponentError):
|
||||
registry.register("component", None)
|
||||
|
||||
|
||||
def test_register_duplicate_key_raises_runtime_already_registered_error(
|
||||
component: DummyComponent,
|
||||
) -> None:
|
||||
registry = TradeRuntimeRegistry()
|
||||
|
||||
registry.register("component", component)
|
||||
|
||||
with pytest.raises(RuntimeAlreadyRegisteredError):
|
||||
registry.register("component", DummyComponent())
|
||||
|
||||
|
||||
def test_unregister_component(
|
||||
component: DummyComponent,
|
||||
) -> None:
|
||||
registry = TradeRuntimeRegistry()
|
||||
|
||||
registry.register("component", component)
|
||||
registry.unregister("component")
|
||||
|
||||
assert registry.is_registered("component") is False
|
||||
|
||||
with pytest.raises(RuntimeNotRegisteredError):
|
||||
registry.get("component")
|
||||
|
||||
|
||||
def test_unregister_unknown_component_raises_runtime_not_registered_error() -> None:
|
||||
registry = TradeRuntimeRegistry()
|
||||
|
||||
with pytest.raises(RuntimeNotRegisteredError):
|
||||
registry.unregister("unknown")
|
||||
|
||||
|
||||
def test_clear_removes_all_components() -> None:
|
||||
registry = TradeRuntimeRegistry()
|
||||
|
||||
registry.register("component_1", DummyComponent())
|
||||
registry.register("component_2", DummyComponent())
|
||||
registry.register("component_3", DummyComponent())
|
||||
|
||||
registry.clear()
|
||||
|
||||
assert registry.is_registered("component_1") is False
|
||||
assert registry.is_registered("component_2") is False
|
||||
assert registry.is_registered("component_3") is False
|
||||
|
||||
with pytest.raises(RuntimeNotRegisteredError):
|
||||
registry.get("component_1")
|
||||
|
||||
with pytest.raises(RuntimeNotRegisteredError):
|
||||
registry.get("component_2")
|
||||
|
||||
with pytest.raises(RuntimeNotRegisteredError):
|
||||
registry.get("component_3")
|
||||
Reference in New Issue
Block a user