feat(agent): SSE — progressive Themen-Tabs (Phase 2)
Der Compliance-Check streamt jetzt progressive Events; der Impressum-Tab
erscheint, sobald das Thema fertig ist, statt am Ende alles auf einmal.
Additiv — das Polling fürs finale Ergebnis bleibt.
- backend: _sse.py (Queue/emit/event_generator) + Endpoint
/compliance-check/{id}/stream; _update emittiert progress,
run_agent_outputs emittiert topic (laeuft jetzt frueh nach Phase B),
Orchestrator emittiert complete/error.
- frontend: SSE-Proxy-Route + EventSource in ComplianceCheckTab merged
topic-Events in agent_outputs -> Tab erscheint progressiv.
- Tests: backend 5 passed (SSE + agent_outputs); tsc 0 neue Fehler,
vitest 2 passed, check-loc 0.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
"""Phase 2: SSE-Plumbing für den Compliance-Check.
|
||||
|
||||
Deckt emit (Queue-Push), _format_sse (SSE-Zeilenformat) und den
|
||||
event_generator (hello → Events → stream_close bei 'complete') ab.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
from compliance.api.agent_check import _sse
|
||||
|
||||
|
||||
def test_emit_pushes_and_format():
|
||||
cid = "sse-test-1"
|
||||
_sse.new_queue(cid)
|
||||
_sse.emit(cid, {"type": "topic", "topic": "impressum", "output": {"x": 1}})
|
||||
q = _sse._check_queues[cid]
|
||||
assert q.qsize() == 1
|
||||
ev = q.get_nowait()
|
||||
assert ev["type"] == "topic" and ev["topic"] == "impressum"
|
||||
line = _sse._format_sse(ev)
|
||||
assert line.startswith("data: ") and line.endswith("\n\n")
|
||||
assert '"impressum"' in line
|
||||
|
||||
|
||||
def test_emit_is_noop_without_queue():
|
||||
# Kein new_queue → emit darf nicht crashen (best-effort).
|
||||
_sse.emit("does-not-exist-xyz", {"type": "topic"})
|
||||
|
||||
|
||||
def test_event_generator_streams_topic_then_closes_on_complete():
|
||||
cid = "sse-test-gen"
|
||||
_sse.new_queue(cid)
|
||||
_sse.emit(cid, {"type": "topic", "topic": "impressum", "output": {}})
|
||||
_sse.emit(cid, {"type": "complete", "status": "completed"})
|
||||
|
||||
async def collect():
|
||||
out = []
|
||||
async for line in _sse.event_generator(cid):
|
||||
out.append(line)
|
||||
if len(out) > 12: # safety
|
||||
break
|
||||
return out
|
||||
|
||||
blob = "".join(asyncio.run(collect()))
|
||||
assert '"type": "hello"' in blob
|
||||
assert '"topic": "impressum"' in blob
|
||||
assert '"type": "complete"' in blob
|
||||
assert '"type": "stream_close"' in blob
|
||||
Reference in New Issue
Block a user