e4fbbb541a
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>
133 lines
3.9 KiB
Python
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
|