From 4f0c3567b52505364280a435c2a6b21d0d9fdc01 Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Tue, 4 Aug 2026 23:23:26 +0200 Subject: [PATCH] =?UTF-8?q?fixup!=20=E2=9C=A8(backend)=20add=20connection-?= =?UTF-8?q?test=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/core/api/diagnostics.py | 77 ------------------- src/backend/core/api/viewsets.py | 68 ++++++++++++++++ src/backend/core/services/livekit_events.py | 21 ++--- src/backend/core/services/room_management.py | 28 +++++++ src/backend/core/tasks/connection_test.py | 30 +++----- .../tests/services/test_livekit_events.py | 59 +++++++------- .../tests/services/test_room_management.py | 60 +++++++++++++++ .../core/tests/tasks/test_connection_test.py | 58 +++++++------- .../core/tests/test_api_diagnostics.py | 31 ++++---- src/backend/core/urls.py | 4 +- 10 files changed, 248 insertions(+), 188 deletions(-) delete mode 100644 src/backend/core/api/diagnostics.py create mode 100644 src/backend/core/tests/services/test_room_management.py diff --git a/src/backend/core/api/diagnostics.py b/src/backend/core/api/diagnostics.py deleted file mode 100644 index 33123aae..00000000 --- a/src/backend/core/api/diagnostics.py +++ /dev/null @@ -1,77 +0,0 @@ -"""Diagnostics API endpoints.""" - -from datetime import timedelta -from uuid import uuid4 - -from django.conf import settings - -from rest_framework import decorators, permissions, viewsets -from rest_framework import ( - response as drf_response, -) - -from core.api import throttling -from core.tasks.connection_test import delete_connection_test_room -from core.utils import generate_token - -CONNECTION_TEST_USERNAME = "Test connexion" - - -class DiagnosticsViewSet(viewsets.ViewSet): - """Endpoints helping users and support diagnose connectivity issues. - - Diagnostics are grouped behind a single prefix so upcoming checks - (rtcstats collection, ICE candidate reports, etc.) can be added as new - actions rather than new top-level routes. - - They are open to anonymous users: someone who cannot join a room is - exactly who needs to run a test, and they may well not be logged in. - Each action therefore carries its own throttle scope. - """ - - permission_classes = [permissions.AllowAny] - - @decorators.action( - detail=False, - methods=["get"], - url_path="connection", - url_name="connection", - throttle_classes=[ - throttling.ConnectionTestUserRateThrottle, - throttling.ConnectionTestAnonRateThrottle, - ], - ) - def connection(self, request): - """Return a short-lived LiveKit token for an ephemeral test room. - - Going through the room API is not an option here: it is tied to - registered meetings, lobby rules and longer-lived tokens. Each call - gets its own room so two people testing at the same time never meet. - """ - room = f"{settings.CONNECTION_TEST_ROOM_PREFIX}-{uuid4()}" - expires_in = settings.CONNECTION_TEST_TOKEN_TTL_SECONDS - - # LiveKit refreshes tokens for connected clients, so JWT TTL alone does not - # eject someone who stays connected. Schedule a hard DeleteRoom when Celery - # is available. - if settings.CELERY_ENABLED: - delete_connection_test_room.apply_async( - args=[room], - countdown=settings.CONNECTION_TEST_ROOM_MAX_AGE_SECONDS, - ) - - return drf_response.Response( - { - "livekit": { - "url": settings.LIVEKIT_CONFIGURATION["url"], - "room": room, - "token": generate_token( - room=room, - user=request.user, - username=CONNECTION_TEST_USERNAME, - ttl=timedelta(seconds=expires_in), - ), - "expires_in": expires_in, - }, - } - ) diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index 8a088ace..cfc27a40 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -2,8 +2,10 @@ # pylint: disable=too-many-lines import uuid +from datetime import timedelta from logging import getLogger from urllib.parse import unquote, urlparse +from uuid import uuid4 from django.conf import settings from django.core.exceptions import ValidationError as DjangoValidationError @@ -27,6 +29,9 @@ from rest_framework import ( from rest_framework import ( exceptions as drf_exceptions, ) +from rest_framework import ( + permissions as drf_permissions, +) from rest_framework import ( response as drf_response, ) @@ -36,6 +41,7 @@ from rest_framework import ( from rest_framework.settings import api_settings from core import analytics, enums, models, utils +from core.api import throttling from core.api.filters import ListFileFilter from core.enums import MEDIA_STORAGE_URL_PATTERN from core.recording.enums import FileExtension @@ -93,7 +99,9 @@ from core.services.room_roles import ( RoomRoleService, ) from core.services.subtitle import SubtitleException, SubtitleService +from core.tasks.connection_test import delete_connection_test_room from core.tasks.file import process_file_deletion +from core.utils import generate_token from ..authentication.livekit import LiveKitTokenAuthentication from ..models import RoomAccessLevel @@ -1563,3 +1571,63 @@ class FileViewSet( request = utils.generate_s3_authorization_headers(f"{url_params.get('key'):s}") return drf_response.Response("authorized", headers=request.headers, status=200) + + +class DiagnosticsViewSet(viewsets.ViewSet): + """Endpoints helping users and support diagnose connectivity issues. + + Diagnostics are grouped behind a single prefix so upcoming checks + (rtcstats collection, ICE candidate reports, etc.) can be added as new + actions rather than new top-level routes. + + They are open to anonymous users: someone who cannot join a room is + exactly who needs to run a test, and they may well not be logged in. + Each action therefore carries its own throttle scope. + """ + + permission_classes = [drf_permissions.AllowAny] + + @decorators.action( + detail=False, + methods=["POST"], + url_path="connection", + url_name="connection", + throttle_classes=[ + throttling.ConnectionTestUserRateThrottle, + throttling.ConnectionTestAnonRateThrottle, + ], + ) + def connection(self, request): + """Return a short-lived LiveKit token for an ephemeral test room. + + Going through the room API is not an option here: it is tied to + registered meetings, lobby rules and longer-lived tokens. Each call + gets its own room so two people testing at the same time never meet. + """ + room = f"{settings.CONNECTION_TEST_ROOM_PREFIX}-{uuid4()}" + expires_in = settings.CONNECTION_TEST_TOKEN_TTL_SECONDS + + # LiveKit refreshes tokens for connected clients, so JWT TTL alone does not + # eject someone who stays connected. Schedule a hard DeleteRoom when Celery + # is available. + if settings.CELERY_ENABLED: + delete_connection_test_room.apply_async( + args=[room], + countdown=settings.CONNECTION_TEST_ROOM_MAX_AGE_SECONDS, + ) + + return drf_response.Response( + { + "livekit": { + "url": settings.LIVEKIT_CONFIGURATION["url"], + "room": room, + "token": generate_token( + room=room, + user=request.user, + username="Connection Test", + ttl=timedelta(seconds=expires_in), + ), + "expires_in": expires_in, + }, + } + ) diff --git a/src/backend/core/services/livekit_events.py b/src/backend/core/services/livekit_events.py index 769268f3..44835582 100644 --- a/src/backend/core/services/livekit_events.py +++ b/src/backend/core/services/livekit_events.py @@ -137,6 +137,13 @@ class LiveKitEventsService: room_name = data.room.name or data.egress_info.room_name + if self._is_connection_test_room(room_name): + logger.info( + "Ignoring webhook event for connection test room '%s'.", + room_name, + ) + return + if self._filter_regex and not self._filter_regex.search(room_name): logger.info("Filtered webhook event for room '%s'", room_name) return @@ -236,13 +243,6 @@ class LiveKitEventsService: def _handle_room_started(self, data): """Handle 'room_started' event.""" - if self._is_connection_test_room(data.room.name): - logger.info( - "Ignoring room_started event for connection test room '%s'.", - data.room.name, - ) - return - try: room_id = uuid.UUID(data.room.name) except ValueError as e: @@ -268,13 +268,6 @@ class LiveKitEventsService: def _handle_room_finished(self, data): """Handle 'room_finished' event.""" - if self._is_connection_test_room(data.room.name): - logger.info( - "Ignoring room_finished event for connection test room '%s'.", - data.room.name, - ) - return - try: room_id = uuid.UUID(data.room.name) except ValueError as e: diff --git a/src/backend/core/services/room_management.py b/src/backend/core/services/room_management.py index 109d7f9d..163af6e2 100644 --- a/src/backend/core/services/room_management.py +++ b/src/backend/core/services/room_management.py @@ -8,6 +8,7 @@ from typing import Dict, Optional from asgiref.sync import async_to_sync from livekit.api import ( + DeleteRoomRequest, ListRoomsRequest, TwirpError, UpdateRoomMetadataRequest, @@ -88,3 +89,30 @@ class RoomManagement: finally: await lkapi.aclose() + + @async_to_sync + async def delete_room(self, room_name: str): + """Delete a LiveKit room and disconnect all participants. + + Raises: + RoomNotFoundException: the room does not exist in LiveKit. + RoomManagementException: the deletion otherwise fails. + """ + + lkapi = utils.create_livekit_client() + + try: + await lkapi.room.delete_room(DeleteRoomRequest(room=room_name)) + logger.info("Deleted LiveKit room %s", room_name) + except TwirpError as e: + if e.code == "not_found": + logger.warning( + "Room %s not found in LiveKit, skipping deletion", + room_name, + ) + raise RoomNotFoundException("Room does not exist") from e + + logger.exception("Unexpected error deleting room %s", room_name) + raise RoomManagementException("Could not delete room") from e + finally: + await lkapi.aclose() diff --git a/src/backend/core/tasks/connection_test.py b/src/backend/core/tasks/connection_test.py index e6cb33ea..ba8872d6 100644 --- a/src/backend/core/tasks/connection_test.py +++ b/src/backend/core/tasks/connection_test.py @@ -4,14 +4,12 @@ import logging from django.conf import settings -from asgiref.sync import async_to_sync -from livekit.api import ( # pylint: disable=no-name-in-module - DeleteRoomRequest, - TwirpError, +from core.services.room_management import ( + RoomManagement, + RoomManagementException, + RoomNotFoundException, ) - from core.tasks._task import task -from core.utils import create_livekit_client logger = logging.getLogger(__name__) @@ -32,20 +30,10 @@ def delete_connection_test_room(room_name: str): ) return - async_to_sync(_delete_room)(room_name) - - -async def _delete_room(room_name: str): - lkapi = create_livekit_client() try: - await lkapi.room.delete_room(DeleteRoomRequest(room=room_name)) - logger.info("Deleted connection test room '%s'.", room_name) - except TwirpError as exc: + RoomManagement().delete_room(room_name) + except RoomNotFoundException: # Room may already be gone after empty/departure timeout. - logger.info( - "Could not delete connection test room '%s': %s", - room_name, - exc, - ) - finally: - await lkapi.aclose() + logger.info("Connection test room '%s' already gone.", room_name) + except RoomManagementException: + logger.exception("Failed to delete connection test room '%s'.", room_name) diff --git a/src/backend/core/tests/services/test_livekit_events.py b/src/backend/core/tests/services/test_livekit_events.py index b01d74c5..c9b30f86 100644 --- a/src/backend/core/tests/services/test_livekit_events.py +++ b/src/backend/core/tests/services/test_livekit_events.py @@ -6,8 +6,6 @@ Test LiveKitEvents service. import uuid from unittest import mock -from django.test.utils import override_settings - import pytest from livekit.api import EgressStatus @@ -577,23 +575,6 @@ def test_handle_room_finished_raises_error_when_telephony_deletion_fails( mock_clear_cache.assert_not_called() -@override_settings(CONNECTION_TEST_ROOM_PREFIX="connection-test") -@mock.patch.object(LobbyService, "clear_room_cache") -@mock.patch.object(SIPManagement, "delete_dispatch_rule") -def test_handle_room_finished_ignores_connection_test_room( - mock_delete_dispatch_rule, mock_clear_cache, service, settings -): - """Should ignore room_finished events for connection test rooms.""" - settings.ROOM_TELEPHONY_ENABLED = True - mock_data = mock.MagicMock() - mock_data.room.name = f"{settings.CONNECTION_TEST_ROOM_PREFIX}{uuid.uuid4()}" - - service._handle_room_finished(mock_data) - - mock_delete_dispatch_rule.assert_not_called() - mock_clear_cache.assert_not_called() - - def test_handle_room_finished_raises_error_for_invalid_room_name(service): """Should raise ActionFailedError when room name format is invalid when room finishes.""" mock_data = mock.MagicMock() @@ -697,15 +678,6 @@ def test_handle_room_started_raises_error_for_invalid_room_name(service): service._handle_room_started(mock_data) -@override_settings(CONNECTION_TEST_ROOM_PREFIX="connection-test-") -def test_handle_room_started_ignores_connection_test_room(service, settings): - """Should ignore room_started events for connection test rooms.""" - mock_data = mock.MagicMock() - mock_data.room.name = f"{settings.CONNECTION_TEST_ROOM_PREFIX}{uuid.uuid4()}" - - service._handle_room_started(mock_data) - - def test_handle_room_started_raises_error_for_nonexistent_room(service): """Should raise ActionFailedError when a room starts that doesn't exist in the database.""" mock_data = mock.MagicMock() @@ -748,6 +720,7 @@ def test_receive_unsupported_event(mock_receive, service): # Mock returned data with unsupported event type mock_data = mock.MagicMock() + mock_data.room.name = str(uuid.uuid4()) mock_data.event = "unsupported_event" mock_receive.return_value = mock_data @@ -851,3 +824,33 @@ def test_receive_filter_processes_matching_events( service.receive(mock_request) mock_handle_room_started.assert_called_once() + + +@mock.patch.object(api.WebhookReceiver, "receive") +@mock.patch.object(LiveKitEventsService, "_handle_room_finished") +@mock.patch.object(LiveKitEventsService, "_handle_room_started") +def test_receive_ignores_connection_test_room( + mock_handle_room_started, + mock_handle_room_finished, + mock_receive, + mock_livekit_config, + settings, +): + """Should ignore all webhook events for connection test rooms in receive().""" + + settings.CONNECTION_TEST_ROOM_PREFIX = "connection-test" + + mock_request = mock.MagicMock() + mock_request.headers = {"Authorization": "test_token"} + mock_request.body = b"{}" + + mock_data = mock.MagicMock() + mock_data.room.name = f"{settings.CONNECTION_TEST_ROOM_PREFIX}-{uuid.uuid4()}" + mock_data.event = "room_started" + mock_receive.return_value = mock_data + + service = LiveKitEventsService() + service.receive(mock_request) + + mock_handle_room_started.assert_not_called() + mock_handle_room_finished.assert_not_called() diff --git a/src/backend/core/tests/services/test_room_management.py b/src/backend/core/tests/services/test_room_management.py new file mode 100644 index 00000000..a63e5aa4 --- /dev/null +++ b/src/backend/core/tests/services/test_room_management.py @@ -0,0 +1,60 @@ +"""Tests for the RoomManagement service.""" + +from unittest import mock + +import pytest +from livekit.api import TwirpError + +from core.services.room_management import ( + RoomManagement, + RoomManagementException, + RoomNotFoundException, +) + + +@mock.patch("core.services.room_management.utils.create_livekit_client") +def test_delete_room_calls_livekit(mock_create_livekit_client): + """DeleteRoom is forwarded to the LiveKit API.""" + mock_api = mock.MagicMock() + mock_api.room.delete_room = mock.AsyncMock() + mock_api.aclose = mock.AsyncMock() + mock_create_livekit_client.return_value = mock_api + + RoomManagement().delete_room("room-abc") + + mock_api.room.delete_room.assert_awaited_once() + request = mock_api.room.delete_room.await_args.args[0] + assert request.room == "room-abc" + mock_api.aclose.assert_awaited_once() + + +@mock.patch("core.services.room_management.utils.create_livekit_client") +def test_delete_room_raises_not_found(mock_create_livekit_client): + """Missing rooms raise RoomNotFoundException.""" + mock_api = mock.MagicMock() + mock_api.room.delete_room = mock.AsyncMock( + side_effect=TwirpError("not_found", "room not found", status=404) + ) + mock_api.aclose = mock.AsyncMock() + mock_create_livekit_client.return_value = mock_api + + with pytest.raises(RoomNotFoundException): + RoomManagement().delete_room("missing-room") + + mock_api.aclose.assert_awaited_once() + + +@mock.patch("core.services.room_management.utils.create_livekit_client") +def test_delete_room_raises_management_exception(mock_create_livekit_client): + """Unexpected Twirp errors raise RoomManagementException.""" + mock_api = mock.MagicMock() + mock_api.room.delete_room = mock.AsyncMock( + side_effect=TwirpError("internal", "boom", status=500) + ) + mock_api.aclose = mock.AsyncMock() + mock_create_livekit_client.return_value = mock_api + + with pytest.raises(RoomManagementException): + RoomManagement().delete_room("room-abc") + + mock_api.aclose.assert_awaited_once() diff --git a/src/backend/core/tests/tasks/test_connection_test.py b/src/backend/core/tests/tasks/test_connection_test.py index 58811d88..1626ebd0 100644 --- a/src/backend/core/tests/tasks/test_connection_test.py +++ b/src/backend/core/tests/tasks/test_connection_test.py @@ -4,48 +4,48 @@ from unittest import mock from django.test.utils import override_settings -from livekit.api import TwirpError - +from core.services.room_management import ( + RoomManagementException, + RoomNotFoundException, +) from core.tasks.connection_test import delete_connection_test_room -@override_settings(CONNECTION_TEST_ROOM_PREFIX="connection-test") -@mock.patch("core.tasks.connection_test.create_livekit_client") -def test_delete_connection_test_room_calls_livekit(mock_create_livekit_client): - """DeleteRoom is called for rooms with the connection-test prefix.""" - mock_api = mock.MagicMock() - mock_api.room.delete_room = mock.AsyncMock() - mock_api.aclose = mock.AsyncMock() - mock_create_livekit_client.return_value = mock_api - +@mock.patch("core.tasks.connection_test.RoomManagement.delete_room") +def test_delete_connection_test_room_calls_room_management(mock_delete_room, settings): + """RoomManagement.delete_room is called for connection-test rooms.""" + settings.CONNECTION_TEST_ROOM_PREFIX = "connection-test" delete_connection_test_room("connection-test-abc") - mock_api.room.delete_room.assert_awaited_once() - request = mock_api.room.delete_room.await_args.args[0] - assert request.room == "connection-test-abc" - mock_api.aclose.assert_awaited_once() + mock_delete_room.assert_called_once_with("connection-test-abc") -@override_settings(CONNECTION_TEST_ROOM_PREFIX="connection-test") -@mock.patch("core.tasks.connection_test.create_livekit_client") -def test_delete_connection_test_room_refuses_other_rooms(mock_create_livekit_client): +@mock.patch("core.tasks.connection_test.RoomManagement.delete_room") +def test_delete_connection_test_room_refuses_other_rooms(mock_delete_room, settings): """Refuse to delete rooms outside the connection-test namespace.""" + settings.CONNECTION_TEST_ROOM_PREFIX = "connection-test" delete_connection_test_room("production-room") - mock_create_livekit_client.assert_not_called() + mock_delete_room.assert_not_called() -@override_settings(CONNECTION_TEST_ROOM_PREFIX="connection-test") -@mock.patch("core.tasks.connection_test.create_livekit_client") -def test_delete_connection_test_room_ignores_missing_room(mock_create_livekit_client): +@mock.patch("core.tasks.connection_test.RoomManagement.delete_room") +def test_delete_connection_test_room_ignores_missing_room(mock_delete_room, settings): """Missing rooms are treated as already cleaned up.""" - mock_api = mock.MagicMock() - mock_api.room.delete_room = mock.AsyncMock( - side_effect=TwirpError("not_found", "room not found", status=404) - ) - mock_api.aclose = mock.AsyncMock() - mock_create_livekit_client.return_value = mock_api + settings.CONNECTION_TEST_ROOM_PREFIX = "connection-test" + mock_delete_room.side_effect = RoomNotFoundException("Room does not exist") delete_connection_test_room("connection-test-gone") - mock_api.aclose.assert_awaited_once() + mock_delete_room.assert_called_once_with("connection-test-gone") + + +@mock.patch("core.tasks.connection_test.RoomManagement.delete_room") +def test_delete_connection_test_room_logs_other_failures(mock_delete_room, settings): + """Unexpected LiveKit failures are swallowed after logging.""" + settings.CONNECTION_TEST_ROOM_PREFIX = "connection-test" + mock_delete_room.side_effect = RoomManagementException("Could not delete room") + + delete_connection_test_room("connection-test-fail") + + mock_delete_room.assert_called_once_with("connection-test-fail") diff --git a/src/backend/core/tests/test_api_diagnostics.py b/src/backend/core/tests/test_api_diagnostics.py index aa529cc6..49fa8a84 100644 --- a/src/backend/core/tests/test_api_diagnostics.py +++ b/src/backend/core/tests/test_api_diagnostics.py @@ -10,7 +10,6 @@ import jwt import pytest from rest_framework.test import APIClient -from core.api.diagnostics import CONNECTION_TEST_USERNAME from core.api.throttling import ( ConnectionTestAnonRateThrottle, ConnectionTestUserRateThrottle, @@ -19,18 +18,16 @@ from core.factories import UserFactory pytestmark = pytest.mark.django_db -CONNECTION_URL = "/api/v1.0/diagnostics/connection/" - def test_api_diagnostics_connection_url(): """The connection check is exposed under the diagnostics namespace.""" - assert reverse("diagnostics-connection") == CONNECTION_URL + assert reverse("diagnostics-connection") == "/api/v1.0/diagnostics/connection/" -def test_api_diagnostics_connection_rejects_post(): - """Only GET is exposed, the endpoint has no side effect to trigger.""" +def test_api_diagnostics_connection_rejects_get(): + """Only POST is exposed, the endpoint has no side effect to trigger.""" client = APIClient() - response = client.post(CONNECTION_URL) + response = client.get("/api/v1.0/diagnostics/connection/") assert response.status_code == 405 @@ -41,8 +38,8 @@ def test_api_diagnostics_connection_returns_ephemeral_livekit_config(settings, c settings.CONNECTION_TEST_TOKEN_TTL_SECONDS = 600 settings.CONNECTION_TEST_ROOM_PREFIX = "connection-test" - response_a = client.get(CONNECTION_URL) - response_b = client.get(CONNECTION_URL) + response_a = client.post("/api/v1.0/diagnostics/connection/") + response_b = client.post("/api/v1.0/diagnostics/connection/") assert response_a.status_code == 200 assert response_b.status_code == 200 @@ -70,7 +67,7 @@ def test_api_diagnostics_connection_token_is_short_lived_for_user(settings, clie settings.CONNECTION_TEST_TOKEN_TTL_SECONDS = 300 client = APIClient() - response = client.get(CONNECTION_URL) + response = client.post("/api/v1.0/diagnostics/connection/") assert response.status_code == 200 @@ -84,7 +81,7 @@ def test_api_diagnostics_connection_token_is_short_lived_for_user(settings, clie assert config["expires_in"] == 300 assert payload["video"]["room"] == config["room"] - assert payload["name"] == CONNECTION_TEST_USERNAME + assert payload["name"] == "Connection Test" assert payload["video"]["roomAdmin"] is False assert payload["exp"] - payload["nbf"] == 300 @@ -98,7 +95,7 @@ def test_api_diagnostics_connection_token_for_authenticated_user(settings, clien user = UserFactory() client.force_login(user) - response = client.get(CONNECTION_URL) + response = client.post("/api/v1.0/diagnostics/connection/") assert response.status_code == 200 @@ -114,7 +111,7 @@ def test_api_diagnostics_connection_token_for_authenticated_user(settings, clien assert payload["exp"] - payload["nbf"] == 300 -@mock.patch("core.api.diagnostics.delete_connection_test_room.apply_async") +@mock.patch("core.api.viewsets.delete_connection_test_room.apply_async") def test_api_diagnostics_connection_schedules_room_deletion( mock_apply_async, settings, client ): @@ -124,21 +121,21 @@ def test_api_diagnostics_connection_schedules_room_deletion( settings.CONNECTION_TEST_ROOM_MAX_AGE_SECONDS = 300 settings.CONNECTION_TEST_ROOM_PREFIX = "connection-test" - response = client.get(CONNECTION_URL) + response = client.post("/api/v1.0/diagnostics/connection/") assert response.status_code == 200 room = response.json()["livekit"]["room"] mock_apply_async.assert_called_once_with(args=[room], countdown=300) -@mock.patch("core.api.diagnostics.delete_connection_test_room.apply_async") +@mock.patch("core.api.viewsets.delete_connection_test_room.apply_async") def test_api_diagnostics_connection_skips_room_deletion_without_celery( mock_apply_async, settings, client ): """Without Celery, do not schedule deletion (apply_async would run immediately).""" settings.CELERY_ENABLED = False - response = client.get(CONNECTION_URL) + response = client.post("/api/v1.0/diagnostics/connection/") assert response.status_code == 200 mock_apply_async.assert_not_called() @@ -154,6 +151,6 @@ def test_api_diagnostics_connection_is_throttled(throttle_class, client): mock.patch.object(throttle_class, "allow_request", return_value=False), mock.patch.object(throttle_class, "wait", return_value=42), ): - response = client.get(CONNECTION_URL) + response = client.post("/api/v1.0/diagnostics/connection/") assert response.status_code == 429 diff --git a/src/backend/core/urls.py b/src/backend/core/urls.py index ba0c492d..6209216f 100644 --- a/src/backend/core/urls.py +++ b/src/backend/core/urls.py @@ -7,7 +7,7 @@ from lasuite.oidc_login.urls import urlpatterns as oidc_urls from rest_framework.routers import DefaultRouter, SimpleRouter from core.addons import viewsets as addons_viewsets -from core.api import diagnostics, get_frontend_configuration, viewsets +from core.api import get_frontend_configuration, viewsets from core.external_api import viewsets as external_viewsets from core.roomkit import viewsets as roomkit_viewsets @@ -32,7 +32,7 @@ router.register( ) router.register( "diagnostics", - diagnostics.DiagnosticsViewSet, + viewsets.DiagnosticsViewSet, basename="diagnostics", )