From 0b301b95c8541447d2313f135993cdbdc5980cf6 Mon Sep 17 00:00:00 2001 From: Samuel Paccoud - DINUM Date: Sun, 13 Jul 2025 19:56:07 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(backend)=20allow=20masking=20document?= =?UTF-8?q?s=20from=20the=20list=20view?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Once users have visited a document to which they have access, they can't remove it from their list view anymore. Several users reported that this is annoying because a document that gets a lot of updates keeps popping up at the top of their list view. They want to be able to mask the document in a click. We propose to add a "masked documents" section in the left side bar where the masked documents can still be found. --- CHANGELOG.md | 4 +- src/backend/core/api/filters.py | 22 ++ src/backend/core/api/viewsets.py | 48 ++- src/backend/core/factories.py | 11 +- .../0024_add_is_masked_field_to_link_trace.py | 36 ++ src/backend/core/models.py | 2 + .../test_api_documents_favorite_list.py | 4 +- .../test_api_documents_list_filters.py | 78 ++++ .../documents/test_api_documents_mask.py | 353 ++++++++++++++++++ .../documents/test_api_documents_retrieve.py | 5 + .../documents/test_api_documents_trashbin.py | 1 + .../core/tests/test_models_documents.py | 8 + 12 files changed, 560 insertions(+), 12 deletions(-) create mode 100644 src/backend/core/migrations/0024_add_is_masked_field_to_link_trace.py create mode 100644 src/backend/core/tests/documents/test_api_documents_mask.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 66e13b4a..2d7fec8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to ### Added +- ✨(backend) allow masking documents from the list view #1171 - ✨(frontend) add duplicate action to doc tree #1175 ### Changed @@ -33,7 +34,6 @@ and this project adheres to - 🐛(backend) improve prompt to not use code blocks delimiter #1188 - ## [3.4.1] - 2025-07-15 ### Fixed @@ -58,7 +58,7 @@ and this project adheres to - ✨(backend) add ancestors links reach and role to document API #846 - 📝(project) add troubleshoot doc #1066 - 📝(project) add system-requirement doc #1066 -- 🔧(front) configure x-frame-options to DENY in nginx conf #1084 +- 🔧(frontend) configure x-frame-options to DENY in nginx conf #1084 - ✨(backend) allow to disable checking unsafe mimetype on attachment upload #1099 - ✨(doc) add documentation to install with compose #855 diff --git a/src/backend/core/api/filters.py b/src/backend/core/api/filters.py index 5b0721fb..38f465a3 100644 --- a/src/backend/core/api/filters.py +++ b/src/backend/core/api/filters.py @@ -60,6 +60,9 @@ class ListDocumentFilter(DocumentFilter): is_creator_me = django_filters.BooleanFilter( method="filter_is_creator_me", label=_("Creator is me") ) + is_masked = django_filters.BooleanFilter( + method="filter_is_masked", label=_("Masked") + ) is_favorite = django_filters.BooleanFilter( method="filter_is_favorite", label=_("Favorite") ) @@ -106,3 +109,22 @@ class ListDocumentFilter(DocumentFilter): return queryset return queryset.filter(is_favorite=bool(value)) + + # pylint: disable=unused-argument + def filter_is_masked(self, queryset, name, value): + """ + Filter documents based on whether they are masked by the current user. + + Example: + - /api/v1.0/documents/?is_masked=true + → Filters documents marked as masked by the logged-in user + - /api/v1.0/documents/?is_masked=false + → Filters documents not marked as masked by the logged-in user + """ + user = self.request.user + + if not user.is_authenticated: + return queryset + + queryset_method = queryset.filter if bool(value) else queryset.exclude + return queryset_method(link_traces__user=user, link_traces__is_masked=True) diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index 7919033e..ee0c594e 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -455,9 +455,8 @@ class DocumentViewSet( # Annotate favorite status and filter if applicable as late as possible queryset = queryset.annotate_is_favorite(user) - queryset = filterset.filters["is_favorite"].filter( - queryset, filter_data["is_favorite"] - ) + for field in ["is_favorite", "is_masked"]: + queryset = filterset.filters[field].filter(queryset, filter_data[field]) # Apply ordering only now that everything is filtered and annotated queryset = filters.OrderingFilter().filter_queryset( @@ -1109,15 +1108,50 @@ class DocumentViewSet( document=document, user=user ).delete() if deleted: - return drf.response.Response( - {"detail": "Document unmarked as favorite"}, - status=drf.status.HTTP_204_NO_CONTENT, - ) + return drf.response.Response(status=drf.status.HTTP_204_NO_CONTENT) return drf.response.Response( {"detail": "Document was already not marked as favorite"}, status=drf.status.HTTP_200_OK, ) + @drf.decorators.action(detail=True, methods=["post", "delete"], url_path="mask") + def mask(self, request, *args, **kwargs): + """Mask or unmask the document for the logged-in user based on the HTTP method.""" + # Check permissions first + document = self.get_object() + user = request.user + + try: + link_trace = models.LinkTrace.objects.get(document=document, user=user) + except models.LinkTrace.DoesNotExist: + return drf.response.Response( + {"detail": "User never accessed this document before."}, + status=status.HTTP_400_BAD_REQUEST, + ) + + if request.method == "POST": + if link_trace.is_masked: + return drf.response.Response( + {"detail": "Document was already masked"}, + status=drf.status.HTTP_200_OK, + ) + link_trace.is_masked = True + link_trace.save(update_fields=["is_masked"]) + return drf.response.Response( + {"detail": "Document was masked"}, + status=drf.status.HTTP_201_CREATED, + ) + + # Handle DELETE method to unmask document + if not link_trace.is_masked: + return drf.response.Response( + {"detail": "Document was already not masked"}, + status=drf.status.HTTP_200_OK, + ) + link_trace.is_masked = False + link_trace.save(update_fields=["is_masked"]) + return drf.response.Response(status=drf.status.HTTP_204_NO_CONTENT) + @drf.decorators.action(detail=True, methods=["post"], url_path="attachment-upload") def attachment_upload(self, request, *args, **kwargs): """Upload a file related to a given document""" diff --git a/src/backend/core/factories.py b/src/backend/core/factories.py index 9d08c51c..1b3715e7 100644 --- a/src/backend/core/factories.py +++ b/src/backend/core/factories.py @@ -150,7 +150,7 @@ class DocumentFactory(factory.django.DjangoModelFactory): """Add link traces to document from a given list of users.""" if create and extracted: for item in extracted: - models.LinkTrace.objects.create(document=self, user=item) + models.LinkTrace.objects.update_or_create(document=self, user=item) @factory.post_generation def favorited_by(self, create, extracted, **kwargs): @@ -159,6 +159,15 @@ class DocumentFactory(factory.django.DjangoModelFactory): for item in extracted: models.DocumentFavorite.objects.create(document=self, user=item) + @factory.post_generation + def masked_by(self, create, extracted, **kwargs): + """Mark document as masked by a list of users.""" + if create and extracted: + for item in extracted: + models.LinkTrace.objects.update_or_create( + document=self, user=item, defaults={"is_masked": True} + ) + class UserDocumentAccessFactory(factory.django.DjangoModelFactory): """Create fake document user accesses for testing.""" diff --git a/src/backend/core/migrations/0024_add_is_masked_field_to_link_trace.py b/src/backend/core/migrations/0024_add_is_masked_field_to_link_trace.py new file mode 100644 index 00000000..c8a68b29 --- /dev/null +++ b/src/backend/core/migrations/0024_add_is_masked_field_to_link_trace.py @@ -0,0 +1,36 @@ +# Generated by Django 5.2.3 on 2025-07-13 08:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0023_remove_document_is_public_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="linktrace", + name="is_masked", + field=models.BooleanField(default=False), + ), + migrations.AlterField( + model_name="user", + name="language", + field=models.CharField( + blank=True, + choices=[ + ("en-us", "English"), + ("fr-fr", "Français"), + ("de-de", "Deutsch"), + ("nl-nl", "Nederlands"), + ("es-es", "Español"), + ], + default=None, + help_text="The language in which the user wants to see the interface.", + max_length=10, + null=True, + verbose_name="language", + ), + ), + ] diff --git a/src/backend/core/models.py b/src/backend/core/models.py index d6e79c51..a1182964 100644 --- a/src/backend/core/models.py +++ b/src/backend/core/models.py @@ -793,6 +793,7 @@ class Document(MP_Node, BaseModel): "favorite": can_get and user.is_authenticated, "link_configuration": is_owner_or_admin, "invite_owner": is_owner, + "mask": can_get and user.is_authenticated, "move": is_owner_or_admin and not self.ancestors_deleted_at, "partial_update": can_update, "restore": is_owner, @@ -958,6 +959,7 @@ class LinkTrace(BaseModel): related_name="link_traces", ) user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="link_traces") + is_masked = models.BooleanField(default=False) class Meta: db_table = "impress_link_trace" diff --git a/src/backend/core/tests/documents/test_api_documents_favorite_list.py b/src/backend/core/tests/documents/test_api_documents_favorite_list.py index 544ff1b3..3ac9170a 100644 --- a/src/backend/core/tests/documents/test_api_documents_favorite_list.py +++ b/src/backend/core/tests/documents/test_api_documents_favorite_list.py @@ -41,8 +41,8 @@ def test_api_document_favorite_list_authenticated_with_favorite(): client = APIClient() client.force_login(user) - # User don't have access to this document (e.g the user had access and this - # access was removed. It should not be in the favorite list anymore. + # If the user doesn't have access to this document (e.g the user had access + # and this access was removed), it should not be in the favorite list anymore. factories.DocumentFactory(favorited_by=[user]) document = factories.UserDocumentAccessFactory( diff --git a/src/backend/core/tests/documents/test_api_documents_list_filters.py b/src/backend/core/tests/documents/test_api_documents_list_filters.py index f93ad6d5..5baa7e30 100644 --- a/src/backend/core/tests/documents/test_api_documents_list_filters.py +++ b/src/backend/core/tests/documents/test_api_documents_list_filters.py @@ -312,6 +312,84 @@ def test_api_documents_list_filter_is_favorite_invalid(): assert len(results) == 5 +# Filters: is_masked + + +def test_api_documents_list_filter_is_masked_true(): + """ + Authenticated users should be able to filter documents they marked as masked. + """ + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + factories.DocumentFactory.create_batch(2, users=[user]) + masked_documents = factories.DocumentFactory.create_batch( + 3, users=[user], masked_by=[user] + ) + unmasked_documents = factories.DocumentFactory.create_batch(2, users=[user]) + for document in unmasked_documents: + models.LinkTrace.objects.create(document=document, user=user, is_masked=False) + + response = client.get("/api/v1.0/documents/?is_masked=true") + + assert response.status_code == 200 + results = response.json()["results"] + assert len(results) == 3 + + # Ensure all results are marked as masked by the current user + masked_documents_ids = [str(doc.id) for doc in masked_documents] + for result in results: + assert result["id"] in masked_documents_ids + + +def test_api_documents_list_filter_is_masked_false(): + """ + Authenticated users should be able to filter documents they didn't mark as masked. + """ + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + factories.DocumentFactory.create_batch(2, users=[user]) + masked_documents = factories.DocumentFactory.create_batch( + 3, users=[user], masked_by=[user] + ) + unmasked_documents = factories.DocumentFactory.create_batch(2, users=[user]) + for document in unmasked_documents: + models.LinkTrace.objects.create(document=document, user=user, is_masked=False) + + response = client.get("/api/v1.0/documents/?is_masked=false") + + assert response.status_code == 200 + results = response.json()["results"] + assert len(results) == 4 + + # Ensure all results are not marked as masked by the current user + masked_documents_ids = [str(doc.id) for doc in masked_documents] + for result in results: + assert result["id"] not in masked_documents_ids + + +def test_api_documents_list_filter_is_masked_invalid(): + """Filtering with an invalid `is_masked` value should do nothing.""" + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + factories.DocumentFactory.create_batch(2, users=[user]) + factories.DocumentFactory.create_batch(3, users=[user], masked_by=[user]) + unmasked_documents = factories.DocumentFactory.create_batch(2, users=[user]) + for document in unmasked_documents: + models.LinkTrace.objects.create(document=document, user=user, is_masked=False) + + response = client.get("/api/v1.0/documents/?is_masked=invalid") + + assert response.status_code == 200 + results = response.json()["results"] + assert len(results) == 7 + + # Filters: title diff --git a/src/backend/core/tests/documents/test_api_documents_mask.py b/src/backend/core/tests/documents/test_api_documents_mask.py new file mode 100644 index 00000000..8d5e4f9b --- /dev/null +++ b/src/backend/core/tests/documents/test_api_documents_mask.py @@ -0,0 +1,353 @@ +"""Test mask document API endpoint for users in impress's core app.""" + +import pytest +from rest_framework.test import APIClient + +from core import factories, models + +pytestmark = pytest.mark.django_db + + +@pytest.mark.parametrize( + "reach", + [ + "restricted", + "authenticated", + "public", + ], +) +@pytest.mark.parametrize("method", ["post", "delete"]) +def test_api_document_mask_anonymous_user(method, reach): + """Anonymous users should not be able to mask/unmask documents.""" + document = factories.DocumentFactory(link_reach=reach) + + response = getattr(APIClient(), method)( + f"/api/v1.0/documents/{document.id!s}/mask/" + ) + + assert response.status_code == 401 + assert response.json() == { + "detail": "Authentication credentials were not provided." + } + + # Verify in database + assert models.LinkTrace.objects.exists() is False + + +@pytest.mark.parametrize( + "reach, has_role", + [ + ["restricted", True], + ["authenticated", False], + ["authenticated", True], + ["public", False], + ["public", True], + ], +) +def test_api_document_mask_authenticated_post_allowed(reach, has_role): + """Authenticated users should be able to mask a document to which they have access.""" + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + document = factories.DocumentFactory(link_reach=reach) + if has_role: + models.DocumentAccess.objects.create(document=document, user=user) + + # Try masking the document without a link trace + response = client.post(f"/api/v1.0/documents/{document.id!s}/mask/") + assert response.status_code == 400 + assert response.json() == {"detail": "User never accessed this document before."} + assert not models.LinkTrace.objects.filter(document=document, user=user).exists() + + models.LinkTrace.objects.create(document=document, user=user) + # Mask document + response = client.post(f"/api/v1.0/documents/{document.id!s}/mask/") + + assert response.status_code == 201 + assert response.json() == {"detail": "Document was masked"} + assert models.LinkTrace.objects.filter( + document=document, user=user, is_masked=True + ).exists() + + +def test_api_document_mask_authenticated_post_forbidden(): + """ + Authenticated users should no be allowed to mask a document + to which they don't have access. + """ + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + document = factories.DocumentFactory(link_reach="restricted") + + # Try masking + response = client.post(f"/api/v1.0/documents/{document.id!s}/mask/") + + assert response.status_code == 403 + assert response.json() == { + "detail": "You do not have permission to perform this action." + } + + # Verify in database + assert ( + models.LinkTrace.objects.filter(document=document, user=user).exists() is False + ) + + +@pytest.mark.parametrize( + "reach, has_role", + [ + ["restricted", True], + ["authenticated", False], + ["authenticated", True], + ["public", False], + ["public", True], + ], +) +def test_api_document_mask_authenticated_post_already_masked_allowed(reach, has_role): + """POST should not create duplicate link trace if already marked.""" + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + document = factories.DocumentFactory(link_reach=reach, masked_by=[user]) + if has_role: + models.DocumentAccess.objects.create(document=document, user=user) + + # Try masking again + response = client.post(f"/api/v1.0/documents/{document.id!s}/mask/") + + assert response.status_code == 200 + assert response.json() == {"detail": "Document was already masked"} + assert models.LinkTrace.objects.filter( + document=document, user=user, is_masked=True + ).exists() + + +def test_api_document_mask_authenticated_post_already_masked_forbidden(): + """POST should not create duplicate masks if already marked.""" + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + document = factories.DocumentFactory(link_reach="restricted", masked_by=[user]) + # Try masking again + response = client.post(f"/api/v1.0/documents/{document.id!s}/mask/") + + assert response.status_code == 403 + assert response.json() == { + "detail": "You do not have permission to perform this action." + } + assert models.LinkTrace.objects.filter(document=document, user=user).exists() + + +@pytest.mark.parametrize( + "reach, has_role", + [ + ["restricted", True], + ["authenticated", False], + ["authenticated", True], + ["public", False], + ["public", True], + ], +) +def test_api_document_mask_authenticated_post_unmasked_allowed(reach, has_role): + """POST should not create duplicate link trace if unmasked.""" + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + document = factories.DocumentFactory(link_reach=reach) + models.LinkTrace.objects.create(document=document, user=user, is_masked=False) + if has_role: + models.DocumentAccess.objects.create(document=document, user=user) + + # Try masking again + response = client.post(f"/api/v1.0/documents/{document.id!s}/mask/") + + assert response.status_code == 201 + assert response.json() == {"detail": "Document was masked"} + assert models.LinkTrace.objects.filter( + document=document, user=user, is_masked=True + ).exists() + + +def test_api_document_mask_authenticated_post_unmasked_forbidden(): + """POST should not create duplicate masks if unmasked.""" + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + document = factories.DocumentFactory(link_reach="restricted") + models.LinkTrace.objects.create(document=document, user=user, is_masked=False) + # Try masking again + response = client.post(f"/api/v1.0/documents/{document.id!s}/mask/") + + assert response.status_code == 403 + assert response.json() == { + "detail": "You do not have permission to perform this action." + } + assert models.LinkTrace.objects.filter( + document=document, user=user, is_masked=False + ).exists() + + +@pytest.mark.parametrize( + "reach, has_role", + [ + ["restricted", True], + ["authenticated", False], + ["authenticated", True], + ["public", False], + ["public", True], + ], +) +def test_api_document_mask_authenticated_delete_allowed(reach, has_role): + """Authenticated users should be able to unmask a document using DELETE.""" + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + document = factories.DocumentFactory(link_reach=reach, masked_by=[user]) + if has_role: + models.DocumentAccess.objects.create(document=document, user=user) + + # Unmask document + response = client.delete(f"/api/v1.0/documents/{document.id!s}/mask/") + + assert response.status_code == 204 + assert response.content == b"" # No body + assert response.text == "" # Empty decoded text + assert "Content-Type" not in response.headers # No Content-Type for 204 + + assert models.LinkTrace.objects.filter( + document=document, user=user, is_masked=False + ).exists() + + +def test_api_document_mask_authenticated_delete_forbidden(): + """ + Authenticated users should not be allowed to unmask a document if + they don't have access to it. + """ + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + document = factories.DocumentFactory(link_reach="restricted", masked_by=[user]) + + # Unmask document + response = client.delete(f"/api/v1.0/documents/{document.id!s}/mask/") + + assert response.status_code == 403 + assert response.json() == { + "detail": "You do not have permission to perform this action." + } + assert models.LinkTrace.objects.filter( + document=document, user=user, is_masked=True + ).exists() + + +@pytest.mark.parametrize( + "reach, has_role", + [ + ["restricted", True], + ["authenticated", False], + ["authenticated", True], + ["public", False], + ["public", True], + ], +) +def test_api_document_mask_authenticated_delete_not_masked_allowed(reach, has_role): + """DELETE should be idempotent if the document is not masked.""" + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + document = factories.DocumentFactory(link_reach=reach) + if has_role: + models.DocumentAccess.objects.create(document=document, user=user) + + # Try unmasking the document without a link trace + response = client.delete(f"/api/v1.0/documents/{document.id!s}/mask/") + assert response.status_code == 400 + assert response.json() == {"detail": "User never accessed this document before."} + assert not models.LinkTrace.objects.filter(document=document, user=user).exists() + + models.LinkTrace.objects.create(document=document, user=user, is_masked=False) + # Unmask document + response = client.delete(f"/api/v1.0/documents/{document.id!s}/mask/") + + assert response.status_code == 200 + assert response.json() == {"detail": "Document was already not masked"} + assert models.LinkTrace.objects.filter( + document=document, user=user, is_masked=False + ).exists() + + +def test_api_document_mask_authenticated_delete_not_masked_forbidden(): + """DELETE should be idempotent if the document is not masked.""" + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + document = factories.DocumentFactory(link_reach="restricted") + + # Try to unmask when no entry exists + response = client.delete(f"/api/v1.0/documents/{document.id!s}/mask/") + + assert response.status_code == 403 + assert response.json() == { + "detail": "You do not have permission to perform this action." + } + assert ( + models.LinkTrace.objects.filter(document=document, user=user).exists() is False + ) + + +@pytest.mark.parametrize( + "reach, has_role", + [ + ["restricted", True], + ["authenticated", False], + ["authenticated", True], + ["public", False], + ["public", True], + ], +) +def test_api_document_mask_authenticated_post_unmark_then_mark_again_allowed( + reach, has_role +): + """A user should be able to mask, unmask, and mask a document again.""" + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + document = factories.DocumentFactory(link_reach=reach) + if has_role: + models.DocumentAccess.objects.create(document=document, user=user) + models.LinkTrace.objects.create(document=document, user=user, is_masked=False) + + url = f"/api/v1.0/documents/{document.id!s}/mask/" + + # Mask document + response = client.post(url) + assert response.status_code == 201 + + # Unmask document + response = client.delete(url) + assert response.status_code == 204 + assert response.content == b"" # No body + assert response.text == "" # Empty decoded text + assert "Content-Type" not in response.headers # No Content-Type for 204 + + # Mask document again + response = client.post(url) + assert response.status_code == 201 + assert response.json() == {"detail": "Document was masked"} + + assert models.LinkTrace.objects.filter( + document=document, user=user, is_masked=True + ).exists() diff --git a/src/backend/core/tests/documents/test_api_documents_retrieve.py b/src/backend/core/tests/documents/test_api_documents_retrieve.py index b229adb5..63fa8f09 100644 --- a/src/backend/core/tests/documents/test_api_documents_retrieve.py +++ b/src/backend/core/tests/documents/test_api_documents_retrieve.py @@ -49,6 +49,7 @@ def test_api_documents_retrieve_anonymous_public_standalone(): "public": ["reader", "editor"], "restricted": None, }, + "mask": False, "media_auth": True, "media_check": True, "move": False, @@ -121,6 +122,7 @@ def test_api_documents_retrieve_anonymous_public_parent(): "link_select_options": models.LinkReachChoices.get_select_options( **links_definition ), + "mask": False, "media_auth": True, "media_check": True, "move": False, @@ -226,6 +228,7 @@ def test_api_documents_retrieve_authenticated_unrelated_public_or_authenticated( "public": ["reader", "editor"], "restricted": None, }, + "mask": True, "media_auth": True, "media_check": True, "move": False, @@ -305,6 +308,7 @@ def test_api_documents_retrieve_authenticated_public_or_authenticated_parent(rea "link_select_options": models.LinkReachChoices.get_select_options( **links_definition ), + "mask": True, "move": False, "media_auth": True, "media_check": True, @@ -498,6 +502,7 @@ def test_api_documents_retrieve_authenticated_related_parent(): "link_select_options": models.LinkReachChoices.get_select_options( **link_definition ), + "mask": True, "media_auth": True, "media_check": True, "move": access.role in ["administrator", "owner"], diff --git a/src/backend/core/tests/documents/test_api_documents_trashbin.py b/src/backend/core/tests/documents/test_api_documents_trashbin.py index 9e805397..25bc1cda 100644 --- a/src/backend/core/tests/documents/test_api_documents_trashbin.py +++ b/src/backend/core/tests/documents/test_api_documents_trashbin.py @@ -91,6 +91,7 @@ def test_api_documents_trashbin_format(): "public": ["reader", "editor"], "restricted": None, }, + "mask": True, "media_auth": True, "media_check": True, "move": False, # Can't move a deleted document diff --git a/src/backend/core/tests/test_models_documents.py b/src/backend/core/tests/test_models_documents.py index 57427fb9..6874009c 100644 --- a/src/backend/core/tests/test_models_documents.py +++ b/src/backend/core/tests/test_models_documents.py @@ -165,6 +165,7 @@ def test_models_documents_get_abilities_forbidden( "duplicate": False, "favorite": False, "invite_owner": False, + "mask": False, "media_auth": False, "media_check": False, "move": False, @@ -233,6 +234,7 @@ def test_models_documents_get_abilities_reader( "public": ["reader", "editor"], "restricted": None, }, + "mask": is_authenticated, "media_auth": True, "media_check": True, "move": False, @@ -297,6 +299,7 @@ def test_models_documents_get_abilities_editor( "public": ["reader", "editor"], "restricted": None, }, + "mask": is_authenticated, "media_auth": True, "media_check": True, "move": False, @@ -350,6 +353,7 @@ def test_models_documents_get_abilities_owner(django_assert_num_queries): "public": ["reader", "editor"], "restricted": None, }, + "mask": True, "media_auth": True, "media_check": True, "move": True, @@ -400,6 +404,7 @@ def test_models_documents_get_abilities_administrator(django_assert_num_queries) "public": ["reader", "editor"], "restricted": None, }, + "mask": True, "media_auth": True, "media_check": True, "move": True, @@ -453,6 +458,7 @@ def test_models_documents_get_abilities_editor_user(django_assert_num_queries): "public": ["reader", "editor"], "restricted": None, }, + "mask": True, "media_auth": True, "media_check": True, "move": False, @@ -513,6 +519,7 @@ def test_models_documents_get_abilities_reader_user( "public": ["reader", "editor"], "restricted": None, }, + "mask": True, "media_auth": True, "media_check": True, "move": False, @@ -571,6 +578,7 @@ def test_models_documents_get_abilities_preset_role(django_assert_num_queries): "public": ["reader", "editor"], "restricted": None, }, + "mask": True, "media_auth": True, "media_check": True, "move": False,