build 059.3: parse websocket OHLC events

This commit is contained in:
2026-07-16 23:21:14 +03:00
parent e0aa04b32a
commit c8c0bb6951
4 changed files with 896 additions and 0 deletions

View File

@@ -0,0 +1,200 @@
# app/tests/unit/market_data/acquisition/adapters/dzengi/test_websocket_ohlc_parser.py
from __future__ import annotations
from types import MappingProxyType
import pytest
from src.market_data.acquisition.adapters.dzengi.models import (
DzengiWebSocketOhlcEvent,
)
from src.market_data.acquisition.adapters.dzengi.parser import (
parse_dzengi_websocket_ohlc,
)
from src.market_data.acquisition.exceptions import (
CandleWebSocketParseError,
)
from src.market_data.acquisition.validation.schema import (
ValidatedWebSocketOhlcDocument,
)
def _validated_document(
**payload_overrides: object,
) -> ValidatedWebSocketOhlcDocument:
payload: dict[str, object] = {
"symbol": "BTC/USD_LEVERAGE",
"interval": "1m",
"type": "classic",
"t": 1784224740000,
"o": 63992.0,
"h": 64032.55,
"l": 63984.0,
"c": 64032.55,
}
payload.update(payload_overrides)
return ValidatedWebSocketOhlcDocument(
payload=MappingProxyType(payload),
status="OK",
destination="ohlc.event",
correlation_id=None,
)
def test_parse_websocket_ohlc_returns_transport_model() -> None:
result = parse_dzengi_websocket_ohlc(
_validated_document()
)
assert result == DzengiWebSocketOhlcEvent(
symbol="BTC/USD_LEVERAGE",
interval="1m",
candle_type="classic",
open_time=1784224740000,
open_price=63992.0,
high_price=64032.55,
low_price=63984.0,
close_price=64032.55,
)
def test_parse_websocket_ohlc_preserves_heikin_ashi_type() -> None:
result = parse_dzengi_websocket_ohlc(
_validated_document(type="heikin-ashi")
)
assert result.candle_type == "heikin-ashi"
def test_parse_websocket_ohlc_preserves_raw_numeric_types() -> None:
result = parse_dzengi_websocket_ohlc(
_validated_document(
o="63992.00",
h=64033,
l=63984.0,
c="64032.55",
)
)
assert result.open_price == "63992.00"
assert result.high_price == 64033
assert result.low_price == 63984.0
assert result.close_price == "64032.55"
@pytest.mark.parametrize(
("field_name", "value"),
[
("symbol", None),
("symbol", 123),
("interval", None),
("interval", []),
("type", None),
("type", True),
],
)
def test_parse_websocket_ohlc_rejects_non_string_fields(
field_name: str,
value: object,
) -> None:
with pytest.raises(
CandleWebSocketParseError,
match=rf"\$\.payload\.{field_name} должен быть строкой",
):
parse_dzengi_websocket_ohlc(
_validated_document(**{field_name: value})
)
@pytest.mark.parametrize(
"value",
[
None,
True,
1784224740000.0,
"1784224740000",
[],
],
)
def test_parse_websocket_ohlc_rejects_invalid_open_time(
value: object,
) -> None:
with pytest.raises(
CandleWebSocketParseError,
match=r"\$\.payload\.t должен быть целым числом",
):
parse_dzengi_websocket_ohlc(
_validated_document(t=value)
)
@pytest.mark.parametrize(
"field_name",
[
"o",
"h",
"l",
"c",
],
)
@pytest.mark.parametrize(
"value",
[
None,
True,
[],
{},
object(),
],
)
def test_parse_websocket_ohlc_rejects_invalid_raw_numeric(
field_name: str,
value: object,
) -> None:
with pytest.raises(
CandleWebSocketParseError,
match=rf"\$\.payload\.{field_name} должен быть строкой или числом",
):
parse_dzengi_websocket_ohlc(
_validated_document(**{field_name: value})
)
def test_parse_websocket_ohlc_does_not_validate_subject_values() -> None:
result = parse_dzengi_websocket_ohlc(
_validated_document(
symbol="",
interval="unsupported",
type="unknown",
t=-1,
o="NaN",
h=-10,
l=100,
c=0,
)
)
assert result.symbol == ""
assert result.interval == "unsupported"
assert result.candle_type == "unknown"
assert result.open_time == -1
assert result.open_price == "NaN"
assert result.high_price == -10
assert result.low_price == 100
assert result.close_price == 0
def test_parse_websocket_ohlc_ignores_envelope_metadata() -> None:
document = ValidatedWebSocketOhlcDocument(
payload=_validated_document().payload,
status=123,
destination=None,
correlation_id={"unexpected": "value"},
)
result = parse_dzengi_websocket_ohlc(document)
assert result.symbol == "BTC/USD_LEVERAGE"
assert result.interval == "1m"