diff --git a/CHANGELOG.md b/CHANGELOG.md index 96337e3a3..e8d73e6f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,10 @@ and this project adheres to - 🛂(frontend) fix cannot manage member on small screen #2226 - 🐛(backend) load jwks url when OIDC_RS_PRIVATE_KEY_STR is set +### Removed + +- đŸ”„(backend) remove deprecated descendants endpoint #2243 + ## [v4.8.6] - 2026-04-08 ### Added diff --git a/UPGRADE.md b/UPGRADE.md index b5ba82544..17ea69698 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -16,6 +16,8 @@ the following command inside your docker container: ## [Unreleased] +- The deprecated endpoint `/api/v1.0/documents//descendants` is removed. The search endpoint should be used instead. + ## [4.6.0] - 2026-02-27 - ⚠ Some setup have changed to offer a bigger flexibility and consistency, overriding the favicon and logo are now from the theme configuration. diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index 32e52f710..d87929d69 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -1112,30 +1112,6 @@ class DocumentViewSet( return self.get_response_for_queryset(queryset) - @drf.decorators.action( - detail=True, - methods=["get"], - ordering=["path"], - ) - def descendants(self, request, *args, **kwargs): - """Deprecated endpoint to list descendants of a document.""" - logger.warning( - "The 'descendants' endpoint is deprecated and will be removed in a future release. " - "The search endpoint should be used for all document retrieval use cases." - ) - document = self.get_object() - - queryset = document.get_descendants().filter(ancestors_deleted_at__isnull=True) - queryset = self.filter_queryset(queryset) - - filterset = DocumentFilter(request.GET, queryset=queryset) - if not filterset.is_valid(): - raise drf.exceptions.ValidationError(filterset.errors) - - queryset = filterset.qs - - return self.get_response_for_queryset(queryset) - @drf.decorators.action( detail=True, methods=["get"], diff --git a/src/backend/core/tests/documents/test_api_documents_descendants.py b/src/backend/core/tests/documents/test_api_documents_descendants.py deleted file mode 100644 index f320b0707..000000000 --- a/src/backend/core/tests/documents/test_api_documents_descendants.py +++ /dev/null @@ -1,807 +0,0 @@ -""" -Tests for Documents API endpoint in impress's core app: descendants -""" - -import random - -from django.contrib.auth.models import AnonymousUser - -import pytest -from rest_framework.test import APIClient - -from core import factories - -pytestmark = pytest.mark.django_db - - -def test_api_documents_descendants_list_anonymous_public_standalone(): - """Anonymous users should be allowed to retrieve the descendants of a public document.""" - document = factories.DocumentFactory(link_reach="public") - child1, child2 = factories.DocumentFactory.create_batch(2, parent=document) - grand_child = factories.DocumentFactory(parent=child1) - - factories.UserDocumentAccessFactory(document=child1) - - response = APIClient().get(f"/api/v1.0/documents/{document.id!s}/descendants/") - - assert response.status_code == 200 - assert response.json() == { - "count": 3, - "next": None, - "previous": None, - "results": [ - { - "abilities": child1.get_abilities(AnonymousUser()), - "ancestors_link_reach": "public", - "ancestors_link_role": document.link_role, - "computed_link_reach": child1.computed_link_reach, - "computed_link_role": child1.computed_link_role, - "created_at": child1.created_at.isoformat().replace("+00:00", "Z"), - "creator": str(child1.creator.id), - "deleted_at": None, - "depth": 2, - "excerpt": child1.excerpt, - "id": str(child1.id), - "is_favorite": False, - "link_reach": child1.link_reach, - "link_role": child1.link_role, - "numchild": 1, - "nb_accesses_ancestors": 1, - "nb_accesses_direct": 1, - "path": child1.path, - "title": child1.title, - "updated_at": child1.updated_at.isoformat().replace("+00:00", "Z"), - "user_role": None, - }, - { - "abilities": grand_child.get_abilities(AnonymousUser()), - "ancestors_link_reach": "public", - "ancestors_link_role": "editor" - if (child1.link_reach == "public" and child1.link_role == "editor") - else document.link_role, - "computed_link_reach": "public", - "computed_link_role": grand_child.computed_link_role, - "created_at": grand_child.created_at.isoformat().replace("+00:00", "Z"), - "creator": str(grand_child.creator.id), - "deleted_at": None, - "depth": 3, - "excerpt": grand_child.excerpt, - "id": str(grand_child.id), - "is_favorite": False, - "link_reach": grand_child.link_reach, - "link_role": grand_child.link_role, - "numchild": 0, - "nb_accesses_ancestors": 1, - "nb_accesses_direct": 0, - "path": grand_child.path, - "title": grand_child.title, - "updated_at": grand_child.updated_at.isoformat().replace("+00:00", "Z"), - "user_role": None, - }, - { - "abilities": child2.get_abilities(AnonymousUser()), - "ancestors_link_reach": "public", - "ancestors_link_role": document.link_role, - "computed_link_reach": "public", - "computed_link_role": child2.computed_link_role, - "created_at": child2.created_at.isoformat().replace("+00:00", "Z"), - "creator": str(child2.creator.id), - "deleted_at": None, - "depth": 2, - "excerpt": child2.excerpt, - "id": str(child2.id), - "is_favorite": False, - "link_reach": child2.link_reach, - "link_role": child2.link_role, - "numchild": 0, - "nb_accesses_ancestors": 0, - "nb_accesses_direct": 0, - "path": child2.path, - "title": child2.title, - "updated_at": child2.updated_at.isoformat().replace("+00:00", "Z"), - "user_role": None, - }, - ], - } - - -def test_api_documents_descendants_list_anonymous_public_parent(): - """ - Anonymous users should be allowed to retrieve the descendants of a document who - has a public ancestor. - """ - grand_parent = factories.DocumentFactory(link_reach="public") - parent = factories.DocumentFactory( - parent=grand_parent, link_reach=random.choice(["authenticated", "restricted"]) - ) - document = factories.DocumentFactory( - link_reach=random.choice(["authenticated", "restricted"]), parent=parent - ) - child1, child2 = factories.DocumentFactory.create_batch(2, parent=document) - grand_child = factories.DocumentFactory(parent=child1) - - factories.UserDocumentAccessFactory(document=child1) - - response = APIClient().get(f"/api/v1.0/documents/{document.id!s}/descendants/") - - assert response.status_code == 200 - assert response.json() == { - "count": 3, - "next": None, - "previous": None, - "results": [ - { - "abilities": child1.get_abilities(AnonymousUser()), - "ancestors_link_reach": "public", - "ancestors_link_role": grand_parent.link_role, - "computed_link_reach": child1.computed_link_reach, - "computed_link_role": child1.computed_link_role, - "created_at": child1.created_at.isoformat().replace("+00:00", "Z"), - "creator": str(child1.creator.id), - "deleted_at": None, - "depth": 4, - "excerpt": child1.excerpt, - "id": str(child1.id), - "is_favorite": False, - "link_reach": child1.link_reach, - "link_role": child1.link_role, - "numchild": 1, - "nb_accesses_ancestors": 1, - "nb_accesses_direct": 1, - "path": child1.path, - "title": child1.title, - "updated_at": child1.updated_at.isoformat().replace("+00:00", "Z"), - "user_role": None, - }, - { - "abilities": grand_child.get_abilities(AnonymousUser()), - "ancestors_link_reach": "public", - "ancestors_link_role": grand_child.ancestors_link_role, - "computed_link_reach": "public", - "computed_link_role": grand_child.computed_link_role, - "created_at": grand_child.created_at.isoformat().replace("+00:00", "Z"), - "creator": str(grand_child.creator.id), - "deleted_at": None, - "depth": 5, - "excerpt": grand_child.excerpt, - "id": str(grand_child.id), - "is_favorite": False, - "link_reach": grand_child.link_reach, - "link_role": grand_child.link_role, - "numchild": 0, - "nb_accesses_ancestors": 1, - "nb_accesses_direct": 0, - "path": grand_child.path, - "title": grand_child.title, - "updated_at": grand_child.updated_at.isoformat().replace("+00:00", "Z"), - "user_role": None, - }, - { - "abilities": child2.get_abilities(AnonymousUser()), - "ancestors_link_reach": "public", - "ancestors_link_role": grand_parent.link_role, - "computed_link_reach": "public", - "computed_link_role": child2.computed_link_role, - "created_at": child2.created_at.isoformat().replace("+00:00", "Z"), - "creator": str(child2.creator.id), - "deleted_at": None, - "depth": 4, - "excerpt": child2.excerpt, - "id": str(child2.id), - "is_favorite": False, - "link_reach": child2.link_reach, - "link_role": child2.link_role, - "numchild": 0, - "nb_accesses_ancestors": 0, - "nb_accesses_direct": 0, - "path": child2.path, - "title": child2.title, - "updated_at": child2.updated_at.isoformat().replace("+00:00", "Z"), - "user_role": None, - }, - ], - } - - -@pytest.mark.parametrize("reach", ["restricted", "authenticated"]) -def test_api_documents_descendants_list_anonymous_restricted_or_authenticated(reach): - """ - Anonymous users should not be able to retrieve descendants of a document that is not public. - """ - document = factories.DocumentFactory(link_reach=reach) - child = factories.DocumentFactory(parent=document) - _grand_child = factories.DocumentFactory(parent=child) - - response = APIClient().get(f"/api/v1.0/documents/{document.id!s}/descendants/") - - assert response.status_code == 401 - assert response.json() == { - "detail": "Authentication credentials were not provided." - } - - -@pytest.mark.parametrize("reach", ["public", "authenticated"]) -def test_api_documents_descendants_list_authenticated_unrelated_public_or_authenticated( - reach, -): - """ - Authenticated users should be able to retrieve the descendants of a public/authenticated - document to which they are not related. - """ - user = factories.UserFactory() - client = APIClient() - client.force_login(user) - - document = factories.DocumentFactory(link_reach=reach) - child1, child2 = factories.DocumentFactory.create_batch( - 2, parent=document, link_reach="restricted" - ) - grand_child = factories.DocumentFactory(parent=child1) - - factories.UserDocumentAccessFactory(document=child1) - - response = client.get( - f"/api/v1.0/documents/{document.id!s}/descendants/", - ) - assert response.status_code == 200 - assert response.json() == { - "count": 3, - "next": None, - "previous": None, - "results": [ - { - "abilities": child1.get_abilities(user), - "ancestors_link_reach": reach, - "ancestors_link_role": document.link_role, - "computed_link_reach": child1.computed_link_reach, - "computed_link_role": child1.computed_link_role, - "created_at": child1.created_at.isoformat().replace("+00:00", "Z"), - "creator": str(child1.creator.id), - "deleted_at": None, - "depth": 2, - "excerpt": child1.excerpt, - "id": str(child1.id), - "is_favorite": False, - "link_reach": child1.link_reach, - "link_role": child1.link_role, - "numchild": 1, - "nb_accesses_ancestors": 1, - "nb_accesses_direct": 1, - "path": child1.path, - "title": child1.title, - "updated_at": child1.updated_at.isoformat().replace("+00:00", "Z"), - "user_role": None, - }, - { - "abilities": grand_child.get_abilities(user), - "ancestors_link_reach": reach, - "ancestors_link_role": document.link_role, - "computed_link_reach": grand_child.computed_link_reach, - "computed_link_role": grand_child.computed_link_role, - "created_at": grand_child.created_at.isoformat().replace("+00:00", "Z"), - "creator": str(grand_child.creator.id), - "deleted_at": None, - "depth": 3, - "excerpt": grand_child.excerpt, - "id": str(grand_child.id), - "is_favorite": False, - "link_reach": grand_child.link_reach, - "link_role": grand_child.link_role, - "numchild": 0, - "nb_accesses_ancestors": 1, - "nb_accesses_direct": 0, - "path": grand_child.path, - "title": grand_child.title, - "updated_at": grand_child.updated_at.isoformat().replace("+00:00", "Z"), - "user_role": None, - }, - { - "abilities": child2.get_abilities(user), - "ancestors_link_reach": reach, - "ancestors_link_role": document.link_role, - "computed_link_reach": child2.computed_link_reach, - "computed_link_role": child2.computed_link_role, - "created_at": child2.created_at.isoformat().replace("+00:00", "Z"), - "creator": str(child2.creator.id), - "deleted_at": None, - "depth": 2, - "excerpt": child2.excerpt, - "id": str(child2.id), - "is_favorite": False, - "link_reach": child2.link_reach, - "link_role": child2.link_role, - "numchild": 0, - "nb_accesses_ancestors": 0, - "nb_accesses_direct": 0, - "path": child2.path, - "title": child2.title, - "updated_at": child2.updated_at.isoformat().replace("+00:00", "Z"), - "user_role": None, - }, - ], - } - - -@pytest.mark.parametrize("reach", ["public", "authenticated"]) -def test_api_documents_descendants_list_authenticated_public_or_authenticated_parent( - reach, -): - """ - Authenticated users should be allowed to retrieve the descendants of a document who - has a public or authenticated ancestor. - """ - user = factories.UserFactory() - - client = APIClient() - client.force_login(user) - - grand_parent = factories.DocumentFactory(link_reach=reach) - parent = factories.DocumentFactory(parent=grand_parent, link_reach="restricted") - document = factories.DocumentFactory(link_reach="restricted", parent=parent) - child1, child2 = factories.DocumentFactory.create_batch( - 2, parent=document, link_reach="restricted" - ) - grand_child = factories.DocumentFactory(parent=child1) - - factories.UserDocumentAccessFactory(document=child1) - - response = client.get(f"/api/v1.0/documents/{document.id!s}/descendants/") - - assert response.status_code == 200 - assert response.json() == { - "count": 3, - "next": None, - "previous": None, - "results": [ - { - "abilities": child1.get_abilities(user), - "ancestors_link_reach": reach, - "ancestors_link_role": grand_parent.link_role, - "computed_link_reach": child1.computed_link_reach, - "computed_link_role": child1.computed_link_role, - "created_at": child1.created_at.isoformat().replace("+00:00", "Z"), - "creator": str(child1.creator.id), - "deleted_at": None, - "depth": 4, - "excerpt": child1.excerpt, - "id": str(child1.id), - "is_favorite": False, - "link_reach": child1.link_reach, - "link_role": child1.link_role, - "numchild": 1, - "nb_accesses_ancestors": 1, - "nb_accesses_direct": 1, - "path": child1.path, - "title": child1.title, - "updated_at": child1.updated_at.isoformat().replace("+00:00", "Z"), - "user_role": None, - }, - { - "abilities": grand_child.get_abilities(user), - "ancestors_link_reach": reach, - "ancestors_link_role": grand_parent.link_role, - "computed_link_reach": grand_child.computed_link_reach, - "computed_link_role": grand_child.computed_link_role, - "created_at": grand_child.created_at.isoformat().replace("+00:00", "Z"), - "creator": str(grand_child.creator.id), - "deleted_at": None, - "depth": 5, - "excerpt": grand_child.excerpt, - "id": str(grand_child.id), - "is_favorite": False, - "link_reach": grand_child.link_reach, - "link_role": grand_child.link_role, - "numchild": 0, - "nb_accesses_ancestors": 1, - "nb_accesses_direct": 0, - "path": grand_child.path, - "title": grand_child.title, - "updated_at": grand_child.updated_at.isoformat().replace("+00:00", "Z"), - "user_role": None, - }, - { - "abilities": child2.get_abilities(user), - "ancestors_link_reach": reach, - "ancestors_link_role": grand_parent.link_role, - "computed_link_reach": child2.computed_link_reach, - "computed_link_role": child2.computed_link_role, - "created_at": child2.created_at.isoformat().replace("+00:00", "Z"), - "creator": str(child2.creator.id), - "deleted_at": None, - "depth": 4, - "excerpt": child2.excerpt, - "id": str(child2.id), - "is_favorite": False, - "link_reach": child2.link_reach, - "link_role": child2.link_role, - "numchild": 0, - "nb_accesses_ancestors": 0, - "nb_accesses_direct": 0, - "path": child2.path, - "title": child2.title, - "updated_at": child2.updated_at.isoformat().replace("+00:00", "Z"), - "user_role": None, - }, - ], - } - - -def test_api_documents_descendants_list_authenticated_unrelated_restricted(): - """ - Authenticated users should not be allowed to retrieve the descendants of a document that is - restricted and to which they are not related. - """ - user = factories.UserFactory(with_owned_document=True) - client = APIClient() - client.force_login(user) - - document = factories.DocumentFactory(link_reach="restricted") - child1, _child2 = factories.DocumentFactory.create_batch(2, parent=document) - _grand_child = factories.DocumentFactory(parent=child1) - - factories.UserDocumentAccessFactory(document=child1) - - response = client.get( - f"/api/v1.0/documents/{document.id!s}/descendants/", - ) - assert response.status_code == 403 - assert response.json() == { - "detail": "You do not have permission to perform this action." - } - - -def test_api_documents_descendants_list_authenticated_related_direct(): - """ - Authenticated users should be allowed to retrieve the descendants of a document - to which they are directly related whatever the role. - """ - user = factories.UserFactory() - - client = APIClient() - client.force_login(user) - - document = factories.DocumentFactory() - access = factories.UserDocumentAccessFactory(document=document, user=user) - factories.UserDocumentAccessFactory(document=document) - - child1, child2 = factories.DocumentFactory.create_batch(2, parent=document) - factories.UserDocumentAccessFactory(document=child1) - - grand_child = factories.DocumentFactory(parent=child1) - - response = client.get( - f"/api/v1.0/documents/{document.id!s}/descendants/", - ) - assert response.status_code == 200 - assert response.json() == { - "count": 3, - "next": None, - "previous": None, - "results": [ - { - "abilities": child1.get_abilities(user), - "ancestors_link_reach": child1.ancestors_link_reach, - "ancestors_link_role": child1.ancestors_link_role, - "computed_link_reach": child1.computed_link_reach, - "computed_link_role": child1.computed_link_role, - "created_at": child1.created_at.isoformat().replace("+00:00", "Z"), - "creator": str(child1.creator.id), - "deleted_at": None, - "depth": 2, - "excerpt": child1.excerpt, - "id": str(child1.id), - "is_favorite": False, - "link_reach": child1.link_reach, - "link_role": child1.link_role, - "numchild": 1, - "nb_accesses_ancestors": 3, - "nb_accesses_direct": 1, - "path": child1.path, - "title": child1.title, - "updated_at": child1.updated_at.isoformat().replace("+00:00", "Z"), - "user_role": access.role, - }, - { - "abilities": grand_child.get_abilities(user), - "ancestors_link_reach": grand_child.ancestors_link_reach, - "ancestors_link_role": grand_child.ancestors_link_role, - "computed_link_reach": grand_child.computed_link_reach, - "computed_link_role": grand_child.computed_link_role, - "created_at": grand_child.created_at.isoformat().replace("+00:00", "Z"), - "creator": str(grand_child.creator.id), - "deleted_at": None, - "depth": 3, - "excerpt": grand_child.excerpt, - "id": str(grand_child.id), - "is_favorite": False, - "link_reach": grand_child.link_reach, - "link_role": grand_child.link_role, - "numchild": 0, - "nb_accesses_ancestors": 3, - "nb_accesses_direct": 0, - "path": grand_child.path, - "title": grand_child.title, - "updated_at": grand_child.updated_at.isoformat().replace("+00:00", "Z"), - "user_role": access.role, - }, - { - "abilities": child2.get_abilities(user), - "ancestors_link_reach": child2.ancestors_link_reach, - "ancestors_link_role": child2.ancestors_link_role, - "computed_link_reach": child2.computed_link_reach, - "computed_link_role": child2.computed_link_role, - "created_at": child2.created_at.isoformat().replace("+00:00", "Z"), - "creator": str(child2.creator.id), - "deleted_at": None, - "depth": 2, - "excerpt": child2.excerpt, - "id": str(child2.id), - "is_favorite": False, - "link_reach": child2.link_reach, - "link_role": child2.link_role, - "numchild": 0, - "nb_accesses_ancestors": 2, - "nb_accesses_direct": 0, - "path": child2.path, - "title": child2.title, - "updated_at": child2.updated_at.isoformat().replace("+00:00", "Z"), - "user_role": access.role, - }, - ], - } - - -def test_api_documents_descendants_list_authenticated_related_parent(): - """ - Authenticated users should be allowed to retrieve the descendants of a document if they - are related to one of its ancestors whatever the role. - """ - user = factories.UserFactory() - - client = APIClient() - client.force_login(user) - - grand_parent = factories.DocumentFactory(link_reach="restricted") - grand_parent_access = factories.UserDocumentAccessFactory( - document=grand_parent, user=user - ) - - parent = factories.DocumentFactory(parent=grand_parent, link_reach="restricted") - document = factories.DocumentFactory(parent=parent, link_reach="restricted") - - child1, child2 = factories.DocumentFactory.create_batch(2, parent=document) - factories.UserDocumentAccessFactory(document=child1) - - grand_child = factories.DocumentFactory(parent=child1) - - response = client.get( - f"/api/v1.0/documents/{document.id!s}/descendants/", - ) - assert response.status_code == 200 - assert response.json() == { - "count": 3, - "next": None, - "previous": None, - "results": [ - { - "abilities": child1.get_abilities(user), - "ancestors_link_reach": child1.ancestors_link_reach, - "ancestors_link_role": child1.ancestors_link_role, - "computed_link_reach": child1.computed_link_reach, - "computed_link_role": child1.computed_link_role, - "created_at": child1.created_at.isoformat().replace("+00:00", "Z"), - "creator": str(child1.creator.id), - "deleted_at": None, - "depth": 4, - "excerpt": child1.excerpt, - "id": str(child1.id), - "is_favorite": False, - "link_reach": child1.link_reach, - "link_role": child1.link_role, - "numchild": 1, - "nb_accesses_ancestors": 2, - "nb_accesses_direct": 1, - "path": child1.path, - "title": child1.title, - "updated_at": child1.updated_at.isoformat().replace("+00:00", "Z"), - "user_role": grand_parent_access.role, - }, - { - "abilities": grand_child.get_abilities(user), - "ancestors_link_reach": grand_child.ancestors_link_reach, - "ancestors_link_role": grand_child.ancestors_link_role, - "computed_link_reach": grand_child.computed_link_reach, - "computed_link_role": grand_child.computed_link_role, - "created_at": grand_child.created_at.isoformat().replace("+00:00", "Z"), - "creator": str(grand_child.creator.id), - "deleted_at": None, - "depth": 5, - "excerpt": grand_child.excerpt, - "id": str(grand_child.id), - "is_favorite": False, - "link_reach": grand_child.link_reach, - "link_role": grand_child.link_role, - "numchild": 0, - "nb_accesses_ancestors": 2, - "nb_accesses_direct": 0, - "path": grand_child.path, - "title": grand_child.title, - "updated_at": grand_child.updated_at.isoformat().replace("+00:00", "Z"), - "user_role": grand_parent_access.role, - }, - { - "abilities": child2.get_abilities(user), - "ancestors_link_reach": child2.ancestors_link_reach, - "ancestors_link_role": child2.ancestors_link_role, - "computed_link_reach": child2.computed_link_reach, - "computed_link_role": child2.computed_link_role, - "created_at": child2.created_at.isoformat().replace("+00:00", "Z"), - "creator": str(child2.creator.id), - "deleted_at": None, - "depth": 4, - "excerpt": child2.excerpt, - "id": str(child2.id), - "is_favorite": False, - "link_reach": child2.link_reach, - "link_role": child2.link_role, - "numchild": 0, - "nb_accesses_ancestors": 1, - "nb_accesses_direct": 0, - "path": child2.path, - "title": child2.title, - "updated_at": child2.updated_at.isoformat().replace("+00:00", "Z"), - "user_role": grand_parent_access.role, - }, - ], - } - - -def test_api_documents_descendants_list_authenticated_related_child(): - """ - Authenticated users should not be allowed to retrieve all the descendants of a document - as a result of being related to one of its children. - """ - user = factories.UserFactory() - client = APIClient() - client.force_login(user) - - document = factories.DocumentFactory(link_reach="restricted") - child1, _child2 = factories.DocumentFactory.create_batch(2, parent=document) - _grand_child = factories.DocumentFactory(parent=child1) - - factories.UserDocumentAccessFactory(document=child1, user=user) - factories.UserDocumentAccessFactory(document=document) - - response = client.get( - f"/api/v1.0/documents/{document.id!s}/descendants/", - ) - assert response.status_code == 403 - assert response.json() == { - "detail": "You do not have permission to perform this action." - } - - -def test_api_documents_descendants_list_authenticated_related_team_none( - mock_user_teams, -): - """ - Authenticated users should not be able to retrieve the descendants of a restricted document - related to teams in which the user is not. - """ - mock_user_teams.return_value = [] - - user = factories.UserFactory(with_owned_document=True) - client = APIClient() - client.force_login(user) - - document = factories.DocumentFactory(link_reach="restricted") - factories.DocumentFactory.create_batch(2, parent=document) - - factories.TeamDocumentAccessFactory(document=document, team="myteam") - - response = client.get(f"/api/v1.0/documents/{document.id!s}/descendants/") - assert response.status_code == 403 - assert response.json() == { - "detail": "You do not have permission to perform this action." - } - - -def test_api_documents_descendants_list_authenticated_related_team_members( - mock_user_teams, -): - """ - Authenticated users should be allowed to retrieve the descendants of a document to which they - are related via a team whatever the role. - """ - mock_user_teams.return_value = ["myteam"] - - user = factories.UserFactory() - client = APIClient() - client.force_login(user) - - document = factories.DocumentFactory(link_reach="restricted") - child1, child2 = factories.DocumentFactory.create_batch(2, parent=document) - grand_child = factories.DocumentFactory(parent=child1) - - access = factories.TeamDocumentAccessFactory(document=document, team="myteam") - - response = client.get(f"/api/v1.0/documents/{document.id!s}/descendants/") - - # pylint: disable=R0801 - assert response.status_code == 200 - assert response.json() == { - "count": 3, - "next": None, - "previous": None, - "results": [ - { - "abilities": child1.get_abilities(user), - "ancestors_link_reach": child1.ancestors_link_reach, - "ancestors_link_role": child1.ancestors_link_role, - "computed_link_reach": child1.computed_link_reach, - "computed_link_role": child1.computed_link_role, - "created_at": child1.created_at.isoformat().replace("+00:00", "Z"), - "creator": str(child1.creator.id), - "deleted_at": None, - "depth": 2, - "excerpt": child1.excerpt, - "id": str(child1.id), - "is_favorite": False, - "link_reach": child1.link_reach, - "link_role": child1.link_role, - "numchild": 1, - "nb_accesses_ancestors": 1, - "nb_accesses_direct": 0, - "path": child1.path, - "title": child1.title, - "updated_at": child1.updated_at.isoformat().replace("+00:00", "Z"), - "user_role": access.role, - }, - { - "abilities": grand_child.get_abilities(user), - "ancestors_link_reach": grand_child.ancestors_link_reach, - "ancestors_link_role": grand_child.ancestors_link_role, - "computed_link_reach": grand_child.computed_link_reach, - "computed_link_role": grand_child.computed_link_role, - "created_at": grand_child.created_at.isoformat().replace("+00:00", "Z"), - "creator": str(grand_child.creator.id), - "deleted_at": None, - "depth": 3, - "excerpt": grand_child.excerpt, - "id": str(grand_child.id), - "is_favorite": False, - "link_reach": grand_child.link_reach, - "link_role": grand_child.link_role, - "numchild": 0, - "nb_accesses_ancestors": 1, - "nb_accesses_direct": 0, - "path": grand_child.path, - "title": grand_child.title, - "updated_at": grand_child.updated_at.isoformat().replace("+00:00", "Z"), - "user_role": access.role, - }, - { - "abilities": child2.get_abilities(user), - "ancestors_link_reach": child2.ancestors_link_reach, - "ancestors_link_role": child2.ancestors_link_role, - "computed_link_reach": child2.computed_link_reach, - "computed_link_role": child2.computed_link_role, - "created_at": child2.created_at.isoformat().replace("+00:00", "Z"), - "creator": str(child2.creator.id), - "deleted_at": None, - "depth": 2, - "excerpt": child2.excerpt, - "id": str(child2.id), - "is_favorite": False, - "link_reach": child2.link_reach, - "link_role": child2.link_role, - "numchild": 0, - "nb_accesses_ancestors": 1, - "nb_accesses_direct": 0, - "path": child2.path, - "title": child2.title, - "updated_at": child2.updated_at.isoformat().replace("+00:00", "Z"), - "user_role": access.role, - }, - ], - } diff --git a/src/backend/core/tests/documents/test_api_documents_descendants_filters.py b/src/backend/core/tests/documents/test_api_documents_descendants_filters.py deleted file mode 100644 index 342ead705..000000000 --- a/src/backend/core/tests/documents/test_api_documents_descendants_filters.py +++ /dev/null @@ -1,95 +0,0 @@ -""" -Tests for Documents API endpoint in impress's core app: list -""" - -import pytest -from faker import Faker -from rest_framework.test import APIClient - -from core import factories -from core.api.filters import remove_accents - -fake = Faker() -pytestmark = pytest.mark.django_db - - -# Filters: unknown field - - -def test_api_documents_descendants_filter_unknown_field(): - """ - Trying to filter by an unknown field should be ignored. - """ - user = factories.UserFactory() - client = APIClient() - client.force_login(user) - - factories.DocumentFactory() - - document = factories.DocumentFactory(users=[user]) - expected_ids = { - str(document.id) - for document in factories.DocumentFactory.create_batch(2, parent=document) - } - - response = client.get( - f"/api/v1.0/documents/{document.id!s}/descendants/?unknown=true" - ) - - assert response.status_code == 200 - results = response.json()["results"] - assert len(results) == 2 - assert {result["id"] for result in results} == expected_ids - - -# Filters: title - - -@pytest.mark.parametrize( - "query,nb_results", - [ - ("Project Alpha", 1), # Exact match - ("project", 2), # Partial match (case-insensitive) - ("Guide", 2), # Word match within a title - ("Special", 0), # No match (nonexistent keyword) - ("2024", 2), # Match by numeric keyword - ("", 6), # Empty string - ("velo", 1), # Accent-insensitive match (velo vs vĂ©lo) - ("bĂȘta", 1), # Accent-insensitive match (bĂȘta vs beta) - ], -) -def test_api_documents_descendants_filter_title(query, nb_results): - """Authenticated users should be able to search documents by their unaccented title.""" - user = factories.UserFactory() - client = APIClient() - client.force_login(user) - - document = factories.DocumentFactory(users=[user]) - - # Create documents with predefined titles - titles = [ - "Project Alpha Documentation", - "Project Beta Overview", - "User Guide", - "Financial Report 2024", - "Annual Review 2024", - "Guide du vĂ©lo urbain", # <-- Title with accent for accent-insensitive test - ] - for title in titles: - factories.DocumentFactory(title=title, parent=document) - - # Perform the search query - response = client.get( - f"/api/v1.0/documents/{document.id!s}/descendants/?title={query:s}" - ) - - assert response.status_code == 200 - results = response.json()["results"] - assert len(results) == nb_results - - # Ensure all results contain the query in their title - for result in results: - assert ( - remove_accents(query).lower().strip() - in remove_accents(result["title"]).lower() - )