Read the panel SSE body from response.content (task 4cbf2e96)

stream_events iterated the aiohttp/HA ClientResponse itself, which is
not async-iterable, so every panel event stream died with "'async for'
requires an object with __aiter__ method, got HassClientResponse" and
reconnected forever. Iterate response.content (the StreamReader)
instead; parsing, close, and the error/retry contract are unchanged.

The streaming test stub in test_sse_subscription.py made the same
mistake (it implemented __aiter__ on the response), which is why the
suite never caught this; it now exposes .content like the real
response. New test_api_client.py tests use a response without
__aiter__ and byte chunks split across frame boundaries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 07:52:52 -04:00
co-authored by Claude Opus 5.5
parent f2ff73ea31
commit 4834f19118
3 changed files with 105 additions and 8 deletions
+16 -6
View File
@@ -165,19 +165,29 @@ MINIMAL_SNAPSHOT = {
class _StreamingResponse:
"""A fake aiohttp streaming response: ``status`` + chunk iteration.
"""A fake aiohttp streaming response: ``status`` + ``.content``.
``chunks`` are the body pieces the stream yields in order; a chunk
may be an exception, raised when the iteration reaches it (a
mid-stream transport drop). ``hold_open`` keeps the stream open
forever after the chunks run out, for cancellation tests.
Like aiohttp's ``ClientResponse`` (and HA's ``HassClientResponse``)
the response itself is NOT async-iterable; the body is iterated
through ``.content``. ``chunks`` are the body pieces the stream
yields in order; a chunk may be an exception, raised when the
iteration reaches it (a mid-stream transport drop). ``hold_open``
keeps the stream open forever after the chunks run out, for
cancellation tests.
"""
def __init__(self, status: int, chunks, *, hold_open: bool = False) -> None:
self.status = status
self.content = _StreamingContent(chunks, hold_open)
self.closed = False
class _StreamingContent:
"""The fake response body: an async-iterable chunk stream."""
def __init__(self, chunks, hold_open: bool) -> None:
self._chunks = list(chunks)
self._hold_open = hold_open
self.closed = False
def __aiter__(self):
return self._iterate()