build 059.12: add runtime event models

This commit is contained in:
2026-07-17 20:19:00 +03:00
parent d2d8f8fad4
commit f2bacb80c9
3 changed files with 818 additions and 0 deletions

View File

@@ -0,0 +1,114 @@
# app/src/market_data/acquisition/runtime/runtime_events.py
from __future__ import annotations
from dataclasses import dataclass
from src.market_data.acquisition.runtime.transport_messages import (
TransportBinaryMessage,
TransportTextMessage,
)
TransportMessage = TransportTextMessage | TransportBinaryMessage
@dataclass(frozen=True, slots=True)
class ConnectedEvent:
"""
Событие успешного открытия транспортного соединения.
Событие фиксирует уже произошедший факт подключения и не содержит
логики управления транспортом.
"""
@dataclass(frozen=True, slots=True)
class DisconnectedEvent:
"""
Событие завершения транспортного соединения.
Событие не определяет причину отключения и не запускает reconnect
самостоятельно.
"""
@dataclass(frozen=True, slots=True)
class ConnectFailedEvent:
"""
Событие неуспешной попытки открытия транспортного соединения.
reason содержит безопасное текстовое описание причины ошибки.
"""
reason: str
@dataclass(frozen=True, slots=True)
class MessageReceivedEvent:
"""
Событие получения транспортного сообщения.
message содержит непрозрачный текстовый или бинарный payload.
Runtime Event не интерпретирует содержимое сообщения.
"""
message: TransportMessage
@dataclass(frozen=True, slots=True)
class MessageSentEvent:
"""
Событие успешной отправки транспортного сообщения.
message содержит сообщение, переданное транспортному уровню.
"""
message: TransportMessage
@dataclass(frozen=True, slots=True)
class ReconnectStartedEvent:
"""
Событие начала попытки восстановления соединения.
attempt содержит номер текущей попытки reconnect.
"""
attempt: int
@dataclass(frozen=True, slots=True)
class ReconnectCompletedEvent:
"""
Событие успешного завершения восстановления соединения.
attempt содержит номер попытки, на которой соединение было
восстановлено.
"""
attempt: int
@dataclass(frozen=True, slots=True)
class ReconnectFailedEvent:
"""
Событие неуспешной попытки восстановления соединения.
attempt содержит номер попытки.
reason содержит безопасное текстовое описание причины ошибки.
"""
attempt: int
reason: str
@dataclass(frozen=True, slots=True)
class HeartbeatTimeoutEvent:
"""
Событие превышения допустимого интервала heartbeat.
timeout_seconds содержит настроенный порог ожидания ответа.
"""
timeout_seconds: float

View File

@@ -0,0 +1,116 @@
# app/tests/unit/market_data/acquisition/runtime/test_runtime_events.py
from __future__ import annotations
from dataclasses import FrozenInstanceError
import pytest
from src.market_data.acquisition.runtime.runtime_events import (
ConnectedEvent,
ConnectFailedEvent,
DisconnectedEvent,
HeartbeatTimeoutEvent,
MessageReceivedEvent,
MessageSentEvent,
ReconnectCompletedEvent,
ReconnectFailedEvent,
ReconnectStartedEvent,
)
from src.market_data.acquisition.runtime.transport_messages import (
TransportBinaryMessage,
TransportTextMessage,
)
def test_connected_event_is_marker_event() -> None:
assert ConnectedEvent() == ConnectedEvent()
def test_disconnected_event_is_marker_event() -> None:
assert DisconnectedEvent() == DisconnectedEvent()
def test_connect_failed_event_contains_reason() -> None:
event = ConnectFailedEvent(reason="connection refused")
assert event.reason == "connection refused"
def test_message_received_event_supports_text_message() -> None:
message = TransportTextMessage(payload="market-data")
event = MessageReceivedEvent(message=message)
assert event.message is message
def test_message_received_event_supports_binary_message() -> None:
message = TransportBinaryMessage(payload=b"\x01\x02")
event = MessageReceivedEvent(message=message)
assert event.message is message
def test_message_sent_event_contains_transport_message() -> None:
message = TransportTextMessage(payload="ping")
event = MessageSentEvent(message=message)
assert event.message is message
def test_reconnect_started_event_contains_attempt() -> None:
event = ReconnectStartedEvent(attempt=1)
assert event.attempt == 1
def test_reconnect_completed_event_contains_attempt() -> None:
event = ReconnectCompletedEvent(attempt=2)
assert event.attempt == 2
def test_reconnect_failed_event_contains_attempt_and_reason() -> None:
event = ReconnectFailedEvent(
attempt=3,
reason="timeout",
)
assert event.attempt == 3
assert event.reason == "timeout"
def test_heartbeat_timeout_event_contains_timeout_seconds() -> None:
event = HeartbeatTimeoutEvent(timeout_seconds=30.0)
assert event.timeout_seconds == 30.0
def test_connect_failed_event_is_immutable() -> None:
event = ConnectFailedEvent(reason="timeout")
with pytest.raises(FrozenInstanceError):
setattr(event, "reason", "connection refused")
def test_message_received_event_is_immutable() -> None:
event = MessageReceivedEvent(
message=TransportTextMessage(payload="payload"),
)
with pytest.raises(FrozenInstanceError):
setattr(
event,
"message",
TransportTextMessage(payload="other"),
)
def test_reconnect_failed_event_is_immutable() -> None:
event = ReconnectFailedEvent(
attempt=1,
reason="timeout",
)
with pytest.raises(FrozenInstanceError):
setattr(event, "attempt", 2)