mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-05 16:37:43 +00:00
fixup! ✨(backend) add connection-test API
This commit is contained in:
@@ -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,
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user