Загрузка данных
f"({request_name}, invocation_id={invocation_id}). Смотреть вложения received ws message"
)
async def _receive_subscribed_messages(
ws_client: WebSocketClient,
ws_message_type: str,
ws_invoke_type: str,
ws_invoke_params: Any,
*,
attempts: int,
per_attempt_timeout: float,
) -> AsyncIterator[list]:
"""
Генератор сообщений подписки: подписывается и последовательно отдаёт сообщения
нужного типа. При таймауте или обрыве соединения переподключается и подписывается
заново, продолжая попытки до attempts. После исчерпания попыток генератор просто
завершается — итоговую ошибку формирует вызывающая функция.
"""
needs_subscribe = True
for attempt in range(1, attempts + 1):
try:
with allure.step(
f"Получение сообщения с контентом типа: {ws_message_type} - попытка {attempt} из {attempts}"
):
if needs_subscribe:
await connect(ws_client, ws_invoke_type, ws_invoke_params)
needs_subscribe = False
yield await ws_client.receive_by_type(ws_message_type, timeout=per_attempt_timeout)
except asyncio.TimeoutError:
await ws_client.reconnect()
needs_subscribe = True
except Exception:
await ws_client.reconnect()
needs_subscribe = True
async def connect_and_get_parsed_msg_by_tu_id(
tu_id: int,
ws_client: WebSocketClient,
ws_message_type: str,
ws_invoke_type: str,
ws_invoke_params: Any = None,
attempts: int = TestConst.SUBSCRIBE_MESSAGE_POLL_ATTEMPTS,
) -> SubscribeAllLeaksInfoReply:
"""
Подписывается и ищет allLeaksInfo сообщение для конкретного ТУ.
Читает сообщения нужного типа с ретраями и реконнектом, фильтруя по tuId.
"""
async for payload in _receive_subscribed_messages(
ws_client,
ws_message_type,
ws_invoke_type,
ws_invoke_params,
attempts=attempts,
per_attempt_timeout=TestConst.POLL_BY_TIMEOUT_SECONDS,
):
parsed_payload = ws_message_parser.parse_all_leaks_info_msg(payload)
# Ищет сообщение с нужным ТУ
if parsed_payload.replyContent.tuId == tu_id:
return parsed_payload
fail(f"Не удалось получить сообщение {ws_message_type} для ТУ {tu_id} за {attempts} попыток")
async def connect_and_poll_subscribed_msg(
ws_client: WebSocketClient,
ws_message_type: str,
ws_invoke_type: str,
ws_invoke_params: Any = None,
retries: int = TestConst.SUBSCRIBE_MESSAGE_POLL_ATTEMPTS,
) -> list:
"""
Подписывается и ищет нужное сообщение в потоке, с повторным переподключением ws клиента
"""
ws_client.suppress_recv_logging = True
try:
async for payload in _receive_subscribed_messages(
ws_client,
ws_message_type,
ws_invoke_type,
ws_invoke_params,
attempts=retries,
per_attempt_timeout=TestConst.POLL_BY_TIMEOUT_SECONDS,
):
return payload
fail(f"Не удалось получить сообщение с контентом типа: {ws_message_type} за {retries} попыток")
finally:
ws_client.suppress_recv_logging = False
async def connect_and_poll_subscribed_signal(
ws_client: WebSocketClient,
ws_message_type: str,
ws_invoke_type: str,
ws_invoke_params: Any,
sensor_id: int,
sensor_description: str,
parse_payload: Callable[[list], ParsedPayloadType],
extract_signals: Callable[[ParsedPayloadType], List[InputSignalType]],
*,
max_messages: int = TestConst.SUBSCRIBE_MESSAGE_POLL_ATTEMPTS,
timeout: float = TestConst.POLL_BY_TIMEOUT_SECONDS,
) -> tuple[ParsedPayloadType, InputSignalType]:
"""
Подписывается и читает до max_messages сообщений из потока, пока не найдёт сигнал
с нужным id. При таймауте/обрыве соединения переподключается и подписывается заново.
"""
received_ids: Set[int] = set()
async for payload in _receive_subscribed_messages(
ws_client,
ws_message_type,
ws_invoke_type,
ws_invoke_params,
attempts=max_messages,
per_attempt_timeout=timeout,
):
parsed = parse_payload(payload)
signals = extract_signals(parsed) or []
for signal in signals:
signal_id = getattr(signal, "id", None)
if signal_id is not None:
received_ids.add(signal_id)
target_signal = find_object_by_field(signals, "id", sensor_id)
if target_signal is not None:
allure.attach(
f"Сигнал id={sensor_id} ({sensor_description}) найден\n"
f"Всего сигналов в сообщении: {len(signals)}\n"
f"Полученные id: {sorted(received_ids)}",
name=f"Результат поиска сигнала в {ws_message_type}",
attachment_type=allure.attachment_type.TEXT,
)
return parsed, target_signal
received_ids_text = ", ".join(str(signal_id) for signal_id in sorted(received_ids)) or "нет"
fail(
f"Информация по датчику {sensor_description} (id={sensor_id}) не пришла в ответе {ws_message_type} "
f"после {max_messages} попыток чтения из потока подписки. Полученные id: {received_ids_text}"
)
async def poll_balance_algorithm_diagnostic_areas(