Files
mxaccessgw/clients/python/tests/test_stream_timeout_fallback.py
Joseph Doherty e4fbbb541a Resolve Client.Python-003, -005, -009 code-review findings
Client.Python-003: stream_events_raw and query_active_alarms passed `timeout`
to the stub with no TypeError fallback, unlike _unary. Both now route through
a shared _open_stream helper that strips `timeout` on TypeError.

Client.Python-005: discover_hierarchy buffered the entire Galaxy hierarchy in
memory. Added GalaxyRepositoryClient.iter_hierarchy, a lazy async generator
yielding objects page-by-page; discover_hierarchy is now a thin wrapper that
preserves its list contract. README documents iter_hierarchy.

Client.Python-009: added regression coverage for previously untested paths —
write2/add_item2 request shape, the MAX_BULK_ITEMS boundary, the None-argument
TypeError guards, TLS ca_file reading, and the non-auth map_rpc_error fallthrough.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 21:45:16 -04:00

133 lines
3.9 KiB
Python

"""Regression tests for Client.Python-003: stream timeout-kwarg fallback.
`stream_events_raw` and `query_active_alarms` must tolerate a fake/older stub
that does not accept a ``timeout`` keyword argument, matching the fallback
already present in `galaxy.watch_deploy_events` and the unary `_unary` helper.
"""
from __future__ import annotations
from typing import Any
import pytest
from mxgateway import ClientOptions, GatewayClient
from mxgateway.generated import mxaccess_gateway_pb2 as pb
class _NoTimeoutStream:
"""Sync-callable unary-stream fake that rejects a ``timeout`` kwarg."""
def __init__(self, replies: list[Any]) -> None:
self._replies = list(replies)
self.requests: list[Any] = []
self.metadata: tuple[tuple[str, str], ...] | None = None
self.cancelled = False
def __call__(
self,
request: Any,
*,
metadata: tuple[tuple[str, str], ...],
) -> "_NoTimeoutStream":
self.requests.append(request)
self.metadata = metadata
return self
def __aiter__(self) -> "_NoTimeoutStream":
return self
async def __anext__(self) -> Any:
if not self._replies:
raise StopAsyncIteration
return self._replies.pop(0)
def cancel(self) -> None:
self.cancelled = True
class _NoTimeoutStubStreamEvents:
def __init__(self, stream: _NoTimeoutStream) -> None:
self.StreamEvents = stream
class _NoTimeoutStubQueryAlarms:
def __init__(self, stream: _NoTimeoutStream) -> None:
self.QueryActiveAlarms = stream
@pytest.mark.asyncio
async def test_stream_events_raw_falls_back_when_stub_rejects_timeout() -> None:
stream = _NoTimeoutStream(
[pb.MxEvent(session_id="session-1", worker_sequence=1)],
)
client = await GatewayClient.connect(
ClientOptions(endpoint="fake", plaintext=True, stream_timeout=5.0),
stub=_NoTimeoutStubStreamEvents(stream),
)
received = [
event
async for event in client.stream_events_raw(
pb.StreamEventsRequest(session_id="session-1"),
)
]
assert len(received) == 1
assert received[0].worker_sequence == 1
@pytest.mark.asyncio
async def test_query_active_alarms_falls_back_when_stub_rejects_timeout() -> None:
stream = _NoTimeoutStream(
[pb.ActiveAlarmSnapshot(alarm_full_reference="Tank01.Level.HiHi")],
)
client = await GatewayClient.connect(
ClientOptions(endpoint="fake", plaintext=True, stream_timeout=5.0),
stub=_NoTimeoutStubQueryAlarms(stream),
)
received = [
snapshot
async for snapshot in client.query_active_alarms(
pb.QueryActiveAlarmsRequest(session_id="session-1"),
)
]
assert len(received) == 1
assert received[0].alarm_full_reference == "Tank01.Level.HiHi"
@pytest.mark.asyncio
async def test_stream_events_raw_still_passes_timeout_to_capable_stub() -> None:
"""A stub that accepts ``timeout`` must still receive the configured value."""
captured: dict[str, Any] = {}
class _CapableStream(_NoTimeoutStream):
def __call__( # type: ignore[override]
self,
request: Any,
*,
metadata: tuple[tuple[str, str], ...],
timeout: float | None = None,
) -> "_CapableStream":
captured["timeout"] = timeout
return super().__call__(request, metadata=metadata)
stream = _CapableStream([pb.MxEvent(session_id="session-1", worker_sequence=9)])
client = await GatewayClient.connect(
ClientOptions(endpoint="fake", plaintext=True, stream_timeout=7.5),
stub=_NoTimeoutStubStreamEvents(stream),
)
received = [
event
async for event in client.stream_events_raw(
pb.StreamEventsRequest(session_id="session-1"),
)
]
assert len(received) == 1
assert captured["timeout"] == 7.5