build 059.4: validate websocket OHLC values
This commit is contained in:
@@ -0,0 +1,387 @@
|
||||
# app/tests/unit/market_data/acquisition/validation/test_websocket_ohlc_values.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from src.market_data.acquisition.adapters.dzengi.models import (
|
||||
DzengiWebSocketOhlcEvent,
|
||||
)
|
||||
from src.market_data.acquisition.exceptions import (
|
||||
CandleWebSocketValueError,
|
||||
)
|
||||
from src.market_data.acquisition.validation.values import (
|
||||
validate_dzengi_websocket_ohlc_values,
|
||||
)
|
||||
|
||||
|
||||
def _event(
|
||||
**overrides: object,
|
||||
) -> DzengiWebSocketOhlcEvent:
|
||||
values: dict[str, object] = {
|
||||
"symbol": "BTC/USD_LEVERAGE",
|
||||
"interval": "1m",
|
||||
"candle_type": "classic",
|
||||
"open_time": 1784224740000,
|
||||
"open_price": "63992.00",
|
||||
"high_price": "64032.55",
|
||||
"low_price": "63984.00",
|
||||
"close_price": "64032.55",
|
||||
}
|
||||
values.update(overrides)
|
||||
|
||||
return DzengiWebSocketOhlcEvent(
|
||||
symbol=values["symbol"], # type: ignore[arg-type]
|
||||
interval=values["interval"], # type: ignore[arg-type]
|
||||
candle_type=values["candle_type"], # type: ignore[arg-type]
|
||||
open_time=values["open_time"], # type: ignore[arg-type]
|
||||
open_price=values["open_price"], # type: ignore[arg-type]
|
||||
high_price=values["high_price"], # type: ignore[arg-type]
|
||||
low_price=values["low_price"], # type: ignore[arg-type]
|
||||
close_price=values["close_price"], # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"interval",
|
||||
[
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"1h",
|
||||
],
|
||||
)
|
||||
def test_validate_websocket_ohlc_values_accepts_supported_intervals(
|
||||
interval: str,
|
||||
) -> None:
|
||||
validate_dzengi_websocket_ohlc_values(
|
||||
_event(interval=interval)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"candle_type",
|
||||
[
|
||||
"classic",
|
||||
"heikin-ashi",
|
||||
],
|
||||
)
|
||||
def test_validate_websocket_ohlc_values_accepts_supported_types(
|
||||
candle_type: str,
|
||||
) -> None:
|
||||
validate_dzengi_websocket_ohlc_values(
|
||||
_event(candle_type=candle_type)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("field_name", "value"),
|
||||
[
|
||||
("open_price", "63992.00"),
|
||||
("open_price", 63992),
|
||||
("open_price", 63992.0),
|
||||
("high_price", "64032.55"),
|
||||
("low_price", 63984),
|
||||
("close_price", 64032.55),
|
||||
],
|
||||
)
|
||||
def test_validate_websocket_ohlc_values_accepts_numeric_representations(
|
||||
field_name: str,
|
||||
value: object,
|
||||
) -> None:
|
||||
validate_dzengi_websocket_ohlc_values(
|
||||
_event(**{field_name: value})
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"symbol",
|
||||
[
|
||||
"",
|
||||
" ",
|
||||
"\t",
|
||||
"\n",
|
||||
],
|
||||
)
|
||||
def test_validate_websocket_ohlc_values_rejects_empty_symbol(
|
||||
symbol: str,
|
||||
) -> None:
|
||||
with pytest.raises(
|
||||
CandleWebSocketValueError,
|
||||
match=r"\$\.payload\.symbol не должен быть пустым",
|
||||
):
|
||||
validate_dzengi_websocket_ohlc_values(
|
||||
_event(symbol=symbol)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"interval",
|
||||
[
|
||||
"",
|
||||
"1s",
|
||||
"4h",
|
||||
"1M",
|
||||
" 1m ",
|
||||
],
|
||||
)
|
||||
def test_validate_websocket_ohlc_values_rejects_unsupported_interval(
|
||||
interval: str,
|
||||
) -> None:
|
||||
with pytest.raises(
|
||||
CandleWebSocketValueError,
|
||||
match=r"\$\.payload\.interval содержит неподдерживаемый интервал",
|
||||
):
|
||||
validate_dzengi_websocket_ohlc_values(
|
||||
_event(interval=interval)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"candle_type",
|
||||
[
|
||||
"",
|
||||
"bid",
|
||||
"ask",
|
||||
"Classic",
|
||||
"unknown",
|
||||
" classic ",
|
||||
],
|
||||
)
|
||||
def test_validate_websocket_ohlc_values_rejects_unsupported_type(
|
||||
candle_type: str,
|
||||
) -> None:
|
||||
with pytest.raises(
|
||||
CandleWebSocketValueError,
|
||||
match=r"\$\.payload\.type содержит неподдерживаемый тип свечи",
|
||||
):
|
||||
validate_dzengi_websocket_ohlc_values(
|
||||
_event(candle_type=candle_type)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"open_time",
|
||||
[
|
||||
0,
|
||||
-1,
|
||||
-1784224740000,
|
||||
],
|
||||
)
|
||||
def test_validate_websocket_ohlc_values_rejects_non_positive_open_time(
|
||||
open_time: int,
|
||||
) -> None:
|
||||
with pytest.raises(
|
||||
CandleWebSocketValueError,
|
||||
match=r"\$\.payload\.t должно быть целым числом больше нуля",
|
||||
):
|
||||
validate_dzengi_websocket_ohlc_values(
|
||||
_event(open_time=open_time)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"field_name",
|
||||
[
|
||||
"open_price",
|
||||
"high_price",
|
||||
"low_price",
|
||||
"close_price",
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"value",
|
||||
[
|
||||
0,
|
||||
-1,
|
||||
"-0.01",
|
||||
],
|
||||
)
|
||||
def test_validate_websocket_ohlc_values_rejects_non_positive_prices(
|
||||
field_name: str,
|
||||
value: object,
|
||||
) -> None:
|
||||
path_by_field = {
|
||||
"open_price": "o",
|
||||
"high_price": "h",
|
||||
"low_price": "l",
|
||||
"close_price": "c",
|
||||
}
|
||||
|
||||
with pytest.raises(
|
||||
CandleWebSocketValueError,
|
||||
match=(
|
||||
rf"\$\.payload\.{path_by_field[field_name]} "
|
||||
r"должно быть больше нуля"
|
||||
),
|
||||
):
|
||||
validate_dzengi_websocket_ohlc_values(
|
||||
_event(**{field_name: value})
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"field_name",
|
||||
[
|
||||
"open_price",
|
||||
"high_price",
|
||||
"low_price",
|
||||
"close_price",
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"value",
|
||||
[
|
||||
"NaN",
|
||||
"Infinity",
|
||||
"-Infinity",
|
||||
float("nan"),
|
||||
float("inf"),
|
||||
float("-inf"),
|
||||
],
|
||||
)
|
||||
def test_validate_websocket_ohlc_values_rejects_non_finite_prices(
|
||||
field_name: str,
|
||||
value: object,
|
||||
) -> None:
|
||||
path_by_field = {
|
||||
"open_price": "o",
|
||||
"high_price": "h",
|
||||
"low_price": "l",
|
||||
"close_price": "c",
|
||||
}
|
||||
|
||||
with pytest.raises(
|
||||
CandleWebSocketValueError,
|
||||
match=(
|
||||
rf"\$\.payload\.{path_by_field[field_name]} "
|
||||
r"должно быть конечным числом"
|
||||
),
|
||||
):
|
||||
validate_dzengi_websocket_ohlc_values(
|
||||
_event(**{field_name: value})
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"field_name",
|
||||
[
|
||||
"open_price",
|
||||
"high_price",
|
||||
"low_price",
|
||||
"close_price",
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"value",
|
||||
[
|
||||
"",
|
||||
"invalid",
|
||||
"--1",
|
||||
],
|
||||
)
|
||||
def test_validate_websocket_ohlc_values_rejects_invalid_numeric_strings(
|
||||
field_name: str,
|
||||
value: str,
|
||||
) -> None:
|
||||
path_by_field = {
|
||||
"open_price": "o",
|
||||
"high_price": "h",
|
||||
"low_price": "l",
|
||||
"close_price": "c",
|
||||
}
|
||||
|
||||
with pytest.raises(
|
||||
CandleWebSocketValueError,
|
||||
match=(
|
||||
rf"\$\.payload\.{path_by_field[field_name]} "
|
||||
r"должно быть корректным числом"
|
||||
),
|
||||
):
|
||||
validate_dzengi_websocket_ohlc_values(
|
||||
_event(**{field_name: value})
|
||||
)
|
||||
|
||||
|
||||
def test_validate_websocket_ohlc_values_rejects_high_below_low() -> None:
|
||||
with pytest.raises(
|
||||
CandleWebSocketValueError,
|
||||
match=r"\$\.payload\.h не должно быть меньше \$\.payload\.l",
|
||||
):
|
||||
validate_dzengi_websocket_ohlc_values(
|
||||
_event(
|
||||
high_price="63980",
|
||||
low_price="63984",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_validate_websocket_ohlc_values_rejects_high_below_open() -> None:
|
||||
with pytest.raises(
|
||||
CandleWebSocketValueError,
|
||||
match=r"\$\.payload\.h не должно быть меньше \$\.payload\.o",
|
||||
):
|
||||
validate_dzengi_websocket_ohlc_values(
|
||||
_event(
|
||||
open_price="64000",
|
||||
high_price="63999",
|
||||
low_price="63980",
|
||||
close_price="63990",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_validate_websocket_ohlc_values_rejects_high_below_close() -> None:
|
||||
with pytest.raises(
|
||||
CandleWebSocketValueError,
|
||||
match=r"\$\.payload\.h не должно быть меньше \$\.payload\.c",
|
||||
):
|
||||
validate_dzengi_websocket_ohlc_values(
|
||||
_event(
|
||||
open_price="63990",
|
||||
high_price="64000",
|
||||
low_price="63980",
|
||||
close_price="64001",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_validate_websocket_ohlc_values_rejects_low_above_open() -> None:
|
||||
with pytest.raises(
|
||||
CandleWebSocketValueError,
|
||||
match=r"\$\.payload\.l не должно превышать \$\.payload\.o",
|
||||
):
|
||||
validate_dzengi_websocket_ohlc_values(
|
||||
_event(
|
||||
open_price="63990",
|
||||
high_price="64020",
|
||||
low_price="64000",
|
||||
close_price="64010",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_validate_websocket_ohlc_values_rejects_low_above_close() -> None:
|
||||
with pytest.raises(
|
||||
CandleWebSocketValueError,
|
||||
match=r"\$\.payload\.l не должно превышать \$\.payload\.c",
|
||||
):
|
||||
validate_dzengi_websocket_ohlc_values(
|
||||
_event(
|
||||
open_price="64010",
|
||||
high_price="64020",
|
||||
low_price="64000",
|
||||
close_price="63999",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_validate_websocket_ohlc_values_accepts_flat_candle() -> None:
|
||||
validate_dzengi_websocket_ohlc_values(
|
||||
_event(
|
||||
open_price="64000",
|
||||
high_price="64000",
|
||||
low_price="64000",
|
||||
close_price="64000",
|
||||
)
|
||||
)
|
||||
Reference in New Issue
Block a user