From 5790bb1e477fa8ece7506b850415b45b92ab3efc Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Thu, 30 Jul 2026 21:01:07 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B(backend)=20ensure=20SIP=20dispatch?= =?UTF-8?q?=20rule=20instead=20of=20creating=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The roomkit can now create a SIP dispatch rule before the LiveKit webhook that used to trigger this creation is fired. In practice, when the roomkit connects to the room, it also triggers the webhook, leading to a duplicated dispatch rule. Switch from "create dispatch rule" to "ensure dispatch rule exists" semantics, so subsequent calls are idempotent and no duplicate rule is created. --- src/backend/core/services/livekit_events.py | 2 +- .../tests/services/test_livekit_events.py | 67 +++++++++++++++---- 2 files changed, 54 insertions(+), 15 deletions(-) diff --git a/src/backend/core/services/livekit_events.py b/src/backend/core/services/livekit_events.py index 32e7be98..b6e0ad71 100644 --- a/src/backend/core/services/livekit_events.py +++ b/src/backend/core/services/livekit_events.py @@ -247,7 +247,7 @@ class LiveKitEventsService: if settings.ROOM_TELEPHONY_ENABLED or settings.ROOMKIT_ENABLED: try: - self.sip_management.create_dispatch_rule(room) + self.sip_management.ensure_dispatch_rule(room) except SIPException as e: raise ActionFailedError( f"Failed to create sip dispatch rule for room {room_id}" diff --git a/src/backend/core/tests/services/test_livekit_events.py b/src/backend/core/tests/services/test_livekit_events.py index e0a1054f..50a9d92d 100644 --- a/src/backend/core/tests/services/test_livekit_events.py +++ b/src/backend/core/tests/services/test_livekit_events.py @@ -21,7 +21,10 @@ from core.services.livekit_events import ( ) from core.services.lobby import LobbyService from core.services.room_management import RoomManagementException -from core.services.sip_management import SIPException, SIPManagement +from core.services.sip_management import ( + SIPException, + SIPManagement, +) from core.utils import NotificationError pytestmark = pytest.mark.django_db @@ -581,11 +584,11 @@ def test_handle_room_finished_raises_error_for_invalid_room_name(service): service._handle_room_finished(mock_data) -@mock.patch.object(SIPManagement, "create_dispatch_rule") +@mock.patch.object(SIPManagement, "ensure_dispatch_rule") def test_handle_room_started_creates_dispatch_rule_successfully( - mock_create_dispatch_rule, service, settings + mock_ensure_dispatch_rule, service, settings ): - """Should create SIP dispatch rule when room starts successfully.""" + """Should ensure the SIP dispatch rule exists when room starts successfully.""" settings.ROOM_TELEPHONY_ENABLED = True room = RoomFactory() mock_data = mock.MagicMock() @@ -593,14 +596,14 @@ def test_handle_room_started_creates_dispatch_rule_successfully( service._handle_room_started(mock_data) - mock_create_dispatch_rule.assert_called_once_with(room) + mock_ensure_dispatch_rule.assert_called_once_with(room) -@mock.patch.object(SIPManagement, "create_dispatch_rule") +@mock.patch.object(SIPManagement, "ensure_dispatch_rule") def test_handle_room_started_creates_dispatch_rule_when_only_roomkit_enabled( - mock_create_dispatch_rule, service, settings + mock_ensure_dispatch_rule, service, settings ): - """Should create dispatch rule when only roomkit is enabled during room start.""" + """Should ensure the dispatch rule exists when only roomkit is enabled during room start.""" settings.ROOM_TELEPHONY_ENABLED = False settings.ROOMKIT_ENABLED = True room = RoomFactory() @@ -609,14 +612,50 @@ def test_handle_room_started_creates_dispatch_rule_when_only_roomkit_enabled( service._handle_room_started(mock_data) - mock_create_dispatch_rule.assert_called_once_with(room) + mock_ensure_dispatch_rule.assert_called_once_with(room) -@mock.patch.object(SIPManagement, "create_dispatch_rule") -def test_handle_room_started_skips_dispatch_rule_when_telephony_disabled( - mock_create_dispatch_rule, service, settings +@mock.patch.object(SIPManagement, "ensure_dispatch_rule", return_value=False) +def test_handle_room_started_ignores_existing_dispatch_rule( + mock_ensure_dispatch_rule, service, settings ): - """Should skip creating SIP dispatch rule when telephony is disabled during room start.""" + """Should proceed silently when the dispatch rule already exists when room starts.""" + settings.ROOM_TELEPHONY_ENABLED = True + room = RoomFactory() + mock_data = mock.MagicMock() + mock_data.room.name = str(room.id) + + # ensure_dispatch_rule reports the rule as pre-existing: nothing to raise + service._handle_room_started(mock_data) + + mock_ensure_dispatch_rule.assert_called_once_with(room) + + +@mock.patch.object( + SIPManagement, + "ensure_dispatch_rule", + side_effect=SIPException("Test error"), +) +def test_handle_room_started_raises_error_when_dispatch_rule_creation_fails( + mock_ensure_dispatch_rule, service, settings +): + """Should raise ActionFailedError when ensuring the dispatch rule fails when room starts.""" + settings.ROOM_TELEPHONY_ENABLED = True + room = RoomFactory() + mock_data = mock.MagicMock() + mock_data.room.name = str(room.id) + + expected_error = f"Failed to create sip dispatch rule for room {room.id}" + + with pytest.raises(ActionFailedError, match=expected_error): + service._handle_room_started(mock_data) + + +@mock.patch.object(SIPManagement, "ensure_dispatch_rule") +def test_handle_room_started_skips_dispatch_rule_when_telephony_disabled( + mock_ensure_dispatch_rule, service, settings +): + """Should skip ensuring the SIP dispatch rule when telephony is disabled during room start.""" settings.ROOM_TELEPHONY_ENABLED = False settings.ROOMKIT_ENABLED = False room = RoomFactory() @@ -625,7 +664,7 @@ def test_handle_room_started_skips_dispatch_rule_when_telephony_disabled( service._handle_room_started(mock_data) - mock_create_dispatch_rule.assert_not_called() + mock_ensure_dispatch_rule.assert_not_called() def test_handle_room_started_raises_error_for_invalid_room_name(service):