fix(redis): 修复 Redis 流读取兼容性问题

- 支持 bytes 和 str 类型的 entry_id
- 支持 list 类型响应格式
- 优化 payload 解码处理
This commit is contained in:
qzl
2026-03-11 21:33:25 +08:00
parent e4f69a64bd
commit 18db6c50e7
17 changed files with 359 additions and 54 deletions
@@ -110,6 +110,18 @@ class _FakeAgentService:
}
class _FailingStreamAgentService(_FakeAgentService):
async def stream_events(
self,
*,
thread_id: str,
last_event_id: str | None,
current_user: CurrentUser,
) -> list[dict[str, object]]:
del thread_id, last_event_id, current_user
raise RuntimeError("redis timeout")
def test_run_requires_auth_and_returns_202_task_id() -> None:
app.dependency_overrides[get_agent_service] = lambda: _FakeAgentService()
client = TestClient(app)
@@ -197,6 +209,38 @@ def test_stream_reads_from_last_event_id() -> None:
app.dependency_overrides = {}
def test_stream_handles_stream_backend_errors_without_connection_crash() -> None:
app.dependency_overrides[get_agent_service] = lambda: _FailingStreamAgentService()
app.dependency_overrides[get_current_user] = lambda: CurrentUser(
id=uuid4(), email="user@example.com"
)
client = TestClient(app)
original_acquire = agent_router._acquire_sse_slot
original_release = agent_router._release_sse_slot
async def _allow_slot(*, user_id: str) -> bool:
del user_id
return True
async def _noop_release(*, user_id: str) -> None:
del user_id
return None
agent_router._acquire_sse_slot = _allow_slot # type: ignore[assignment]
agent_router._release_sse_slot = _noop_release # type: ignore[assignment]
try:
response = client.get(
"/api/v1/agent/runs/00000000-0000-0000-0000-000000000001/events?idle_limit=1"
)
assert response.status_code == 200
assert response.headers["content-type"].startswith("text/event-stream")
finally:
agent_router._acquire_sse_slot = original_acquire # type: ignore[assignment]
agent_router._release_sse_slot = original_release # type: ignore[assignment]
app.dependency_overrides = {}
def test_stream_rejects_invalid_last_event_id() -> None:
app.dependency_overrides[get_agent_service] = lambda: _FakeAgentService()
app.dependency_overrides[get_current_user] = lambda: CurrentUser(
@@ -142,20 +142,19 @@ async def test_agent_runs_events_history_live_with_image_input() -> None:
assert run_resp.status_code == 202
events_url = f"{BASE_URL}/api/v1/agent/runs/{thread_id}/events"
event_names: list[str] = []
async with client.stream(
"GET", events_url, headers=headers, timeout=20.0
) as sse_resp:
assert sse_resp.status_code == 200
assert sse_resp.headers.get("content-type", "").startswith(
"text/event-stream"
)
async for line in sse_resp.aiter_lines():
if line.startswith("event:"):
event_name = line.split(":", 1)[1].strip()
event_names.append(event_name)
if event_name in {"RUN_FINISHED", "RUN_ERROR"}:
break
sse_resp = await client.get(
events_url,
headers=headers,
params={"idle_limit": 150},
timeout=60.0,
)
assert sse_resp.status_code == 200
assert sse_resp.headers.get("content-type", "").startswith("text/event-stream")
event_names = [
line.split(":", 1)[1].strip()
for line in sse_resp.text.splitlines()
if line.startswith("event:")
]
assert "RUN_STARTED" in event_names
assert "RUN_FINISHED" in event_names or "RUN_ERROR" in event_names