Skip to content

Commit 0829741

Browse files
committed
Keep the successful GET test stream open
1 parent daf9469 commit 0829741

1 file changed

Lines changed: 31 additions & 29 deletions

File tree

‎tests/client/test_streamable_http.py‎

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,28 @@ def handler(request: httpx2.Request) -> httpx2.Response:
163163
assert received.message.params == {"data": payload}
164164

165165

166+
class _ParkedSSEStream(httpx2.AsyncByteStream):
167+
"""An SSE response body that emits one frame, then parks until closed.
168+
169+
`opened` fires when the body is consumed; `closed` fires when httpx2 releases it.
170+
"""
171+
172+
def __init__(self, content: bytes = b": parked\n\n") -> None:
173+
self.opened = anyio.Event()
174+
self.closed = anyio.Event()
175+
self._release = anyio.Event()
176+
self._content = content
177+
178+
async def __aiter__(self) -> AsyncIterator[bytes]:
179+
self.opened.set()
180+
yield self._content
181+
await self._release.wait()
182+
183+
async def aclose(self) -> None:
184+
self.closed.set()
185+
self._release.set()
186+
187+
166188
@pytest.mark.anyio
167189
async def test_oversized_get_event_reconnects_and_delivers_later_messages() -> None:
168190
"""SDK-defined: a bad GET event does not prevent later server messages or POSTs.
@@ -177,11 +199,14 @@ def handler(request: httpx2.Request) -> httpx2.Response:
177199
if len(gets) == 1:
178200
content = b'retry: 0\n\ndata: {"jsonrpc":"2.0","method":"notifications/message","params":{"data":"'
179201
content += b"x" * 2048 + b'"}}\n\n'
180-
elif len(gets) == 2:
181-
content = b'data: {"jsonrpc":"2.0","method":"notifications/message","params":{"data":"ok"}}\n\n'
182-
else:
183-
return httpx2.Response(500)
184-
return httpx2.Response(200, content=content, headers={"content-type": "text/event-stream"})
202+
return httpx2.Response(200, content=content, headers={"content-type": "text/event-stream"})
203+
return httpx2.Response(
204+
200,
205+
stream=_ParkedSSEStream(
206+
b'data: {"jsonrpc":"2.0","method":"notifications/message","params":{"data":"ok"}}\n\n'
207+
),
208+
headers={"content-type": "text/event-stream"},
209+
)
185210
body = json.loads(request.content)
186211
if body.get("method") == "initialize":
187212
return httpx2.Response(
@@ -211,7 +236,7 @@ def handler(request: httpx2.Request) -> httpx2.Response:
211236
assert isinstance(reply, SessionMessage)
212237
assert isinstance(reply.message, JSONRPCResponse)
213238
assert reply.message.id == 2
214-
assert len(gets) >= 2
239+
assert len(gets) == 2
215240

216241

217242
@pytest.mark.anyio
@@ -500,29 +525,6 @@ def handler(request: httpx2.Request) -> httpx2.Response:
500525
assert recorded[3].headers[MCP_PROTOCOL_VERSION_HEADER] == "2025-11-25"
501526

502527

503-
class _ParkedSSEStream(httpx2.AsyncByteStream):
504-
"""An SSE response body that emits one comment line, then parks until closed.
505-
506-
`opened` fires once the transport is iterating the body (the POST is truly in
507-
flight); `closed` fires when httpx2 tears the body down — the observable proof
508-
that an abort, not a response, ended the stream.
509-
"""
510-
511-
def __init__(self) -> None:
512-
self.opened = anyio.Event()
513-
self.closed = anyio.Event()
514-
self._release = anyio.Event()
515-
516-
async def __aiter__(self) -> AsyncIterator[bytes]:
517-
self.opened.set()
518-
yield b": parked\n\n"
519-
await self._release.wait()
520-
521-
async def aclose(self) -> None:
522-
self.closed.set()
523-
self._release.set()
524-
525-
526528
def _sse_or_ack_handler(
527529
parked: _ParkedSSEStream, posted: list[dict[str, Any]], frame_posted: anyio.Event
528530
) -> Callable[[httpx2.Request], httpx2.Response]:

0 commit comments

Comments
 (0)