build 059.1-059.2: add websocket OHLC transport and schema validation

This commit is contained in:
2026-07-16 22:14:25 +03:00
parent ebb578db35
commit e0aa04b32a
6 changed files with 1016 additions and 1 deletions

View File

@@ -0,0 +1,217 @@
# app/tests/unit/market_data/acquisition/validation/test_websocket_ohlc_schema.py
from __future__ import annotations
from types import MappingProxyType
import pytest
from src.market_data.acquisition.exceptions import (
CandleWebSocketSchemaError,
)
from src.market_data.acquisition.validation.schema import (
ValidatedWebSocketOhlcDocument,
validate_dzengi_websocket_ohlc_schema,
)
def _valid_document() -> dict[str, object]:
return {
"status": "OK",
"destination": "ohlc.event",
"payload": {
"symbol": "BTC/USD_LEVERAGE",
"interval": "1m",
"type": "classic",
"t": 1784224740000,
"o": 63992.0,
"h": 64032.55,
"l": 63984.0,
"c": 64032.55,
},
}
def test_validate_websocket_ohlc_schema_returns_immutable_document() -> None:
result = validate_dzengi_websocket_ohlc_schema(
_valid_document()
)
assert isinstance(result, ValidatedWebSocketOhlcDocument)
assert isinstance(result.payload, MappingProxyType)
assert result.status == "OK"
assert result.destination == "ohlc.event"
assert result.correlation_id is None
assert result.payload == {
"symbol": "BTC/USD_LEVERAGE",
"interval": "1m",
"type": "classic",
"t": 1784224740000,
"o": 63992.0,
"h": 64032.55,
"l": 63984.0,
"c": 64032.55,
}
def test_validate_websocket_ohlc_schema_preserves_correlation_id() -> None:
document = _valid_document()
document["correlationId"] = "correlation-1"
result = validate_dzengi_websocket_ohlc_schema(document)
assert result.correlation_id == "correlation-1"
def test_validate_websocket_ohlc_schema_copies_payload() -> None:
document = _valid_document()
payload = document["payload"]
assert isinstance(payload, dict)
result = validate_dzengi_websocket_ohlc_schema(document)
payload["c"] = 1.0
assert result.payload["c"] == 64032.55
def test_validate_websocket_ohlc_schema_rejects_non_mapping_root() -> None:
with pytest.raises(
CandleWebSocketSchemaError,
match=r"\$ должен быть JSON-объектом",
):
validate_dzengi_websocket_ohlc_schema([])
@pytest.mark.parametrize(
"field_name",
[
"status",
"destination",
"payload",
],
)
def test_validate_websocket_ohlc_schema_rejects_missing_root_field(
field_name: str,
) -> None:
document = _valid_document()
del document[field_name]
with pytest.raises(
CandleWebSocketSchemaError,
match=rf"\$\.{field_name} отсутствует",
):
validate_dzengi_websocket_ohlc_schema(document)
def test_validate_websocket_ohlc_schema_rejects_non_mapping_payload() -> None:
document = _valid_document()
document["payload"] = []
with pytest.raises(
CandleWebSocketSchemaError,
match=r"\$\.payload должен быть JSON-объектом",
):
validate_dzengi_websocket_ohlc_schema(document)
@pytest.mark.parametrize(
"field_name",
[
"symbol",
"interval",
"type",
"t",
"o",
"h",
"l",
"c",
],
)
def test_validate_websocket_ohlc_schema_rejects_missing_payload_field(
field_name: str,
) -> None:
document = _valid_document()
payload = document["payload"]
assert isinstance(payload, dict)
del payload[field_name]
with pytest.raises(
CandleWebSocketSchemaError,
match=rf"обязательные поля WebSocket OHLC: {field_name}",
):
validate_dzengi_websocket_ohlc_schema(document)
def test_validate_websocket_ohlc_schema_reports_all_missing_fields() -> None:
document = _valid_document()
payload = document["payload"]
assert isinstance(payload, dict)
del payload["symbol"]
del payload["t"]
del payload["c"]
with pytest.raises(
CandleWebSocketSchemaError,
match=r"symbol, t, c",
):
validate_dzengi_websocket_ohlc_schema(document)
def test_validate_websocket_ohlc_schema_does_not_validate_values() -> None:
document = _valid_document()
payload = document["payload"]
assert isinstance(payload, dict)
document["status"] = 123
document["destination"] = None
payload["symbol"] = None
payload["interval"] = []
payload["type"] = "unknown"
payload["t"] = -1
payload["o"] = "NaN"
payload["h"] = object()
payload["l"] = False
payload["c"] = None
result = validate_dzengi_websocket_ohlc_schema(document)
assert result.status == 123
assert result.destination is None
assert result.payload["type"] == "unknown"
assert result.payload["t"] == -1
def test_validate_websocket_ohlc_schema_rejects_non_string_root_key() -> None:
document = _valid_document()
document[1] = "invalid" # type: ignore[index]
with pytest.raises(
CandleWebSocketSchemaError,
match=r"\$ содержит нестроковый ключ",
):
validate_dzengi_websocket_ohlc_schema(document)
def test_validate_websocket_ohlc_schema_rejects_non_string_payload_key() -> None:
document = _valid_document()
payload = document["payload"]
assert isinstance(payload, dict)
payload[1] = "invalid" # type: ignore[index]
with pytest.raises(
CandleWebSocketSchemaError,
match=r"\$\.payload содержит нестроковый ключ",
):
validate_dzengi_websocket_ohlc_schema(document)