🚨(backend) fix list descendants

I am fixing the _list_descendants logic
based on the parent path istead of the
parent id and testing.

Signed-off-by: charles <[email protected]>
This commit is contained in:
charles
2026-02-16 16:43:15 +01:00
parent b96779eb08
commit 1637a74ce9
2 changed files with 146 additions and 60 deletions
+1
View File
@@ -12,6 +12,7 @@ from core.models import DocumentAccess, RoleChoices, get_trashbin_cutoff
ACTION_FOR_METHOD_TO_PERMISSION = {
"versions_detail": {"DELETE": "versions_destroy", "GET": "versions_retrieve"},
"children": {"GET": "children_list", "POST": "children_create"},
"search": {"GET": "retrieve"},
}
@@ -1,5 +1,6 @@
"""
Tests for Documents API endpoint in impress's core app: descendants
Tests for search API endpoint in impress's core app when indexer is not
available and a path param is given.
"""
import random
@@ -14,22 +15,55 @@ from core import factories
pytestmark = pytest.mark.django_db
def test_api_documents_descendants_list_anonymous_public_standalone():
@pytest.fixture(autouse=True)
def disable_indexer(indexer_settings):
"""Disable search indexer for all tests in this file."""
indexer_settings.SEARCH_INDEXER_CLASS = None
def test_api_documents_descendants_list_anonymous_public_standalone(indexer_settings):
"""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)
document = factories.DocumentFactory(link_reach="public", title="doc parent")
child1, child2 = factories.DocumentFactory.create_batch(2, parent=document, title="doc child")
grand_child = factories.DocumentFactory(parent=child1, title="doc grand child")
factories.UserDocumentAccessFactory(document=child1)
response = APIClient().get(f"/api/v1.0/documents/{document.id!s}/descendants/")
response = APIClient().get(
"/api/v1.0/documents/search/",
data={"q": "doc", "path": document.path}
)
assert response.status_code == 200
assert response.json() == {
"count": 3,
"count": 4,
"next": None,
"previous": None,
"results": [
{
# the search should include the parent document itself
"abilities": document.get_abilities(AnonymousUser()),
"ancestors_link_reach": None,
"ancestors_link_role": None,
"computed_link_reach": "public",
"computed_link_role": document.computed_link_role,
"created_at": document.created_at.isoformat().replace("+00:00", "Z"),
"creator": str(document.creator.id),
"deleted_at": None,
"depth": 1,
"excerpt": document.excerpt,
"id": str(document.id),
"is_favorite": False,
"link_reach": document.link_reach,
"link_role": document.link_role,
"numchild": 2,
"nb_accesses_ancestors": 0,
"nb_accesses_direct": 0,
"path": document.path,
"title": document.title,
"updated_at": document.updated_at.isoformat().replace("+00:00", "Z"),
"user_role": None,
},
{
"abilities": child1.get_abilities(AnonymousUser()),
"ancestors_link_reach": "public",
@@ -105,31 +139,58 @@ def test_api_documents_descendants_list_anonymous_public_standalone():
}
def test_api_documents_descendants_list_anonymous_public_parent():
def test_api_documents_descendants_list_anonymous_public_parent(indexer_settings):
"""
Anonymous users should be allowed to retrieve the descendants of a document who
has a public ancestor.
"""
grand_parent = factories.DocumentFactory(link_reach="public")
grand_parent = factories.DocumentFactory(link_reach="public", title="grand parent doc")
parent = factories.DocumentFactory(
parent=grand_parent, link_reach=random.choice(["authenticated", "restricted"])
parent=grand_parent, link_reach=random.choice(["authenticated", "restricted"]), title="parent doc"
)
document = factories.DocumentFactory(
link_reach=random.choice(["authenticated", "restricted"]), parent=parent
link_reach=random.choice(["authenticated", "restricted"]), parent=parent, title="document"
)
child1, child2 = factories.DocumentFactory.create_batch(2, parent=document)
grand_child = factories.DocumentFactory(parent=child1)
child1, child2 = factories.DocumentFactory.create_batch(2, parent=document, title="child doc")
grand_child = factories.DocumentFactory(parent=child1, title="grand child doc")
factories.UserDocumentAccessFactory(document=child1)
response = APIClient().get(f"/api/v1.0/documents/{document.id!s}/descendants/")
response = APIClient().get(
"/api/v1.0/documents/search/",
data={"q": "doc", "path": document.path}
)
assert response.status_code == 200
assert response.json() == {
"count": 3,
"count": 4,
"next": None,
"previous": None,
"results": [
{
# the search should include the parent document itself
"abilities": document.get_abilities(AnonymousUser()),
"ancestors_link_reach": "public",
"ancestors_link_role": grand_parent.link_role,
"computed_link_reach": document.computed_link_reach,
"computed_link_role": document.computed_link_role,
"created_at": document.created_at.isoformat().replace("+00:00", "Z"),
"creator": str(document.creator.id),
"deleted_at": None,
"depth": 3,
"excerpt": document.excerpt,
"id": str(document.id),
"is_favorite": False,
"link_reach": document.link_reach,
"link_role": document.link_role,
"numchild": 2,
"nb_accesses_ancestors": 0,
"nb_accesses_direct": 0,
"path": document.path,
"title": document.title,
"updated_at": document.updated_at.isoformat().replace("+00:00", "Z"),
"user_role": None,
},
{
"abilities": child1.get_abilities(AnonymousUser()),
"ancestors_link_reach": "public",
@@ -204,15 +265,18 @@ def test_api_documents_descendants_list_anonymous_public_parent():
@pytest.mark.parametrize("reach", ["restricted", "authenticated"])
def test_api_documents_descendants_list_anonymous_restricted_or_authenticated(reach):
def test_api_documents_descendants_list_anonymous_restricted_or_authenticated(reach, indexer_settings):
"""
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)
document = factories.DocumentFactory(title="parent", link_reach=reach)
child = factories.DocumentFactory(title="child", parent=document)
_grand_child = factories.DocumentFactory(title="grand child", parent=child)
response = APIClient().get(f"/api/v1.0/documents/{document.id!s}/descendants/")
response = APIClient().get(
"/api/v1.0/documents/search/",
data={"q": "doc", "path": document.path}
)
assert response.status_code == 401
assert response.json() == {
@@ -222,7 +286,7 @@ def test_api_documents_descendants_list_anonymous_restricted_or_authenticated(re
@pytest.mark.parametrize("reach", ["public", "authenticated"])
def test_api_documents_descendants_list_authenticated_unrelated_public_or_authenticated(
reach,
reach, indexer_settings
):
"""
Authenticated users should be able to retrieve the descendants of a public/authenticated
@@ -232,17 +296,19 @@ def test_api_documents_descendants_list_authenticated_unrelated_public_or_authen
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory(link_reach=reach)
document = factories.DocumentFactory(link_reach=reach, title="parent")
child1, child2 = factories.DocumentFactory.create_batch(
2, parent=document, link_reach="restricted"
2, parent=document, link_reach="restricted", title="child"
)
grand_child = factories.DocumentFactory(parent=child1)
grand_child = factories.DocumentFactory(parent=child1, title="grand child")
factories.UserDocumentAccessFactory(document=child1)
response = client.get(
f"/api/v1.0/documents/{document.id!s}/descendants/",
"/api/v1.0/documents/search/",
data={"q": "child", "path": document.path}
)
assert response.status_code == 200
assert response.json() == {
"count": 3,
@@ -324,7 +390,7 @@ def test_api_documents_descendants_list_authenticated_unrelated_public_or_authen
@pytest.mark.parametrize("reach", ["public", "authenticated"])
def test_api_documents_descendants_list_authenticated_public_or_authenticated_parent(
reach,
reach, indexer_settings
):
"""
Authenticated users should be allowed to retrieve the descendants of a document who
@@ -335,17 +401,20 @@ def test_api_documents_descendants_list_authenticated_public_or_authenticated_pa
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)
grand_parent = factories.DocumentFactory(link_reach=reach, title="grand parent")
parent = factories.DocumentFactory(parent=grand_parent, link_reach="restricted", title="parent")
document = factories.DocumentFactory(link_reach="restricted", parent=parent, title="document")
child1, child2 = factories.DocumentFactory.create_batch(
2, parent=document, link_reach="restricted"
2, parent=document, link_reach="restricted", title="child"
)
grand_child = factories.DocumentFactory(parent=child1)
grand_child = factories.DocumentFactory(parent=child1, title="grand child")
factories.UserDocumentAccessFactory(document=child1)
response = client.get(f"/api/v1.0/documents/{document.id!s}/descendants/")
response = client.get(
"/api/v1.0/documents/search/",
data={"q": "child", "path": document.path}
)
assert response.status_code == 200
assert response.json() == {
@@ -426,7 +495,7 @@ def test_api_documents_descendants_list_authenticated_public_or_authenticated_pa
}
def test_api_documents_descendants_list_authenticated_unrelated_restricted():
def test_api_documents_descendants_list_authenticated_unrelated_restricted(indexer_settings):
"""
Authenticated users should not be allowed to retrieve the descendants of a document that is
restricted and to which they are not related.
@@ -435,42 +504,47 @@ def test_api_documents_descendants_list_authenticated_unrelated_restricted():
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)
document = factories.DocumentFactory(link_reach="restricted", title="parent")
child1, _child2 = factories.DocumentFactory.create_batch(2, parent=document, title="child")
_grand_child = factories.DocumentFactory(parent=child1, title="grand child")
factories.UserDocumentAccessFactory(document=child1)
response = client.get(
f"/api/v1.0/documents/{document.id!s}/descendants/",
"/api/v1.0/documents/search/",
data={"q": "child", "path": document.path}
)
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():
def test_api_documents_descendants_list_authenticated_related_direct(indexer_settings):
"""
Authenticated users should be allowed to retrieve the descendants of a document
to which they are directly related whatever the role.
"""
indexer_settings.SEARCH_INDEXER_QUERY_URL = None
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
document = factories.DocumentFactory(title="parent")
access = factories.UserDocumentAccessFactory(document=document, user=user)
factories.UserDocumentAccessFactory(document=document)
child1, child2 = factories.DocumentFactory.create_batch(2, parent=document)
child1, child2 = factories.DocumentFactory.create_batch(2, parent=document, title="child")
factories.UserDocumentAccessFactory(document=child1)
grand_child = factories.DocumentFactory(parent=child1)
grand_child = factories.DocumentFactory(parent=child1, title="grand child")
response = client.get(
f"/api/v1.0/documents/{document.id!s}/descendants/",
"/api/v1.0/documents/search/",
data={"q": "child", "path": document.path}
)
assert response.status_code == 200
assert response.json() == {
@@ -551,7 +625,7 @@ def test_api_documents_descendants_list_authenticated_related_direct():
}
def test_api_documents_descendants_list_authenticated_related_parent():
def test_api_documents_descendants_list_authenticated_related_parent(indexer_settings):
"""
Authenticated users should be allowed to retrieve the descendants of a document if they
are related to one of its ancestors whatever the role.
@@ -561,21 +635,22 @@ def test_api_documents_descendants_list_authenticated_related_parent():
client = APIClient()
client.force_login(user)
grand_parent = factories.DocumentFactory(link_reach="restricted")
grand_parent = factories.DocumentFactory(link_reach="restricted", title="parent")
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")
parent = factories.DocumentFactory(parent=grand_parent, link_reach="restricted", title="parent")
document = factories.DocumentFactory(parent=parent, link_reach="restricted", title="document")
child1, child2 = factories.DocumentFactory.create_batch(2, parent=document)
child1, child2 = factories.DocumentFactory.create_batch(2, parent=document, title="child")
factories.UserDocumentAccessFactory(document=child1)
grand_child = factories.DocumentFactory(parent=child1)
grand_child = factories.DocumentFactory(parent=child1, title="grand child")
response = client.get(
f"/api/v1.0/documents/{document.id!s}/descendants/",
"/api/v1.0/documents/search/",
data={"q": "child", "path": document.path}
)
assert response.status_code == 200
assert response.json() == {
@@ -656,7 +731,7 @@ def test_api_documents_descendants_list_authenticated_related_parent():
}
def test_api_documents_descendants_list_authenticated_related_child():
def test_api_documents_descendants_list_authenticated_related_child(indexer_settings):
"""
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.
@@ -673,7 +748,8 @@ def test_api_documents_descendants_list_authenticated_related_child():
factories.UserDocumentAccessFactory(document=document)
response = client.get(
f"/api/v1.0/documents/{document.id!s}/descendants/",
"/api/v1.0/documents/search/",
data={"q": "doc", "path": document.path}
)
assert response.status_code == 403
assert response.json() == {
@@ -682,24 +758,30 @@ def test_api_documents_descendants_list_authenticated_related_child():
def test_api_documents_descendants_list_authenticated_related_team_none(
mock_user_teams,
mock_user_teams, indexer_settings
):
"""
Authenticated users should not be able to retrieve the descendants of a restricted document
related to teams in which the user is not.
"""
indexer_settings.SEARCH_INDEXER_QUERY_URL = None
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)
document = factories.DocumentFactory(link_reach="restricted", title="document")
factories.DocumentFactory.create_batch(2, parent=document, title="child")
factories.TeamDocumentAccessFactory(document=document, team="myteam")
response = client.get(f"/api/v1.0/documents/{document.id!s}/descendants/")
response = client.get(
"/api/v1.0/documents/search/",
data={"q": "doc", "path": document.path}
)
assert response.status_code == 403
assert response.json() == {
"detail": "You do not have permission to perform this action."
@@ -707,7 +789,7 @@ def test_api_documents_descendants_list_authenticated_related_team_none(
def test_api_documents_descendants_list_authenticated_related_team_members(
mock_user_teams,
mock_user_teams, indexer_settings
):
"""
Authenticated users should be allowed to retrieve the descendants of a document to which they
@@ -719,13 +801,16 @@ def test_api_documents_descendants_list_authenticated_related_team_members(
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)
document = factories.DocumentFactory(link_reach="restricted", title="parent")
child1, child2 = factories.DocumentFactory.create_batch(2, parent=document, title="child")
grand_child = factories.DocumentFactory(parent=child1, title="grand child")
access = factories.TeamDocumentAccessFactory(document=document, team="myteam")
response = client.get(f"/api/v1.0/documents/{document.id!s}/descendants/")
response = client.get(
"/api/v1.0/documents/search/",
data={"q": "child", "path": document.path}
)
# pylint: disable=R0801
assert response.status_code == 200