diff --git a/src/backend/core/services/lobby.py b/src/backend/core/services/lobby.py index d7818a9b..83430e9c 100644 --- a/src/backend/core/services/lobby.py +++ b/src/backend/core/services/lobby.py @@ -145,12 +145,7 @@ class LobbyService: user_role = room.get_role(request.user) if self.can_bypass_lobby(room=room, user=request.user, role=user_role): - self._update_participant_status( - room_id, - participant_id, - LobbyParticipantStatus.ACCEPTED, - settings.LOBBY_ACCEPTED_TIMEOUT - ) + participant = self.handle_participant_entry(room_id, participant.id, True) livekit_config = utils.generate_livekit_config( room_id=room_id, @@ -280,7 +275,7 @@ class LobbyService: room_id: UUID, participant_id: str, allow_entry: bool, - ) -> None: + ) -> LobbyParticipant: """Handle decision on participant entry. Updates participant status based on allow_entry: @@ -298,7 +293,7 @@ class LobbyService: "timeout": settings.LOBBY_DENIED_TIMEOUT, } - self._update_participant_status(room_id, participant_id, **decision) + return self._update_participant_status(room_id, participant_id, **decision) def _update_participant_status( self, @@ -306,7 +301,7 @@ class LobbyService: participant_id: str, status: LobbyParticipantStatus, timeout: int, - ) -> None: + ) -> LobbyParticipant: """Update participant status with appropriate timeout.""" cache_key = self._get_cache_key(room_id, participant_id) @@ -328,6 +323,8 @@ class LobbyService: participant.status = status cache.set(cache_key, participant.to_dict(), timeout=timeout) + return participant + def clear_room_cache(self, room_id: UUID) -> None: """Clear all participant entries from the cache for a specific room.""" diff --git a/src/backend/core/tests/rooms/test_api_rooms_lobby.py b/src/backend/core/tests/rooms/test_api_rooms_lobby.py index 765ea33b..6ce48247 100644 --- a/src/backend/core/tests/rooms/test_api_rooms_lobby.py +++ b/src/backend/core/tests/rooms/test_api_rooms_lobby.py @@ -221,6 +221,10 @@ def test_request_entry_public_room(settings): # The accepted participant is persisted, out of the waiting list lobby_keys = cache.keys(f"mocked-cache-prefix_{room.id}_*") assert len(lobby_keys) == 1 + + ttl = cache.ttl(lobby_keys[0]) + assert ttl is not None + assert ttl == pytest.approx(settings.LOBBY_ACCEPTED_TIMEOUT, abs=2000) assert cache.get(lobby_keys[0])["status"] == "accepted" @@ -268,6 +272,9 @@ def test_request_entry_authenticated_user_public_room(settings): lobby_keys = cache.keys(f"mocked-cache-prefix_{room.id}_*") assert len(lobby_keys) == 1 assert cache.get(lobby_keys[0])["status"] == "accepted" + ttl = cache.ttl(lobby_keys[0]) + assert ttl is not None + assert ttl == pytest.approx(settings.LOBBY_ACCEPTED_TIMEOUT, abs=2000) def test_request_entry_waiting_participant_public_room(settings): @@ -318,6 +325,11 @@ def test_request_entry_waiting_participant_public_room(settings): lobby_keys = cache.keys(f"mocked-cache-prefix_{room.id}_*") assert len(lobby_keys) == 1 + ttl = cache.ttl(lobby_keys[0]) + assert ttl is not None + assert ttl == pytest.approx(settings.LOBBY_ACCEPTED_TIMEOUT, abs=2000) + assert cache.get(lobby_keys[0])["status"] == "accepted" + def test_request_entry_invalid_data(): """Should return 400 for invalid request data.""" diff --git a/src/backend/core/tests/services/test_lobby.py b/src/backend/core/tests/services/test_lobby.py index 86b565b0..faca81dd 100644 --- a/src/backend/core/tests/services/test_lobby.py +++ b/src/backend/core/tests/services/test_lobby.py @@ -7,7 +7,7 @@ Test lobby service. import uuid from unittest import mock -from django.conf import settings +from django.conf import settings as django_settings from django.contrib.auth.models import AnonymousUser from django.core.cache import cache @@ -129,7 +129,7 @@ def test_get_cache_key(lobby_service, participant_id): room = RoomFactory(access_level=RoomAccessLevel.RESTRICTED) cache_key = lobby_service._get_cache_key(room.id, participant_id) - expected_key = f"{settings.LOBBY_KEY_PREFIX}_{room.id!s}_{participant_id}" + expected_key = f"{django_settings.LOBBY_KEY_PREFIX}_{room.id!s}_{participant_id}" assert cache_key == expected_key @@ -196,22 +196,26 @@ def test_can_bypass_lobby_private_room_with_any_role(role, lobby_service): @mock.patch("core.utils.generate_livekit_config") def test_request_entry_public_room( - mock_generate_config, lobby_service, participant_id, username + mock_generate_config, lobby_service, participant_id, username, settings ): """Test requesting entry to a public room.""" + settings.LOBBY_KEY_PREFIX = "mocked-cache-prefix" + request = mock.Mock() request.user = AnonymousUser() room = RoomFactory(access_level=RoomAccessLevel.PUBLIC) - mocked_participant = LobbyParticipant( - status=LobbyParticipantStatus.UNKNOWN, - username=username, - id=participant_id, - color="#123456", + cache.set( + f"mocked-cache-prefix_{room.id}_{participant_id}", + { + "id": participant_id, + "username": username, + "status": "waiting", + "color": "#123456", + }, ) - lobby_service._get_participant = mock.Mock(return_value=mocked_participant) mock_generate_config.return_value = {"token": "test-token"} participant, livekit_config = lobby_service.request_entry( @@ -226,31 +230,33 @@ def test_request_entry_public_room( username=username, color=participant.color, configuration=room.configuration, - participant_id="test-participant-id", + participant_id=participant_id, role=None, ) - lobby_service._get_participant.assert_called_once_with(room.id, participant_id) - @mock.patch("core.utils.generate_livekit_config") def test_request_entry_trusted_room( - mock_generate_config, lobby_service, participant_id, username + mock_generate_config, lobby_service, participant_id, username, settings ): """Test requesting entry to a trusted room when the user is authenticated.""" + settings.LOBBY_KEY_PREFIX = "mocked-cache-prefix" + request = mock.Mock() request.user = UserFactory() room = RoomFactory(access_level=RoomAccessLevel.TRUSTED) - mocked_participant = LobbyParticipant( - status=LobbyParticipantStatus.UNKNOWN, - username=username, - id=participant_id, - color="#123456", + cache.set( + f"mocked-cache-prefix_{room.id}_{participant_id}", + { + "id": participant_id, + "username": username, + "status": "waiting", + "color": "#123456", + }, ) - lobby_service._get_participant = mock.Mock(return_value=mocked_participant) mock_generate_config.return_value = {"token": "test-token"} participant, livekit_config = lobby_service.request_entry( @@ -265,12 +271,10 @@ def test_request_entry_trusted_room( username=username, color=participant.color, configuration=room.configuration, - participant_id="test-participant-id", + participant_id=participant_id, role=None, ) - lobby_service._get_participant.assert_called_once_with(room.id, participant_id) - @mock.patch("core.services.lobby.LobbyService._notify_entry_request") @mock.patch("core.services.lobby.LobbyService._create_participant") @@ -339,21 +343,24 @@ def test_request_entry_waiting_participant( @mock.patch("core.utils.generate_livekit_config") def test_request_entry_accepted_participant( - mock_generate_config, lobby_service, participant_id, username + mock_generate_config, lobby_service, participant_id, username, settings ): """Test requesting entry for an accepted participant.""" + settings.LOBBY_KEY_PREFIX = "mocked-cache-prefix" request = mock.Mock() request.user = AnonymousUser() room = RoomFactory(access_level=RoomAccessLevel.RESTRICTED) - mocked_participant = LobbyParticipant( - status=LobbyParticipantStatus.ACCEPTED, - username=username, - id=participant_id, - color="#123456", + cache.set( + f"mocked-cache-prefix_{room.id}_{participant_id}", + { + "id": participant_id, + "username": username, + "status": "accepted", + "color": "#123456", + }, ) - lobby_service._get_participant = mock.Mock(return_value=mocked_participant) mock_generate_config.return_value = {"token": "test-token"} @@ -372,14 +379,15 @@ def test_request_entry_accepted_participant( participant_id="test-participant-id", role=None, ) - lobby_service._get_participant.assert_called_once_with(room.id, participant_id) @mock.patch("core.utils.generate_livekit_config") def test_request_entry_participant_with_role( - mock_generate_config, lobby_service, participant_id, username + mock_generate_config, lobby_service, participant_id, username, settings ): """Test requesting entry for a participant with a role on the room.""" + settings.LOBBY_KEY_PREFIX = "mocked-cache-prefix" + request = mock.Mock() request.user = UserFactory() @@ -387,13 +395,15 @@ def test_request_entry_participant_with_role( UserResourceAccessFactory(resource=room, user=request.user, role="administrator") - mocked_participant = LobbyParticipant( - status=LobbyParticipantStatus.ACCEPTED, - username=username, - id=participant_id, - color="#123456", + cache.set( + f"mocked-cache-prefix_{room.id}_{participant_id}", + { + "id": participant_id, + "username": username, + "status": "accepted", + "color": "#123456", + }, ) - lobby_service._get_participant = mock.Mock(return_value=mocked_participant) mock_generate_config.return_value = {"token": "test-token"} @@ -412,7 +422,6 @@ def test_request_entry_participant_with_role( participant_id="test-participant-id", role="administrator", ) - lobby_service._get_participant.assert_called_once_with(room.id, participant_id) @mock.patch("core.services.lobby.cache") @@ -422,7 +431,7 @@ def test_refresh_waiting_status(mock_cache, lobby_service, participant_id): room = RoomFactory(access_level=RoomAccessLevel.RESTRICTED) lobby_service.refresh_waiting_status(room.id, participant_id) mock_cache.touch.assert_called_once_with( - "mocked_cache_key", settings.LOBBY_WAITING_TIMEOUT + "mocked_cache_key", django_settings.LOBBY_WAITING_TIMEOUT ) @@ -433,7 +442,6 @@ def test_create_participant( mock_cache, lobby_service, username, - settings, ): """A created participant is waiting, colored, and persisted.""" mock_generate_color.return_value = "#123456" @@ -454,7 +462,7 @@ def test_create_participant( mock_cache.set.assert_called_once_with( "mocked_cache_key", participant.to_dict(), - timeout=settings.LOBBY_WAITING_TIMEOUT, + timeout=django_settings.LOBBY_WAITING_TIMEOUT, ) @@ -512,7 +520,7 @@ def test_list_waiting_participants_empty(mock_cache, lobby_service): result = lobby_service.list_waiting_participants(room.id) assert result == [] - pattern = f"{settings.LOBBY_KEY_PREFIX}_{room.id!s}_*" + pattern = f"{django_settings.LOBBY_KEY_PREFIX}_{room.id!s}_*" mock_cache.keys.assert_called_once_with(pattern) mock_cache.get_many.assert_not_called() @@ -521,7 +529,7 @@ def test_list_waiting_participants_empty(mock_cache, lobby_service): def test_list_waiting_participants(mock_cache, lobby_service, participant_dict): """Test listing waiting participants with valid data.""" room = RoomFactory(access_level=RoomAccessLevel.RESTRICTED) - cache_key = f"{settings.LOBBY_KEY_PREFIX}_{room.id!s}_participant1" + cache_key = f"{django_settings.LOBBY_KEY_PREFIX}_{room.id!s}_participant1" mock_cache.keys.return_value = [cache_key] mock_cache.get_many.return_value = {cache_key: participant_dict} @@ -530,7 +538,7 @@ def test_list_waiting_participants(mock_cache, lobby_service, participant_dict): assert len(result) == 1 assert result[0]["status"] == "waiting" assert result[0]["username"] == "test-username" - pattern = f"{settings.LOBBY_KEY_PREFIX}_{room.id!s}_*" + pattern = f"{django_settings.LOBBY_KEY_PREFIX}_{room.id!s}_*" mock_cache.keys.assert_called_once_with(pattern) mock_cache.get_many.assert_called_once_with([cache_key]) @@ -539,8 +547,8 @@ def test_list_waiting_participants(mock_cache, lobby_service, participant_dict): def test_list_waiting_participants_multiple(mock_cache, lobby_service): """Test listing multiple waiting participants with valid data.""" room = RoomFactory(access_level=RoomAccessLevel.RESTRICTED) - cache_key1 = f"{settings.LOBBY_KEY_PREFIX}_{room.id!s}_participant1" - cache_key2 = f"{settings.LOBBY_KEY_PREFIX}_{room.id!s}_participant2" + cache_key1 = f"{django_settings.LOBBY_KEY_PREFIX}_{room.id!s}_participant1" + cache_key2 = f"{django_settings.LOBBY_KEY_PREFIX}_{room.id!s}_participant2" participant1 = { "status": "waiting", @@ -573,7 +581,7 @@ def test_list_waiting_participants_multiple(mock_cache, lobby_service): # Verify all participants have waiting status assert all(p["status"] == "waiting" for p in result) - pattern = f"{settings.LOBBY_KEY_PREFIX}_{room.id!s}_*" + pattern = f"{django_settings.LOBBY_KEY_PREFIX}_{room.id!s}_*" mock_cache.keys.assert_called_once_with(pattern) mock_cache.get_many.assert_called_once_with([cache_key1, cache_key2]) @@ -582,7 +590,7 @@ def test_list_waiting_participants_multiple(mock_cache, lobby_service): def test_list_waiting_participants_corrupted_data(mock_cache, lobby_service): """Test listing waiting participants with corrupted data.""" room = RoomFactory(access_level=RoomAccessLevel.RESTRICTED) - cache_key = f"{settings.LOBBY_KEY_PREFIX}_{room.id!s}_participant1" + cache_key = f"{django_settings.LOBBY_KEY_PREFIX}_{room.id!s}_participant1" mock_cache.keys.return_value = [cache_key] mock_cache.get_many.return_value = {cache_key: {"invalid": "data"}} @@ -596,8 +604,8 @@ def test_list_waiting_participants_corrupted_data(mock_cache, lobby_service): def test_list_waiting_participants_partially_corrupted(mock_cache, lobby_service): """Test listing waiting participants with one valid and one corrupted entry.""" room = RoomFactory(access_level=RoomAccessLevel.RESTRICTED) - cache_key1 = f"{settings.LOBBY_KEY_PREFIX}_{room.id!s}_participant1" - cache_key2 = f"{settings.LOBBY_KEY_PREFIX}_{room.id!s}_participant2" + cache_key1 = f"{django_settings.LOBBY_KEY_PREFIX}_{room.id!s}_participant1" + cache_key2 = f"{django_settings.LOBBY_KEY_PREFIX}_{room.id!s}_participant2" valid_participant = { "status": "waiting", @@ -626,7 +634,7 @@ def test_list_waiting_participants_partially_corrupted(mock_cache, lobby_service mock_cache.delete.assert_called_once_with(cache_key1) # Verify both cache keys were queried - pattern = f"{settings.LOBBY_KEY_PREFIX}_{room.id!s}_*" + pattern = f"{django_settings.LOBBY_KEY_PREFIX}_{room.id!s}_*" mock_cache.keys.assert_called_once_with(pattern) mock_cache.get_many.assert_called_once_with([cache_key1, cache_key2]) @@ -635,8 +643,8 @@ def test_list_waiting_participants_partially_corrupted(mock_cache, lobby_service def test_list_waiting_participants_non_waiting(mock_cache, lobby_service): """Test listing only waiting participants (not accepted/denied).""" room = RoomFactory(access_level=RoomAccessLevel.RESTRICTED) - cache_key1 = f"{settings.LOBBY_KEY_PREFIX}_{room.id!s}_participant1" - cache_key2 = f"{settings.LOBBY_KEY_PREFIX}_{room.id!s}_participant2" + cache_key1 = f"{django_settings.LOBBY_KEY_PREFIX}_{room.id!s}_participant1" + cache_key2 = f"{django_settings.LOBBY_KEY_PREFIX}_{room.id!s}_participant2" participant1 = { "status": "waiting", @@ -674,7 +682,7 @@ def test_handle_participant_entry_allow(mock_update, lobby_service, participant_ room.id, participant_id, status=LobbyParticipantStatus.ACCEPTED, - timeout=settings.LOBBY_ACCEPTED_TIMEOUT, + timeout=django_settings.LOBBY_ACCEPTED_TIMEOUT, ) @@ -688,7 +696,7 @@ def test_handle_participant_entry_deny(mock_update, lobby_service, participant_i room.id, participant_id, status=LobbyParticipantStatus.DENIED, - timeout=settings.LOBBY_DENIED_TIMEOUT, + timeout=django_settings.LOBBY_DENIED_TIMEOUT, ) @@ -829,14 +837,16 @@ def test_clear_participant_cache(lobby_service): room_id = uuid.uuid4() participant_id = "test-participant-id" - cache_key = f"{settings.LOBBY_KEY_PREFIX}_{room_id!s}_{participant_id}" + cache_key = f"{django_settings.LOBBY_KEY_PREFIX}_{room_id!s}_{participant_id}" participant_data = { "status": "waiting", "username": "test-username", "id": participant_id, "color": "#123456", } - cache.set(cache_key, participant_data, timeout=settings.LOBBY_WAITING_TIMEOUT) + cache.set( + cache_key, participant_data, timeout=django_settings.LOBBY_WAITING_TIMEOUT + ) assert cache.get(cache_key) is not None lobby_service.clear_participant_cache(room_id, participant_id) @@ -848,7 +858,7 @@ def test_clear_participant_cache_nonexistent(lobby_service): room_id = uuid.uuid4() participant_id = "nonexistent-participant" - cache_key = f"{settings.LOBBY_KEY_PREFIX}_{room_id!s}_{participant_id}" + cache_key = f"{django_settings.LOBBY_KEY_PREFIX}_{room_id!s}_{participant_id}" assert cache.get(cache_key) is None lobby_service.clear_participant_cache(room_id, participant_id)