Build 060.14 — WebSocket Trade Adapter
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
# src/market_data/acquisition/adapters/dzengi/websocket_trade_adapter.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from src.market_data.acquisition.adapters.dzengi.mapper import (
|
||||
map_dzengi_websocket_trade_to_trade,
|
||||
)
|
||||
from src.market_data.acquisition.adapters.dzengi.parser import (
|
||||
parse_dzengi_websocket_trade,
|
||||
)
|
||||
from src.market_data.acquisition.models.trade import Trade
|
||||
from src.market_data.acquisition.validation.schema import (
|
||||
ValidatedWebSocketTradeDocument,
|
||||
)
|
||||
from src.market_data.acquisition.validation.values import (
|
||||
validate_dzengi_websocket_trade_values,
|
||||
)
|
||||
|
||||
|
||||
def adapt_websocket_trade_document(
|
||||
document: ValidatedWebSocketTradeDocument,
|
||||
) -> Trade:
|
||||
"""
|
||||
Преобразовать структурно проверенный документ Dzengi WebSocket Trade
|
||||
в каноническую immutable-модель Trade.
|
||||
|
||||
Функция последовательно выполняет parsing, value validation
|
||||
и mapping. Schema validation должна быть выполнена вызывающим слоем.
|
||||
Исключения отдельных этапов не перехватываются и не оборачиваются.
|
||||
"""
|
||||
|
||||
event = parse_dzengi_websocket_trade(document)
|
||||
|
||||
validate_dzengi_websocket_trade_values(event)
|
||||
|
||||
return map_dzengi_websocket_trade_to_trade(event)
|
||||
@@ -0,0 +1,162 @@
|
||||
# tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_trade_adapter.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from decimal import Decimal
|
||||
from types import MappingProxyType
|
||||
|
||||
import pytest
|
||||
|
||||
from src.market_data.acquisition.adapters.dzengi import (
|
||||
websocket_trade_adapter,
|
||||
)
|
||||
from src.market_data.acquisition.adapters.dzengi.websocket_trade_adapter import (
|
||||
adapt_websocket_trade_document,
|
||||
)
|
||||
from src.market_data.acquisition.exceptions import (
|
||||
TradeMappingError,
|
||||
TradeParseError,
|
||||
TradeValueError,
|
||||
)
|
||||
from src.market_data.acquisition.models.trade import TradeAggressorSide
|
||||
from src.market_data.acquisition.validation.schema import (
|
||||
ValidatedWebSocketTradeDocument,
|
||||
)
|
||||
|
||||
|
||||
def test_adapt_websocket_trade_document_returns_canonical_trade() -> None:
|
||||
document = ValidatedWebSocketTradeDocument(
|
||||
payload=MappingProxyType(
|
||||
{
|
||||
"buyer": True,
|
||||
"id": 101,
|
||||
"orderId": "00a02503-0079-54c4-0000-000081e62b58",
|
||||
"price": "43210.50",
|
||||
"size": "0.125",
|
||||
"symbol": " BTCUSDT ",
|
||||
"ts": 1_700_000_000_123,
|
||||
}
|
||||
),
|
||||
status="OK",
|
||||
destination="internal.trade",
|
||||
correlation_id=None,
|
||||
)
|
||||
|
||||
result = adapt_websocket_trade_document(document)
|
||||
|
||||
assert result.symbol == "BTCUSDT"
|
||||
assert result.trade_id == 101
|
||||
assert result.price == Decimal("43210.50")
|
||||
assert result.quantity == Decimal("0.125")
|
||||
assert result.executed_at == datetime(
|
||||
2023,
|
||||
11,
|
||||
14,
|
||||
22,
|
||||
13,
|
||||
20,
|
||||
123000,
|
||||
tzinfo=timezone.utc,
|
||||
)
|
||||
assert result.aggressor_side is TradeAggressorSide.BUY
|
||||
assert result.source == "dzengi_websocket_trade"
|
||||
|
||||
|
||||
def test_adapt_websocket_trade_document_propagates_parse_error(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
document = ValidatedWebSocketTradeDocument(
|
||||
payload=MappingProxyType({}),
|
||||
status="OK",
|
||||
destination="internal.trade",
|
||||
correlation_id=None,
|
||||
)
|
||||
expected_error = TradeParseError("test parse error")
|
||||
|
||||
def raise_parse_error(
|
||||
_: ValidatedWebSocketTradeDocument,
|
||||
) -> object:
|
||||
raise expected_error
|
||||
|
||||
monkeypatch.setattr(
|
||||
websocket_trade_adapter,
|
||||
"parse_dzengi_websocket_trade",
|
||||
raise_parse_error,
|
||||
)
|
||||
|
||||
with pytest.raises(TradeParseError) as exc_info:
|
||||
adapt_websocket_trade_document(document)
|
||||
|
||||
assert exc_info.value is expected_error
|
||||
|
||||
|
||||
def test_adapt_websocket_trade_document_propagates_value_error(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
document = ValidatedWebSocketTradeDocument(
|
||||
payload=MappingProxyType({}),
|
||||
status="OK",
|
||||
destination="internal.trade",
|
||||
correlation_id=None,
|
||||
)
|
||||
parsed_event = object()
|
||||
expected_error = TradeValueError("test value error")
|
||||
|
||||
monkeypatch.setattr(
|
||||
websocket_trade_adapter,
|
||||
"parse_dzengi_websocket_trade",
|
||||
lambda _: parsed_event,
|
||||
)
|
||||
|
||||
def raise_value_error(_: object) -> None:
|
||||
raise expected_error
|
||||
|
||||
monkeypatch.setattr(
|
||||
websocket_trade_adapter,
|
||||
"validate_dzengi_websocket_trade_values",
|
||||
raise_value_error,
|
||||
)
|
||||
|
||||
with pytest.raises(TradeValueError) as exc_info:
|
||||
adapt_websocket_trade_document(document)
|
||||
|
||||
assert exc_info.value is expected_error
|
||||
|
||||
|
||||
def test_adapt_websocket_trade_document_propagates_mapping_error(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
document = ValidatedWebSocketTradeDocument(
|
||||
payload=MappingProxyType({}),
|
||||
status="OK",
|
||||
destination="internal.trade",
|
||||
correlation_id=None,
|
||||
)
|
||||
parsed_event = object()
|
||||
expected_error = TradeMappingError("test mapping error")
|
||||
|
||||
monkeypatch.setattr(
|
||||
websocket_trade_adapter,
|
||||
"parse_dzengi_websocket_trade",
|
||||
lambda _: parsed_event,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
websocket_trade_adapter,
|
||||
"validate_dzengi_websocket_trade_values",
|
||||
lambda _: None,
|
||||
)
|
||||
|
||||
def raise_mapping_error(_: object) -> object:
|
||||
raise expected_error
|
||||
|
||||
monkeypatch.setattr(
|
||||
websocket_trade_adapter,
|
||||
"map_dzengi_websocket_trade_to_trade",
|
||||
raise_mapping_error,
|
||||
)
|
||||
|
||||
with pytest.raises(TradeMappingError) as exc_info:
|
||||
adapt_websocket_trade_document(document)
|
||||
|
||||
assert exc_info.value is expected_error
|
||||
Reference in New Issue
Block a user