🚧 Update topic explorer
This commit is contained in:
@@ -116,7 +116,10 @@ async def stream(session_id: str, user: OperatorRole):
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, "session not found") from exc
|
||||
|
||||
async def event_stream():
|
||||
index = 0
|
||||
# Start BELOW the first index: messages_since uses strictly-
|
||||
# greater semantics, and a fresh session's first message has
|
||||
# index 0 — a cursor starting at 0 would drop it.
|
||||
index = -1
|
||||
heartbeat = 0.0
|
||||
try:
|
||||
while True:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -256,6 +256,7 @@ def client(db_session, monkeypatch):
|
||||
self.id = id
|
||||
self.username = username
|
||||
self.topic_filter = topic_filter
|
||||
self.buffer = [] # list of message dicts, index-tagged
|
||||
|
||||
class FakeSessions:
|
||||
def __init__(self):
|
||||
@@ -281,7 +282,8 @@ def client(db_session, monkeypatch):
|
||||
monkeypatch.setattr(explorer_module, "sessions", fake_sessions)
|
||||
|
||||
def fake_messages_since(session_id, index):
|
||||
return []
|
||||
session = fake_sessions.get(session_id)
|
||||
return [dict(m) for m in session.buffer if m["index"] > index]
|
||||
|
||||
def fake_list_retained(max_results=200, timeout=3.0):
|
||||
return [
|
||||
|
||||
@@ -283,6 +283,58 @@ def test_topic_explorer_flow(admin_client):
|
||||
assert any(m["topic"] == "site/config/level" for m in retained.json())
|
||||
|
||||
|
||||
def test_topic_explorer_stream_delivers_first_message(admin_client, monkeypatch):
|
||||
"""Regression test: the SSE cursor must include a session's FIRST
|
||||
buffered message (index 0). messages_since is strictly-greater, so
|
||||
a cursor starting at 0 dropped it — subscribe, publish {"cmd":"start"}
|
||||
then {"cmd":"stop"}, and the stream must deliver BOTH."""
|
||||
from app import explorer as explorer_module
|
||||
|
||||
sub = admin_client.post("/api/v1/topics/subscribe", json={"topic_filter": "e2e/#"})
|
||||
assert sub.status_code == 200
|
||||
session_id = sub.json()["session_id"]
|
||||
|
||||
session = explorer_module.sessions.get(session_id)
|
||||
# Simulate the broker delivering both payloads (as _on_message would).
|
||||
session.buffer = [
|
||||
{
|
||||
"index": 0,
|
||||
"topic": "e2e/diff/device1",
|
||||
"qos": 1,
|
||||
"retain": False,
|
||||
"received_at": 1.0,
|
||||
"payload_b64": "eyJjbWQiOiAic3RhcnQifQ==",
|
||||
"payload_text": '{"cmd": "start"}',
|
||||
"properties": None,
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"topic": "e2e/diff/device1",
|
||||
"qos": 1,
|
||||
"retain": False,
|
||||
"received_at": 2.0,
|
||||
"payload_b64": "eyJjbWQiOiAic3RvcCJ9",
|
||||
"payload_text": '{"cmd": "stop"}',
|
||||
"properties": None,
|
||||
},
|
||||
]
|
||||
|
||||
with admin_client.stream("GET", f"/api/v1/topics/stream/{session_id}") as response:
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"].startswith("text/event-stream")
|
||||
received = []
|
||||
for line in response.iter_lines():
|
||||
if line.startswith("data:"):
|
||||
received.append(json.loads(line[len("data:"):].strip()))
|
||||
if len(received) == 2:
|
||||
break
|
||||
|
||||
assert [m["payload_text"] for m in received] == [
|
||||
'{"cmd": "start"}',
|
||||
'{"cmd": "stop"}',
|
||||
], "stream must deliver the session's first message (index 0), not drop it"
|
||||
|
||||
|
||||
def test_topic_explorer_publish_audited(admin_client, db_session):
|
||||
"""Publishes through the test console are audited (spec section 35),
|
||||
and wildcards are rejected."""
|
||||
|
||||
Reference in New Issue
Block a user