From 7c365a5f756c09b775261f6440fcface5fe01cbc Mon Sep 17 00:00:00 2001 From: aniebietafia Date: Sat, 9 May 2026 14:06:54 +0100 Subject: [PATCH 1/2] feat(signaling): implement existing_users event for late joiners - Emit 'existing_users' payload containing IDs, names, and roles of current peers - Update signaling WebSocket to fetch room state before entering the message loop - Update test suite to mock MeetingStateService and consume the new initial event Signed-off-by: aniebietafia --- app/modules/meeting/ws_router.py | 13 +++++++++++++ tests/meeting/test_ws_router.py | 17 ++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/app/modules/meeting/ws_router.py b/app/modules/meeting/ws_router.py index ff4730e..39f95b5 100644 --- a/app/modules/meeting/ws_router.py +++ b/app/modules/meeting/ws_router.py @@ -74,6 +74,19 @@ async def signaling_websocket( sender_id=user_id, # Don't echo back to the joiner themselves ) + # Tell the new user about all existing users so they can update their UI immediately + participants = await MeetingStateService().get_participants(room_code) + existing_users = [ + { + "user_id": pid, + "display_name": pstate.get("display_name", ""), + "role": pstate.get("role", "guest"), + } + for pid, pstate in participants.items() + if pid != user_id + ] + await websocket.send_json({"type": "existing_users", "users": existing_users}) + try: while True: data = await websocket.receive_text() diff --git a/tests/meeting/test_ws_router.py b/tests/meeting/test_ws_router.py index 91b0254..81adb1f 100644 --- a/tests/meeting/test_ws_router.py +++ b/tests/meeting/test_ws_router.py @@ -60,12 +60,27 @@ def mock_kafka_consumer(): yield consumer +@pytest.fixture +def mock_meeting_state(): + with patch( + "app.modules.meeting.ws_router.MeetingStateService" + ) as mock_service_class: + service = MagicMock() + service.get_participants = AsyncMock(return_value={}) + mock_service_class.return_value = service + yield service + + @pytest.mark.usefixtures("mock_room_participant") -def test_signaling_websocket(mock_connection_manager): +def test_signaling_websocket(mock_connection_manager, mock_meeting_state): # This will connect, send a text message, and then close with client.websocket_connect( "/api/v1/ws/signaling/room1?token=mock_token" ) as websocket: + # Consume the initial existing_users message + data = websocket.receive_json() + assert data["type"] == "existing_users" + websocket.send_text(json.dumps({"type": "offer", "target_user_id": "user2"})) # The connection manager's send_to_user should be called From 08b6ae1f3ed338a73564b81ccc72c998cdbb2241 Mon Sep 17 00:00:00 2001 From: aniebietafia Date: Sat, 9 May 2026 14:13:37 +0100 Subject: [PATCH 2/2] fixed ruff errors Signed-off-by: aniebietafia --- tests/meeting/test_ws_router.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/meeting/test_ws_router.py b/tests/meeting/test_ws_router.py index 81adb1f..1ff8bdc 100644 --- a/tests/meeting/test_ws_router.py +++ b/tests/meeting/test_ws_router.py @@ -71,8 +71,8 @@ def mock_meeting_state(): yield service -@pytest.mark.usefixtures("mock_room_participant") -def test_signaling_websocket(mock_connection_manager, mock_meeting_state): +@pytest.mark.usefixtures("mock_room_participant", "mock_meeting_state") +def test_signaling_websocket(mock_connection_manager): # This will connect, send a text message, and then close with client.websocket_connect( "/api/v1/ws/signaling/room1?token=mock_token"