Files
turnstone/tests/test_sdk_sse.py
Patrick Buckley ad0e7ce6eb docs: mark 1.5.0 legacy URL surface removal
CHANGELOG [Unreleased] / Removed (BREAKING — 1.5.0) block calling out
the legacy URL family removal with the swap table. Doc passes on
api-reference.md (per-endpoint sections rewritten with path
parameters and slimmer body shapes), architecture.md (handler-list
diagram and console-proxy URL example), console.md (URL-rewriting
JS shim docstring + SSE proxy example), and the two PlantUML
diagrams (11-console-data-flow, 16-channel-architecture).

Also picks up two test-side stragglers from step 5 that referenced
the legacy adapters in a docstring + a stale /v1/api/events SSE
test: turn into path-keyed equivalents. OpenAPI JSON dump regenerated
to reflect the catalog edits from step 3.

After this commit:
- 4557 tests passing under -m "not live"
- ruff + mypy clean on turnstone/ tests/ sdk/
- grep for "/v1/api/send", "/v1/api/approve", "/v1/api/cancel",
  "/v1/api/workstreams/close" returns zero hits across turnstone/
  sdk/ docs/ tests/ (excluding CHANGELOG.md, which intentionally
  documents the old shape).
- grep for make_legacy_body_keyed_adapter, make_legacy_query_keyed_adapter,
  _make_method_dispatch, close_legacy returns zero hits.
2026-04-26 22:14:22 -07:00

111 lines
3.6 KiB
Python

"""Tests for _BaseClient._stream_sse — SSE stream parsing."""
from __future__ import annotations
import httpx
import pytest
from turnstone.sdk._base import _BaseClient
def _sse_response(*events: str) -> httpx.Response:
"""Build a mock SSE response from data strings."""
body = ""
for event in events:
body += f"data: {event}\n\n"
return httpx.Response(
200,
content=body.encode(),
headers={"content-type": "text/event-stream"},
)
@pytest.mark.anyio
async def test_stream_sse_yields_json():
"""SSE stream with valid JSON payloads yields parsed dicts."""
def handler(request: httpx.Request) -> httpx.Response:
return _sse_response(
'{"type": "content", "text": "hello"}',
'{"type": "stream_end"}',
)
transport = httpx.MockTransport(handler)
async with httpx.AsyncClient(transport=transport, base_url="http://test") as hc:
client = _BaseClient(httpx_client=hc)
events = []
async for data in client._stream_sse("/v1/api/workstreams/ws1/events"):
events.append(data)
assert len(events) == 2
assert events[0]["type"] == "content"
assert events[1]["type"] == "stream_end"
@pytest.mark.anyio
async def test_stream_sse_skips_malformed_json():
"""Malformed JSON in SSE data is silently skipped."""
def handler(request: httpx.Request) -> httpx.Response:
return _sse_response(
"not-json",
'{"type": "content", "text": "ok"}',
)
transport = httpx.MockTransport(handler)
async with httpx.AsyncClient(transport=transport, base_url="http://test") as hc:
client = _BaseClient(httpx_client=hc)
events = []
async for data in client._stream_sse("/test"):
events.append(data)
assert len(events) == 1
assert events[0]["type"] == "content"
@pytest.mark.anyio
async def test_stream_sse_skips_empty_data():
"""SSE frames with empty data field are skipped."""
def handler(request: httpx.Request) -> httpx.Response:
# Build response with an empty data line
body = 'data: \n\ndata: {"type": "info", "message": "ok"}\n\n'
return httpx.Response(
200,
content=body.encode(),
headers={"content-type": "text/event-stream"},
)
transport = httpx.MockTransport(handler)
async with httpx.AsyncClient(transport=transport, base_url="http://test") as hc:
client = _BaseClient(httpx_client=hc)
events = []
async for data in client._stream_sse("/test"):
events.append(data)
# Empty " " data is not valid JSON, so skipped; only "info" event remains
assert len(events) == 1
assert events[0]["type"] == "info"
@pytest.mark.anyio
async def test_stream_sse_multiple_events():
"""Multiple SSE events are yielded in order."""
event_data = [
'{"type": "connected", "model": "gpt-5"}',
'{"type": "content", "text": "word1 "}',
'{"type": "content", "text": "word2"}',
'{"type": "status", "total_tokens": 10}',
'{"type": "stream_end"}',
]
def handler(request: httpx.Request) -> httpx.Response:
return _sse_response(*event_data)
transport = httpx.MockTransport(handler)
async with httpx.AsyncClient(transport=transport, base_url="http://test") as hc:
client = _BaseClient(httpx_client=hc)
events = []
async for data in client._stream_sse("/test"):
events.append(data)
assert len(events) == 5
types = [e["type"] for e in events]
assert types == ["connected", "content", "content", "status", "stream_end"]