Build 060.12: add WebSocket Trade Value Validation
This commit is contained in:
@@ -18,6 +18,7 @@ from src.market_data.acquisition.adapters.dzengi.models import (
|
||||
DzengiTicker24hrResponse,
|
||||
DzengiWebSocketOhlcEvent,
|
||||
DzengiWebSocketQuoteResponse,
|
||||
DzengiWebSocketTradeEvent,
|
||||
)
|
||||
from src.market_data.acquisition.exceptions import (
|
||||
CandleValueError,
|
||||
@@ -820,6 +821,44 @@ def _candle_decimal(
|
||||
return decimal_value
|
||||
|
||||
|
||||
def validate_dzengi_websocket_trade_values(
|
||||
event: DzengiWebSocketTradeEvent,
|
||||
) -> None:
|
||||
"""
|
||||
Проверить допустимость значений transport-модели Dzengi WebSocket Trade.
|
||||
|
||||
Функция не изменяет transport-модель, не преобразует raw numeric
|
||||
значения в Decimal и не выполняет mapping во внутреннюю модель Trade.
|
||||
"""
|
||||
|
||||
_trade_positive_int(
|
||||
event.trade_id,
|
||||
path="$.payload.id",
|
||||
)
|
||||
_trade_positive_decimal(
|
||||
event.price,
|
||||
path="$.payload.price",
|
||||
)
|
||||
_trade_positive_decimal(
|
||||
event.size,
|
||||
path="$.payload.size",
|
||||
)
|
||||
_trade_positive_int(
|
||||
event.timestamp,
|
||||
path="$.payload.ts",
|
||||
)
|
||||
|
||||
if not event.symbol.strip():
|
||||
raise TradeValueError(
|
||||
"$.payload.symbol не должен быть пустым."
|
||||
)
|
||||
|
||||
if not event.order_id.strip():
|
||||
raise TradeValueError(
|
||||
"$.payload.orderId не должен быть пустым."
|
||||
)
|
||||
|
||||
|
||||
def validate_rest_agg_trade_values(
|
||||
trades: tuple[DzengiRestAggTrade, ...],
|
||||
) -> None:
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
# app/tests/unit/market_data/acquisition/validation/test_websocket_trade_values.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from src.market_data.acquisition.adapters.dzengi.models import (
|
||||
DzengiWebSocketTradeEvent,
|
||||
)
|
||||
from src.market_data.acquisition.exceptions import TradeValueError
|
||||
from src.market_data.acquisition.validation.values import (
|
||||
validate_dzengi_websocket_trade_values,
|
||||
)
|
||||
|
||||
|
||||
def _event(
|
||||
**overrides: object,
|
||||
) -> DzengiWebSocketTradeEvent:
|
||||
values: dict[str, object] = {
|
||||
"trade_id": 123456,
|
||||
"price": "63992.50",
|
||||
"size": "0.125",
|
||||
"timestamp": 1784224740000,
|
||||
"symbol": "BTC/USD_LEVERAGE",
|
||||
"buyer": True,
|
||||
"order_id": "order-123456",
|
||||
}
|
||||
values.update(overrides)
|
||||
|
||||
return DzengiWebSocketTradeEvent(
|
||||
trade_id=values["trade_id"], # type: ignore[arg-type]
|
||||
price=values["price"], # type: ignore[arg-type]
|
||||
size=values["size"], # type: ignore[arg-type]
|
||||
timestamp=values["timestamp"], # type: ignore[arg-type]
|
||||
symbol=values["symbol"], # type: ignore[arg-type]
|
||||
buyer=values["buyer"], # type: ignore[arg-type]
|
||||
order_id=values["order_id"], # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"buyer",
|
||||
[
|
||||
True,
|
||||
False,
|
||||
],
|
||||
)
|
||||
def test_validate_websocket_trade_values_accepts_buyer_values(
|
||||
buyer: bool,
|
||||
) -> None:
|
||||
validate_dzengi_websocket_trade_values(
|
||||
_event(buyer=buyer)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("field_name", "value"),
|
||||
[
|
||||
("price", "63992.50"),
|
||||
("price", 63992),
|
||||
("price", 63992.5),
|
||||
("size", "0.125"),
|
||||
("size", 1),
|
||||
("size", 0.125),
|
||||
],
|
||||
)
|
||||
def test_validate_websocket_trade_values_accepts_numeric_representations(
|
||||
field_name: str,
|
||||
value: object,
|
||||
) -> None:
|
||||
validate_dzengi_websocket_trade_values(
|
||||
_event(**{field_name: value})
|
||||
)
|
||||
|
||||
|
||||
def test_validate_websocket_trade_values_does_not_modify_event() -> None:
|
||||
event = _event()
|
||||
original = event
|
||||
|
||||
validate_dzengi_websocket_trade_values(event)
|
||||
|
||||
assert event == original
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"trade_id",
|
||||
[
|
||||
0,
|
||||
-1,
|
||||
-123456,
|
||||
],
|
||||
)
|
||||
def test_validate_websocket_trade_values_rejects_non_positive_trade_id(
|
||||
trade_id: int,
|
||||
) -> None:
|
||||
with pytest.raises(
|
||||
TradeValueError,
|
||||
match=r"\$\.payload\.id должно быть целым числом больше нуля",
|
||||
):
|
||||
validate_dzengi_websocket_trade_values(
|
||||
_event(trade_id=trade_id)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"timestamp",
|
||||
[
|
||||
0,
|
||||
-1,
|
||||
-1784224740000,
|
||||
],
|
||||
)
|
||||
def test_validate_websocket_trade_values_rejects_non_positive_timestamp(
|
||||
timestamp: int,
|
||||
) -> None:
|
||||
with pytest.raises(
|
||||
TradeValueError,
|
||||
match=r"\$\.payload\.ts должно быть целым числом больше нуля",
|
||||
):
|
||||
validate_dzengi_websocket_trade_values(
|
||||
_event(timestamp=timestamp)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"field_name",
|
||||
[
|
||||
"price",
|
||||
"size",
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"value",
|
||||
[
|
||||
0,
|
||||
-1,
|
||||
"-0.01",
|
||||
],
|
||||
)
|
||||
def test_validate_websocket_trade_values_rejects_non_positive_numeric_values(
|
||||
field_name: str,
|
||||
value: object,
|
||||
) -> None:
|
||||
path_by_field = {
|
||||
"price": "price",
|
||||
"size": "size",
|
||||
}
|
||||
|
||||
with pytest.raises(
|
||||
TradeValueError,
|
||||
match=(
|
||||
rf"\$\.payload\.{path_by_field[field_name]} "
|
||||
r"должно быть больше нуля"
|
||||
),
|
||||
):
|
||||
validate_dzengi_websocket_trade_values(
|
||||
_event(**{field_name: value})
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"field_name",
|
||||
[
|
||||
"price",
|
||||
"size",
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"value",
|
||||
[
|
||||
"NaN",
|
||||
"Infinity",
|
||||
"-Infinity",
|
||||
float("nan"),
|
||||
float("inf"),
|
||||
float("-inf"),
|
||||
],
|
||||
)
|
||||
def test_validate_websocket_trade_values_rejects_non_finite_numeric_values(
|
||||
field_name: str,
|
||||
value: object,
|
||||
) -> None:
|
||||
path_by_field = {
|
||||
"price": "price",
|
||||
"size": "size",
|
||||
}
|
||||
|
||||
with pytest.raises(
|
||||
TradeValueError,
|
||||
match=(
|
||||
rf"\$\.payload\.{path_by_field[field_name]} "
|
||||
r"должно быть конечным числом"
|
||||
),
|
||||
):
|
||||
validate_dzengi_websocket_trade_values(
|
||||
_event(**{field_name: value})
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"field_name",
|
||||
[
|
||||
"price",
|
||||
"size",
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"value",
|
||||
[
|
||||
"",
|
||||
"invalid",
|
||||
"--1",
|
||||
],
|
||||
)
|
||||
def test_validate_websocket_trade_values_rejects_invalid_numeric_strings(
|
||||
field_name: str,
|
||||
value: str,
|
||||
) -> None:
|
||||
path_by_field = {
|
||||
"price": "price",
|
||||
"size": "size",
|
||||
}
|
||||
|
||||
with pytest.raises(
|
||||
TradeValueError,
|
||||
match=(
|
||||
rf"\$\.payload\.{path_by_field[field_name]} "
|
||||
r"должно быть корректным числом"
|
||||
),
|
||||
):
|
||||
validate_dzengi_websocket_trade_values(
|
||||
_event(**{field_name: value})
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"symbol",
|
||||
[
|
||||
"",
|
||||
" ",
|
||||
"\t",
|
||||
"\n",
|
||||
],
|
||||
)
|
||||
def test_validate_websocket_trade_values_rejects_empty_symbol(
|
||||
symbol: str,
|
||||
) -> None:
|
||||
with pytest.raises(
|
||||
TradeValueError,
|
||||
match=r"\$\.payload\.symbol не должен быть пустым",
|
||||
):
|
||||
validate_dzengi_websocket_trade_values(
|
||||
_event(symbol=symbol)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"order_id",
|
||||
[
|
||||
"",
|
||||
" ",
|
||||
"\t",
|
||||
"\n",
|
||||
],
|
||||
)
|
||||
def test_validate_websocket_trade_values_rejects_empty_order_id(
|
||||
order_id: str,
|
||||
) -> None:
|
||||
with pytest.raises(
|
||||
TradeValueError,
|
||||
match=r"\$\.payload\.orderId не должен быть пустым",
|
||||
):
|
||||
validate_dzengi_websocket_trade_values(
|
||||
_event(order_id=order_id)
|
||||
)
|
||||
Reference in New Issue
Block a user