mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-08-14 00:47:52 +00:00
164 lines
5.5 KiB
Python
164 lines
5.5 KiB
Python
"""Tests for WebSocket event bridge."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import time
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from openjarvis.core.events import EventBus, EventType
|
|
|
|
try:
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
|
|
HAS_FASTAPI = True
|
|
except ImportError:
|
|
HAS_FASTAPI = False
|
|
|
|
pytestmark = pytest.mark.skipif(not HAS_FASTAPI, reason="fastapi not installed")
|
|
|
|
|
|
@pytest.fixture
|
|
def event_bus():
|
|
return EventBus()
|
|
|
|
|
|
@pytest.fixture
|
|
def app(event_bus):
|
|
from openjarvis.server.ws_bridge import create_ws_router
|
|
|
|
app = FastAPI()
|
|
router = create_ws_router(event_bus)
|
|
app.include_router(router)
|
|
return app
|
|
|
|
|
|
class TestWSBridge:
|
|
def test_websocket_receives_events(self, app, event_bus):
|
|
client = TestClient(app)
|
|
with client.websocket_connect("/v1/agents/events") as ws:
|
|
event_bus.publish(
|
|
EventType.AGENT_TICK_START,
|
|
{
|
|
"agent_id": "test-123",
|
|
"agent_name": "test",
|
|
},
|
|
)
|
|
time.sleep(0.05) # Let call_soon_threadsafe deliver to queue
|
|
data = ws.receive_json()
|
|
assert data["type"] == "agent_tick_start"
|
|
assert data["data"]["agent_id"] == "test-123"
|
|
|
|
def test_websocket_filters_by_agent_id(self, app, event_bus):
|
|
client = TestClient(app)
|
|
with client.websocket_connect("/v1/agents/events?agent_id=agent-A") as ws:
|
|
# This event should NOT be received (different agent)
|
|
event_bus.publish(EventType.AGENT_TICK_START, {"agent_id": "agent-B"})
|
|
# This event SHOULD be received
|
|
event_bus.publish(EventType.AGENT_TICK_START, {"agent_id": "agent-A"})
|
|
time.sleep(0.05) # Let call_soon_threadsafe deliver to queue
|
|
data = ws.receive_json()
|
|
assert data["data"]["agent_id"] == "agent-A"
|
|
|
|
def test_client_disconnect_stops_handler(self, event_bus):
|
|
async def exercise():
|
|
from openjarvis.server.ws_bridge import create_ws_router
|
|
|
|
class FakeWebSocket:
|
|
app = SimpleNamespace(state=SimpleNamespace(api_key=""))
|
|
query_params = {}
|
|
headers = {}
|
|
|
|
async def accept(self):
|
|
pass
|
|
|
|
async def receive(self):
|
|
return {"type": "websocket.disconnect"}
|
|
|
|
endpoint = create_ws_router(event_bus).routes[0].endpoint
|
|
await asyncio.wait_for(endpoint(FakeWebSocket()), timeout=1)
|
|
|
|
asyncio.run(exercise())
|
|
|
|
def test_simultaneous_client_message_does_not_drop_event(self, event_bus):
|
|
async def exercise():
|
|
from openjarvis.server.ws_bridge import create_ws_router
|
|
|
|
class FakeWebSocket:
|
|
def __init__(self):
|
|
self.app = SimpleNamespace(state=SimpleNamespace(api_key=""))
|
|
self.query_params = {}
|
|
self.headers = {}
|
|
self.sent = []
|
|
self.receive_count = 0
|
|
self.disconnect = asyncio.Event()
|
|
|
|
async def accept(self):
|
|
pass
|
|
|
|
async def receive(self):
|
|
self.receive_count += 1
|
|
if self.receive_count == 1:
|
|
event_bus.publish(
|
|
EventType.AGENT_TICK_START, {"agent_id": "not-dropped"}
|
|
)
|
|
return {"type": "websocket.receive", "text": "client message"}
|
|
await self.disconnect.wait()
|
|
return {"type": "websocket.disconnect"}
|
|
|
|
async def send_json(self, payload):
|
|
self.sent.append(payload)
|
|
self.disconnect.set()
|
|
|
|
websocket = FakeWebSocket()
|
|
endpoint = create_ws_router(event_bus).routes[0].endpoint
|
|
|
|
await asyncio.wait_for(endpoint(websocket), timeout=1)
|
|
|
|
assert websocket.sent[0]["data"]["agent_id"] == "not-dropped"
|
|
|
|
asyncio.run(exercise())
|
|
|
|
def test_cancelling_handler_cleans_up_child_tasks(self, event_bus):
|
|
async def exercise():
|
|
from openjarvis.server.ws_bridge import create_ws_router
|
|
|
|
class FakeWebSocket:
|
|
def __init__(self):
|
|
self.app = SimpleNamespace(state=SimpleNamespace(api_key=""))
|
|
self.query_params = {}
|
|
self.headers = {}
|
|
self.receiving = asyncio.Event()
|
|
self.receive_cancelled = asyncio.Event()
|
|
|
|
async def accept(self):
|
|
pass
|
|
|
|
async def receive(self):
|
|
self.receiving.set()
|
|
try:
|
|
await asyncio.Event().wait()
|
|
finally:
|
|
self.receive_cancelled.set()
|
|
|
|
websocket = FakeWebSocket()
|
|
endpoint = create_ws_router(event_bus).routes[0].endpoint
|
|
handler = asyncio.create_task(endpoint(websocket))
|
|
await websocket.receiving.wait()
|
|
|
|
handler.cancel()
|
|
with pytest.raises(asyncio.CancelledError):
|
|
await handler
|
|
|
|
assert websocket.receive_cancelled.is_set()
|
|
assert not [
|
|
task
|
|
for task in asyncio.all_tasks()
|
|
if task is not asyncio.current_task() and not task.done()
|
|
]
|
|
|
|
asyncio.run(exercise())
|