From 9a12452c26dc993ff151cd4ca1895dc3d3a98070 Mon Sep 17 00:00:00 2001 From: Samuel Paccoud - DINUM Date: Mon, 16 Dec 2024 20:13:12 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=9A(backend)=20split=20test=20files=20?= =?UTF-8?q?to=20make=20place=20for=20tests=20on=20tree=20structure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The test_api_documents_list file was getting too long. We can extract tests on filters and ordering. --- .../documents/test_api_documents_list.py | 361 ----------------- .../test_api_documents_list_filters.py | 374 ++++++++++++++++++ 2 files changed, 374 insertions(+), 361 deletions(-) create mode 100644 src/backend/core/tests/documents/test_api_documents_list_filters.py diff --git a/src/backend/core/tests/documents/test_api_documents_list.py b/src/backend/core/tests/documents/test_api_documents_list.py index 8793b34f..727ae266 100644 --- a/src/backend/core/tests/documents/test_api_documents_list.py +++ b/src/backend/core/tests/documents/test_api_documents_list.py @@ -2,10 +2,7 @@ Tests for Documents API endpoint in impress's core app: list """ -import operator -import random from unittest import mock -from urllib.parse import urlencode import pytest from faker import Faker @@ -314,361 +311,3 @@ def test_api_documents_list_favorites_no_extra_queries(django_assert_num_queries assert result["is_favorite"] is True else: assert result["is_favorite"] is False - - -def test_api_documents_list_filter_and_access_rights(): - """Filtering on querystring parameters should respect access rights.""" - user = factories.UserFactory() - client = APIClient() - client.force_login(user) - - other_user = factories.UserFactory() - - def random_favorited_by(): - return random.choice([[], [user], [other_user]]) - - # Documents that should be listed to this user - listed_documents = [ - factories.DocumentFactory( - link_reach="public", - link_traces=[user], - favorited_by=random_favorited_by(), - creator=random.choice([user, other_user]), - ), - factories.DocumentFactory( - link_reach="authenticated", - link_traces=[user], - favorited_by=random_favorited_by(), - creator=random.choice([user, other_user]), - ), - factories.DocumentFactory( - link_reach="restricted", - users=[user], - favorited_by=random_favorited_by(), - creator=random.choice([user, other_user]), - ), - ] - listed_ids = [str(doc.id) for doc in listed_documents] - word_list = [word for doc in listed_documents for word in doc.title.split(" ")] - - # Documents that should not be listed to this user - factories.DocumentFactory( - link_reach="public", - favorited_by=random_favorited_by(), - creator=random.choice([user, other_user]), - ) - factories.DocumentFactory( - link_reach="authenticated", - favorited_by=random_favorited_by(), - creator=random.choice([user, other_user]), - ) - factories.DocumentFactory( - link_reach="restricted", - favorited_by=random_favorited_by(), - creator=random.choice([user, other_user]), - ) - factories.DocumentFactory( - link_reach="restricted", - link_traces=[user], - favorited_by=random_favorited_by(), - creator=random.choice([user, other_user]), - ) - - filters = { - "link_reach": random.choice([None, *models.LinkReachChoices.values]), - "title": random.choice([None, *word_list]), - "favorite": random.choice([None, True, False]), - "creator": random.choice([None, user, other_user]), - "ordering": random.choice( - [ - None, - "created_at", - "-created_at", - "is_favorite", - "-is_favorite", - "nb_accesses", - "-nb_accesses", - "title", - "-title", - "updated_at", - "-updated_at", - ] - ), - } - query_params = {key: value for key, value in filters.items() if value is not None} - querystring = urlencode(query_params) - - response = client.get(f"/api/v1.0/documents/?{querystring:s}") - - assert response.status_code == 200 - results = response.json()["results"] - - # Ensure all documents in results respect expected access rights - for result in results: - assert result["id"] in listed_ids - - -# Filters: ordering - - -def test_api_documents_list_ordering_default(): - """Documents should be ordered by descending "updated_at" by default""" - user = factories.UserFactory() - client = APIClient() - client.force_login(user) - - factories.DocumentFactory.create_batch(5, users=[user]) - - response = client.get("/api/v1.0/documents/") - - assert response.status_code == 200 - results = response.json()["results"] - assert len(results) == 5 - - # Check that results are sorted by descending "updated_at" as expected - for i in range(4): - assert operator.ge(results[i]["updated_at"], results[i + 1]["updated_at"]) - - -def test_api_documents_list_ordering_by_fields(): - """It should be possible to order by several fields""" - user = factories.UserFactory() - client = APIClient() - client.force_login(user) - - factories.DocumentFactory.create_batch(5, users=[user]) - - for parameter in [ - "created_at", - "-created_at", - "is_favorite", - "-is_favorite", - "nb_accesses", - "-nb_accesses", - "title", - "-title", - "updated_at", - "-updated_at", - ]: - is_descending = parameter.startswith("-") - field = parameter.lstrip("-") - querystring = f"?ordering={parameter}" - - response = client.get(f"/api/v1.0/documents/{querystring:s}") - assert response.status_code == 200 - results = response.json()["results"] - assert len(results) == 5 - - # Check that results are sorted by the field in querystring as expected - compare = operator.ge if is_descending else operator.le - for i in range(4): - assert compare(results[i][field], results[i + 1][field]) - - -# Filters: is_creator_me - - -def test_api_documents_list_filter_is_creator_me_true(): - """ - Authenticated users should be able to filter documents they created. - """ - user = factories.UserFactory() - client = APIClient() - client.force_login(user) - - factories.DocumentFactory.create_batch(3, users=[user], creator=user) - factories.DocumentFactory.create_batch(2, users=[user]) - - response = client.get("/api/v1.0/documents/?is_creator_me=true") - - assert response.status_code == 200 - results = response.json()["results"] - assert len(results) == 3 - - # Ensure all results are created by the current user - for result in results: - assert result["creator"] == str(user.id) - - -def test_api_documents_list_filter_is_creator_me_false(): - """ - Authenticated users should be able to filter documents created by others. - """ - user = factories.UserFactory() - client = APIClient() - client.force_login(user) - - factories.DocumentFactory.create_batch(3, users=[user], creator=user) - factories.DocumentFactory.create_batch(2, users=[user]) - - response = client.get("/api/v1.0/documents/?is_creator_me=false") - - assert response.status_code == 200 - results = response.json()["results"] - assert len(results) == 2 - - # Ensure all results are created by other users - for result in results: - assert result["creator"] != str(user.id) - - -def test_api_documents_list_filter_is_creator_me_invalid(): - """Filtering with an invalid `is_creator_me` value should do nothing.""" - user = factories.UserFactory() - client = APIClient() - client.force_login(user) - - factories.DocumentFactory.create_batch(3, users=[user], creator=user) - factories.DocumentFactory.create_batch(2, users=[user]) - - response = client.get("/api/v1.0/documents/?is_creator_me=invalid") - - assert response.status_code == 200 - results = response.json()["results"] - assert len(results) == 5 - - -# Filters: is_favorite - - -def test_api_documents_list_filter_is_favorite_true(): - """ - Authenticated users should be able to filter documents they marked as favorite. - """ - user = factories.UserFactory() - client = APIClient() - client.force_login(user) - - factories.DocumentFactory.create_batch(3, users=[user], favorited_by=[user]) - factories.DocumentFactory.create_batch(2, users=[user]) - - response = client.get("/api/v1.0/documents/?is_favorite=true") - - assert response.status_code == 200 - results = response.json()["results"] - assert len(results) == 3 - - # Ensure all results are marked as favorite by the current user - for result in results: - assert result["is_favorite"] is True - - -def test_api_documents_list_filter_is_favorite_false(): - """ - Authenticated users should be able to filter documents they didn't mark as favorite. - """ - user = factories.UserFactory() - client = APIClient() - client.force_login(user) - - factories.DocumentFactory.create_batch(3, users=[user], favorited_by=[user]) - factories.DocumentFactory.create_batch(2, users=[user]) - - response = client.get("/api/v1.0/documents/?is_favorite=false") - - assert response.status_code == 200 - results = response.json()["results"] - assert len(results) == 2 - - # Ensure all results are not marked as favorite by the current user - for result in results: - assert result["is_favorite"] is False - - -def test_api_documents_list_filter_is_favorite_invalid(): - """Filtering with an invalid `is_favorite` value should do nothing.""" - user = factories.UserFactory() - client = APIClient() - client.force_login(user) - - factories.DocumentFactory.create_batch(3, users=[user], favorited_by=[user]) - factories.DocumentFactory.create_batch(2, users=[user]) - - response = client.get("/api/v1.0/documents/?is_favorite=invalid") - - assert response.status_code == 200 - results = response.json()["results"] - assert len(results) == 5 - - -# Filters: link_reach - - -@pytest.mark.parametrize("reach", models.LinkReachChoices.values) -def test_api_documents_list_filter_link_reach(reach): - """Authenticated users should be able to filter documents by link reach.""" - user = factories.UserFactory() - client = APIClient() - client.force_login(user) - - factories.DocumentFactory.create_batch(5, users=[user]) - - response = client.get(f"/api/v1.0/documents/?link_reach={reach:s}") - - assert response.status_code == 200 - results = response.json()["results"] - - # Ensure all results have the chosen link reach - for result in results: - assert result["link_reach"] == reach - - -def test_api_documents_list_filter_link_reach_invalid(): - """Filtering with an invalid `link_reach` value should raise an error.""" - user = factories.UserFactory() - client = APIClient() - client.force_login(user) - - factories.DocumentFactory.create_batch(3, users=[user]) - - response = client.get("/api/v1.0/documents/?link_reach=invalid") - - assert response.status_code == 400 - assert response.json() == { - "link_reach": [ - "Select a valid choice. invalid is not one of the available choices." - ] - } - - -# Filters: title - - -@pytest.mark.parametrize( - "query,nb_results", - [ - ("Project Alpha", 1), # Exact match - ("project", 2), # Partial match (case-insensitive) - ("Guide", 1), # Word match within a title - ("Special", 0), # No match (nonexistent keyword) - ("2024", 2), # Match by numeric keyword - ("", 5), # Empty string - ], -) -def test_api_documents_list_filter_title(query, nb_results): - """Authenticated users should be able to search documents by their title.""" - user = factories.UserFactory() - client = APIClient() - client.force_login(user) - - # Create documents with predefined titles - titles = [ - "Project Alpha Documentation", - "Project Beta Overview", - "User Guide", - "Financial Report 2024", - "Annual Review 2024", - ] - for title in titles: - factories.DocumentFactory(title=title, users=[user]) - - # Perform the search query - response = client.get(f"/api/v1.0/documents/?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 query.lower().strip() in result["title"].lower() 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 new file mode 100644 index 00000000..659c3f08 --- /dev/null +++ b/src/backend/core/tests/documents/test_api_documents_list_filters.py @@ -0,0 +1,374 @@ +""" +Tests for Documents API endpoint in impress's core app: list +""" + +import operator +import random +from urllib.parse import urlencode + +import pytest +from faker import Faker +from rest_framework.test import APIClient + +from core import factories, models + +fake = Faker() +pytestmark = pytest.mark.django_db + + +def test_api_documents_list_filter_and_access_rights(): + """Filtering on querystring parameters should respect access rights.""" + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + other_user = factories.UserFactory() + + def random_favorited_by(): + return random.choice([[], [user], [other_user]]) + + # Documents that should be listed to this user + listed_documents = [ + factories.DocumentFactory( + link_reach="public", + link_traces=[user], + favorited_by=random_favorited_by(), + creator=random.choice([user, other_user]), + ), + factories.DocumentFactory( + link_reach="authenticated", + link_traces=[user], + favorited_by=random_favorited_by(), + creator=random.choice([user, other_user]), + ), + factories.DocumentFactory( + link_reach="restricted", + users=[user], + favorited_by=random_favorited_by(), + creator=random.choice([user, other_user]), + ), + ] + listed_ids = [str(doc.id) for doc in listed_documents] + word_list = [word for doc in listed_documents for word in doc.title.split(" ")] + + # Documents that should not be listed to this user + factories.DocumentFactory( + link_reach="public", + favorited_by=random_favorited_by(), + creator=random.choice([user, other_user]), + ) + factories.DocumentFactory( + link_reach="authenticated", + favorited_by=random_favorited_by(), + creator=random.choice([user, other_user]), + ) + factories.DocumentFactory( + link_reach="restricted", + favorited_by=random_favorited_by(), + creator=random.choice([user, other_user]), + ) + factories.DocumentFactory( + link_reach="restricted", + link_traces=[user], + favorited_by=random_favorited_by(), + creator=random.choice([user, other_user]), + ) + + filters = { + "link_reach": random.choice([None, *models.LinkReachChoices.values]), + "title": random.choice([None, *word_list]), + "favorite": random.choice([None, True, False]), + "creator": random.choice([None, user, other_user]), + "ordering": random.choice( + [ + None, + "created_at", + "-created_at", + "is_favorite", + "-is_favorite", + "nb_accesses", + "-nb_accesses", + "title", + "-title", + "updated_at", + "-updated_at", + ] + ), + } + query_params = {key: value for key, value in filters.items() if value is not None} + querystring = urlencode(query_params) + + response = client.get(f"/api/v1.0/documents/?{querystring:s}") + + assert response.status_code == 200 + results = response.json()["results"] + + # Ensure all documents in results respect expected access rights + for result in results: + assert result["id"] in listed_ids + + +# Filters: ordering + + +def test_api_documents_list_ordering_default(): + """Documents should be ordered by descending "updated_at" by default""" + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + factories.DocumentFactory.create_batch(5, users=[user]) + + response = client.get("/api/v1.0/documents/") + + assert response.status_code == 200 + results = response.json()["results"] + assert len(results) == 5 + + # Check that results are sorted by descending "updated_at" as expected + for i in range(4): + assert operator.ge(results[i]["updated_at"], results[i + 1]["updated_at"]) + + +def test_api_documents_list_ordering_by_fields(): + """It should be possible to order by several fields""" + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + factories.DocumentFactory.create_batch(5, users=[user]) + + for parameter in [ + "created_at", + "-created_at", + "is_favorite", + "-is_favorite", + "nb_accesses", + "-nb_accesses", + "title", + "-title", + "updated_at", + "-updated_at", + ]: + is_descending = parameter.startswith("-") + field = parameter.lstrip("-") + querystring = f"?ordering={parameter}" + + response = client.get(f"/api/v1.0/documents/{querystring:s}") + assert response.status_code == 200 + results = response.json()["results"] + assert len(results) == 5 + + # Check that results are sorted by the field in querystring as expected + compare = operator.ge if is_descending else operator.le + for i in range(4): + assert compare(results[i][field], results[i + 1][field]) + + +# Filters: is_creator_me + + +def test_api_documents_list_filter_is_creator_me_true(): + """ + Authenticated users should be able to filter documents they created. + """ + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + factories.DocumentFactory.create_batch(3, users=[user], creator=user) + factories.DocumentFactory.create_batch(2, users=[user]) + + response = client.get("/api/v1.0/documents/?is_creator_me=true") + + assert response.status_code == 200 + results = response.json()["results"] + assert len(results) == 3 + + # Ensure all results are created by the current user + for result in results: + assert result["creator"] == str(user.id) + + +def test_api_documents_list_filter_is_creator_me_false(): + """ + Authenticated users should be able to filter documents created by others. + """ + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + factories.DocumentFactory.create_batch(3, users=[user], creator=user) + factories.DocumentFactory.create_batch(2, users=[user]) + + response = client.get("/api/v1.0/documents/?is_creator_me=false") + + assert response.status_code == 200 + results = response.json()["results"] + assert len(results) == 2 + + # Ensure all results are created by other users + for result in results: + assert result["creator"] != str(user.id) + + +def test_api_documents_list_filter_is_creator_me_invalid(): + """Filtering with an invalid `is_creator_me` value should do nothing.""" + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + factories.DocumentFactory.create_batch(3, users=[user], creator=user) + factories.DocumentFactory.create_batch(2, users=[user]) + + response = client.get("/api/v1.0/documents/?is_creator_me=invalid") + + assert response.status_code == 200 + results = response.json()["results"] + assert len(results) == 5 + + +# Filters: is_favorite + + +def test_api_documents_list_filter_is_favorite_true(): + """ + Authenticated users should be able to filter documents they marked as favorite. + """ + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + factories.DocumentFactory.create_batch(3, users=[user], favorited_by=[user]) + factories.DocumentFactory.create_batch(2, users=[user]) + + response = client.get("/api/v1.0/documents/?is_favorite=true") + + assert response.status_code == 200 + results = response.json()["results"] + assert len(results) == 3 + + # Ensure all results are marked as favorite by the current user + for result in results: + assert result["is_favorite"] is True + + +def test_api_documents_list_filter_is_favorite_false(): + """ + Authenticated users should be able to filter documents they didn't mark as favorite. + """ + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + factories.DocumentFactory.create_batch(3, users=[user], favorited_by=[user]) + factories.DocumentFactory.create_batch(2, users=[user]) + + response = client.get("/api/v1.0/documents/?is_favorite=false") + + assert response.status_code == 200 + results = response.json()["results"] + assert len(results) == 2 + + # Ensure all results are not marked as favorite by the current user + for result in results: + assert result["is_favorite"] is False + + +def test_api_documents_list_filter_is_favorite_invalid(): + """Filtering with an invalid `is_favorite` value should do nothing.""" + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + factories.DocumentFactory.create_batch(3, users=[user], favorited_by=[user]) + factories.DocumentFactory.create_batch(2, users=[user]) + + response = client.get("/api/v1.0/documents/?is_favorite=invalid") + + assert response.status_code == 200 + results = response.json()["results"] + assert len(results) == 5 + + +# Filters: link_reach + + +@pytest.mark.parametrize("reach", models.LinkReachChoices.values) +def test_api_documents_list_filter_link_reach(reach): + """Authenticated users should be able to filter documents by link reach.""" + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + factories.DocumentFactory.create_batch(5, users=[user]) + + response = client.get(f"/api/v1.0/documents/?link_reach={reach:s}") + + assert response.status_code == 200 + results = response.json()["results"] + + # Ensure all results have the chosen link reach + for result in results: + assert result["link_reach"] == reach + + +def test_api_documents_list_filter_link_reach_invalid(): + """Filtering with an invalid `link_reach` value should raise an error.""" + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + factories.DocumentFactory.create_batch(3, users=[user]) + + response = client.get("/api/v1.0/documents/?link_reach=invalid") + + assert response.status_code == 400 + assert response.json() == { + "link_reach": [ + "Select a valid choice. invalid is not one of the available choices." + ] + } + + +# Filters: title + + +@pytest.mark.parametrize( + "query,nb_results", + [ + ("Project Alpha", 1), # Exact match + ("project", 2), # Partial match (case-insensitive) + ("Guide", 1), # Word match within a title + ("Special", 0), # No match (nonexistent keyword) + ("2024", 2), # Match by numeric keyword + ("", 5), # Empty string + ], +) +def test_api_documents_list_filter_title(query, nb_results): + """Authenticated users should be able to search documents by their title.""" + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + # Create documents with predefined titles + titles = [ + "Project Alpha Documentation", + "Project Beta Overview", + "User Guide", + "Financial Report 2024", + "Annual Review 2024", + ] + for title in titles: + factories.DocumentFactory(title=title, users=[user]) + + # Perform the search query + response = client.get(f"/api/v1.0/documents/?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 query.lower().strip() in result["title"].lower()