From dc06b55693ce8002074b07208c15569e8adfe744 Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Fri, 11 Apr 2025 16:24:54 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(backend)=20enable=20retrieve=20viewse?= =?UTF-8?q?t=20on=20recording=20model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Django built-in mixins to recording viewset to support individual record retrieval. Enables frontend to access single recording details needed for the upcoming download page implementation. --- src/backend/core/api/viewsets.py | 1 + .../recording/test_api_recordings_list.py | 21 +++ .../recording/test_api_recordings_retrieve.py | 153 ++++++++++++++++++ 3 files changed, 175 insertions(+) create mode 100644 src/backend/core/tests/recording/test_api_recordings_retrieve.py diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index d212bad6..61c384ef 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -544,6 +544,7 @@ class ResourceAccessViewSet( class RecordingViewSet( mixins.DestroyModelMixin, mixins.ListModelMixin, + mixins.RetrieveModelMixin, viewsets.GenericViewSet, ): """ diff --git a/src/backend/core/tests/recording/test_api_recordings_list.py b/src/backend/core/tests/recording/test_api_recordings_list.py index 0ee09b48..e22fa360 100644 --- a/src/backend/core/tests/recording/test_api_recordings_list.py +++ b/src/backend/core/tests/recording/test_api_recordings_list.py @@ -22,6 +22,27 @@ def test_api_recordings_list_anonymous(): assert response.status_code == 401 +def test_api_recordings_list_authenticated_no_recording(): + """ + Authenticated users listing recordings should only + see recordings to which they have access. + """ + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + other_user = factories.UserFactory() + factories.UserRecordingAccessFactory(user=other_user) + + response = client.get( + "/api/v1.0/recordings/", + ) + + assert response.status_code == 200 + results = response.json()["results"] + assert results == [] + + @pytest.mark.parametrize( "role", ["administrator", "member", "owner"], diff --git a/src/backend/core/tests/recording/test_api_recordings_retrieve.py b/src/backend/core/tests/recording/test_api_recordings_retrieve.py new file mode 100644 index 00000000..dca425f2 --- /dev/null +++ b/src/backend/core/tests/recording/test_api_recordings_retrieve.py @@ -0,0 +1,153 @@ +""" +Test recordings API endpoints in the Meet core app: retrieve. +""" + +import pytest +from rest_framework.test import APIClient + +from ...factories import RecordingFactory, UserFactory, UserRecordingAccessFactory +from ...models import RecordingStatusChoices + +pytestmark = pytest.mark.django_db + + +def test_api_recording_retrieve_anonymous(): + """Anonymous users should not be able to retrieve recordings.""" + recording = RecordingFactory() + client = APIClient() + response = client.get(f"/api/v1.0/recordings/{recording.id!s}/") + + assert response.status_code == 401 + assert response.json() == { + "detail": "Authentication credentials were not provided." + } + + +def test_api_recording_retrieve_authenticated(): + """Authenticated users without access receive 404 when requesting recordings. + + The API returns 404 instead of 403 to avoid revealing the existence of + resources the user doesn't have permission to access. + """ + user = UserFactory() + + client = APIClient() + client.force_login(user) + + other_user = UserFactory() + recording = UserRecordingAccessFactory(user=other_user).recording + + response = client.get(f"/api/v1.0/recordings/{recording.id!s}/") + assert response.status_code == 404 + assert response.json() == {"detail": "No Recording matches the given query."} + + +def test_api_recording_retrieve_members(): + """ + A user who is a member of a recording should not be able to retrieve it. + """ + user = UserFactory() + recording = RecordingFactory() + + UserRecordingAccessFactory(recording=recording, user=user, role="member") + + client = APIClient() + client.force_login(user) + + response = client.get(f"/api/v1.0/recordings/{recording.id!s}/") + assert response.status_code == 403 + assert response.json() == { + "detail": "You do not have permission to perform this action." + } + + +def test_api_recording_retrieve_administrators(): + """A user who is an administrator of a recording should be able to retrieve it.""" + + user = UserFactory() + recording = RecordingFactory() + + UserRecordingAccessFactory(recording=recording, user=user, role="administrator") + + client = APIClient() + client.force_login(user) + + response = client.get(f"/api/v1.0/recordings/{recording.id!s}/") + + assert response.status_code == 200 + content = response.json() + room = recording.room + + assert content == { + "id": str(recording.id), + "room": { + "access_level": str(room.access_level), + "id": str(room.id), + "name": room.name, + "slug": room.slug, + }, + "created_at": recording.created_at.isoformat().replace("+00:00", "Z"), + "updated_at": recording.updated_at.isoformat().replace("+00:00", "Z"), + "status": str(recording.status), + "mode": str(recording.mode), + } + + +def test_api_recording_retrieve_owners(): + """A user who is an owner of a recording should be able to retrieve it.""" + user = UserFactory() + recording = RecordingFactory() + + UserRecordingAccessFactory(recording=recording, user=user, role="owner") + + client = APIClient() + client.force_login(user) + + response = client.get(f"/api/v1.0/recordings/{recording.id!s}/") + + assert response.status_code == 200 + content = response.json() + room = recording.room + + assert content == { + "id": str(recording.id), + "room": { + "access_level": str(room.access_level), + "id": str(room.id), + "name": room.name, + "slug": room.slug, + }, + "created_at": recording.created_at.isoformat().replace("+00:00", "Z"), + "updated_at": recording.updated_at.isoformat().replace("+00:00", "Z"), + "status": str(recording.status), + "mode": str(recording.mode), + } + + +@pytest.mark.parametrize( + "status", + [ + RecordingStatusChoices.INITIATED, + RecordingStatusChoices.ACTIVE, + RecordingStatusChoices.SAVED, + RecordingStatusChoices.FAILED_TO_START, + RecordingStatusChoices.FAILED_TO_STOP, + RecordingStatusChoices.ABORTED, + ], +) +def test_api_recording_retrieve_any_status(status): + """Test that recordings with any status can be retrieved.""" + user = UserFactory() + recording = RecordingFactory(status=status) + + UserRecordingAccessFactory(recording=recording, user=user, role="owner") + + client = APIClient() + client.force_login(user) + + response = client.get(f"/api/v1.0/recordings/{recording.id!s}/") + + assert response.status_code == 200 + content = response.json() + assert content["id"] == str(recording.id) + assert content["status"] == status