From 4cc704770c4a7fc7aea9fd94e914dd2f2b2f6347 Mon Sep 17 00:00:00 2001 From: Thomas Steen Rasmussen Date: Tue, 21 Jan 2025 11:40:22 +0100 Subject: [PATCH] split unit tests into seperate files --- .pre-commit-config.yaml | 2 +- pyproject.toml | 3 +- src/albums/tests/__init__.py | 1 + src/albums/{ => tests}/test_api.py | 0 src/albums/{ => tests}/test_views.py | 0 src/files/tests/__init__.py | 1 + src/files/tests/test_admin.py | 153 +++++++ src/files/{tests.py => tests/test_api.py} | 485 +--------------------- src/files/tests/test_views.py | 314 ++++++++++++++ src/pictures/tests/test_utils.py | 38 +- src/tags/tests/__init__.py | 1 + src/tags/{tests.py => tests/test_api.py} | 0 12 files changed, 505 insertions(+), 493 deletions(-) create mode 100644 src/albums/tests/__init__.py rename src/albums/{ => tests}/test_api.py (100%) rename src/albums/{ => tests}/test_views.py (100%) create mode 100644 src/files/tests/__init__.py create mode 100644 src/files/tests/test_admin.py rename src/files/{tests.py => tests/test_api.py} (58%) create mode 100644 src/files/tests/test_views.py create mode 100644 src/tags/tests/__init__.py rename src/tags/{tests.py => tests/test_api.py} (100%) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d86fed0f..41642fe6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: rev: 'v1.11.2' hooks: - id: "mypy" - exclude: "src/.*/tests.py|src/.*/migrations/.*.py|src/.*/tests/.*" + exclude: "src/.*/migrations/.*.py" additional_dependencies: - "django-stubs[compatible-mypy]" - "django-stubs-ext" diff --git a/pyproject.toml b/pyproject.toml index 1fc8f1e4..aace77f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -122,7 +122,7 @@ convention = "google" ########## PYTEST ########## [tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "bma.settings" -python_files = ["tests.py", "test_*.py"] +python_files = ["tests/test_*.py"] log_cli = 1 log_cli_level = "DEBUG" filterwarnings = [ @@ -169,6 +169,7 @@ commands = [["pytest"]] [tool.coverage.run] omit = [ "*/tests.py", + "*/tests/test_*.py", ] [tool.coverage.report] exclude_also = [ diff --git a/src/albums/tests/__init__.py b/src/albums/tests/__init__.py new file mode 100644 index 00000000..5e4e298a --- /dev/null +++ b/src/albums/tests/__init__.py @@ -0,0 +1 @@ +"""Unit tests for the albums app.""" diff --git a/src/albums/test_api.py b/src/albums/tests/test_api.py similarity index 100% rename from src/albums/test_api.py rename to src/albums/tests/test_api.py diff --git a/src/albums/test_views.py b/src/albums/tests/test_views.py similarity index 100% rename from src/albums/test_views.py rename to src/albums/tests/test_views.py diff --git a/src/files/tests/__init__.py b/src/files/tests/__init__.py new file mode 100644 index 00000000..cd1b37b5 --- /dev/null +++ b/src/files/tests/__init__.py @@ -0,0 +1 @@ +"""Unit tests for the files app.""" diff --git a/src/files/tests/test_admin.py b/src/files/tests/test_admin.py new file mode 100644 index 00000000..9c37e9fe --- /dev/null +++ b/src/files/tests/test_admin.py @@ -0,0 +1,153 @@ +"""Tests for the file HTML views.""" + +from django.urls import reverse + +from utils.tests import BmaTestBase + + +class TestFileAdmin(BmaTestBase): + """Tests for the FileAdmin.""" + + def test_file_list_status_code(self) -> None: + """Test the access controls for the list page in the FileAdmin.""" + url = reverse("file_admin:files_basefile_changelist") + # try accessing the file_admin without a login + response = self.client.get(url) + self.assertEqual(response.status_code, 302) + # try accessing the file_admin with a user without permissions for it + self.client.login(username="user0", password="secret") + response = self.client.get(url) + self.assertEqual(response.status_code, 302) + # try accessing the file_admin with a user with is_creator=True + self.client.login(username="creator2", password="secret") + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + # try accessing the file_admin with a user with is_moderator=True + self.client.login(username="moderator4", password="secret") + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + # try accessing the file_admin with a user with is_curator=True + self.client.login(username="curator6", password="secret") + response = self.client.get(url) + self.assertEqual(response.status_code, 302) + + def test_file_list(self) -> None: + """Test the file list page in the FileAdmin.""" + # upload some files + self.files = [self.file_upload() for _ in range(10)] + for _ in range(10): + self.files.append(self.file_upload(uploader="creator3")) + + # the superuser can see all files + url = reverse("file_admin:files_basefile_changelist") + self.client.login(username="superuser", password="secret") + response = self.client.get(url) + self.assertInHTML( + '

20 files

', response.content.decode(), msg_prefix="superuser can not see 20 files" + ) + + # each creator can see 10 files + for c in ["creator2", "creator3"]: + self.client.login(username=c, password="secret") + response = self.client.get(url) + self.assertInHTML( + '

10 files

', + response.content.decode(), + msg_prefix=f"creator {c} can not see 10 files", + ) + + # each moderator can see all 20 files + for m in ["moderator4", "moderator5"]: + self.client.login(username=m, password="secret") + response = self.client.get(url) + self.assertInHTML( + '

20 files

', + response.content.decode(), + msg_prefix=f"moderator {m} can not see 20 files", + ) + + # make moderator4 approve 5 of the files owned by creator2 + data = {"action": "approve", "_selected_action": self.files[:5]} + self.client.login(username="moderator4", password="secret") + response = self.client.post(url, data, follow=True) + self.assertEqual(response.status_code, 200) + self.assertInHTML( + '

20 files

', + response.content.decode(), + msg_prefix=f"moderator {m} can not see 20 files", + ) + + # test filtering to see only approved files + response = self.client.get(url + "?approved__exact=1") + self.assertInHTML( + '

5 files

', response.content.decode(), msg_prefix="can not see 5 approved files" + ) + + # each creator can still see 10 files + for c in ["creator2", "creator3"]: + self.client.login(username=c, password="secret") + response = self.client.get(url) + self.assertInHTML( + '

10 files

', + response.content.decode(), + msg_prefix=f"creator {c} can not see 10 files", + ) + + # make creator2 publish the 5 approved files + data = {"action": "publish", "_selected_action": self.files[:5]} + self.client.login(username="creator2", password="secret") + response = self.client.post(url, data, follow=True) + self.assertEqual(response.status_code, 200) + self.assertInHTML( + '

10 files

', response.content.decode(), msg_prefix="creator2 can not see 10 files" + ) + response = self.client.get(url + "?published__exact=1") + self.assertInHTML( + '

5 files

', response.content.decode(), msg_prefix="can not see 5 published files" + ) + + # make creator2 unpublish the 5 approved files + data = {"action": "unpublish", "_selected_action": self.files[:5]} + response = self.client.post(url, data, follow=True) + self.assertEqual(response.status_code, 200) + self.assertInHTML( + "5 files selected to be unpublished, " + "out of those 5 files had needed permission, " + "and out of those 5 files were successfully unpublished", + response.content.decode(), + msg_prefix="unpublished message not found", + ) + self.assertInHTML( + '

10 files

', response.content.decode(), msg_prefix="creator2 can not see 10 files" + ) + response = self.client.get(url + "?published__exact=0") + self.assertInHTML( + '

10 files

', + response.content.decode(), + msg_prefix="creator2 can not see 10 unpublished files after unpublishing", + ) + + # make moderator4 unapprove 5 of the files owned by creator2 + data = {"action": "unapprove", "_selected_action": self.files[:5]} + self.client.login(username="moderator4", password="secret") + response = self.client.post(url, data, follow=True) + self.assertEqual(response.status_code, 200) + response = self.client.get(url + "?approved__exact=0") + self.assertInHTML( + '

20 files

', + response.content.decode(), + msg_prefix=f"moderator {m} can not see 20 files pending moderation", + ) + + # make creator2 softdelete the 5 approved and pubhlished files + data = {"action": "softdelete", "_selected_action": self.files[:5]} + self.client.login(username="creator2", password="secret") + response = self.client.post(url, data, follow=True) + self.assertEqual(response.status_code, 200) + self.assertInHTML( + '

10 files

', response.content.decode(), msg_prefix="creator2 can not see 10 files" + ) + response = self.client.get(url + "?deleted__exact=1") + self.assertInHTML( + '

5 files

', response.content.decode(), msg_prefix="can not see 5 deleted files" + ) diff --git a/src/files/tests.py b/src/files/tests/test_api.py similarity index 58% rename from src/files/tests.py rename to src/files/tests/test_api.py index ed76906b..0f250426 100644 --- a/src/files/tests.py +++ b/src/files/tests/test_api.py @@ -1,15 +1,13 @@ -"""Tests for the files API, admin and HTML views.""" +"""Tests for the files API.""" from pathlib import Path -from bs4 import BeautifulSoup from django.conf import settings from django.urls import reverse +from files.models import BaseFile from utils.tests import BmaTestBase -from .models import BaseFile - class TestFilesApi(BmaTestBase): """Test for methods in the files API.""" @@ -47,8 +45,8 @@ def test_file_upload(self) -> None: def test_file_list(self) -> None: # noqa: PLR0915 """Test the file_list endpoint.""" files = [self.file_upload(title=f"title{i}") for i in range(15)] - [ - files.append(self.file_upload(title=f"title{i}", description="tag test", tags=["starttag", f"tag{i}"])) + files = files + [ + self.file_upload(title=f"title{i}", description="tag test", tags=["starttag", f"tag{i}"]) for i in range(15, 20) ] response = self.client.get( @@ -60,7 +58,7 @@ def test_file_list(self) -> None: # noqa: PLR0915 # test sorting response = self.client.get( reverse("api-v1-json:file_list"), - data={"limit": 5, "sorting": "title_asc"}, + data={"limit": "5", "sorting": "title_asc"}, headers={"authorization": self.tokens[self.creator2]}, ) assert len(response.json()["bma_response"]) == 5 @@ -70,7 +68,7 @@ def test_file_list(self) -> None: # noqa: PLR0915 assert response.json()["bma_response"][4]["title"] == "title12" response = self.client.get( reverse("api-v1-json:file_list"), - data={"limit": 1, "sorting": "created_at_desc"}, + data={"limit": "1", "sorting": "created_at_desc"}, headers={"authorization": self.tokens[self.creator2]}, ) assert response.json()["bma_response"][0]["title"] == "title19" @@ -78,7 +76,7 @@ def test_file_list(self) -> None: # noqa: PLR0915 # test offset response = self.client.get( reverse("api-v1-json:file_list"), - data={"offset": 5, "sorting": "created_at_asc"}, + data={"offset": "5", "sorting": "created_at_asc"}, headers={"authorization": self.tokens[self.creator2]}, ) assert response.json()["bma_response"][0]["title"] == "title5" @@ -87,13 +85,13 @@ def test_file_list(self) -> None: # noqa: PLR0915 # test uploader filter response = self.client.get( reverse("api-v1-json:file_list"), - data={"uploaders": [self.creator2.uuid, self.user0.uuid]}, + data={"uploaders": [str(self.creator2.uuid), str(self.user0.uuid)]}, headers={"authorization": self.tokens[self.creator2]}, ) assert len(response.json()["bma_response"]) == 20 response = self.client.get( reverse("api-v1-json:file_list"), - data={"uploaders": [self.user0.uuid]}, + data={"uploaders": [str(self.user0.uuid)]}, headers={"authorization": self.tokens[self.creator2]}, ) assert len(response.json()["bma_response"]) == 0 @@ -302,14 +300,14 @@ def test_file_list(self) -> None: # noqa: PLR0915 # test taggers filter response = self.client.get( reverse("api-v1-json:file_list"), - data={"taggers": [self.creator2.uuid]}, + data={"taggers": [str(self.creator2.uuid)]}, headers={"authorization": self.tokens[self.creator2]}, ) assert len(response.json()["bma_response"]) == 10 response = self.client.get( reverse("api-v1-json:file_list"), - data={"taggers": [self.creator2.uuid, self.curator6.uuid]}, + data={"taggers": [str(self.creator2.uuid), str(self.curator6.uuid)]}, headers={"authorization": self.tokens[self.creator2]}, ) assert len(response.json()["bma_response"]) == 3 @@ -356,7 +354,7 @@ def test_file_list(self) -> None: # noqa: PLR0915 # curator6 still tagged 8 different files response = self.client.get( reverse("api-v1-json:file_list"), - data={"taggers": [self.curator6.uuid]}, + data={"taggers": [str(self.curator6.uuid)]}, headers={"authorization": self.tokens[self.creator2]}, ) assert len(response.json()["bma_response"]) == 8 @@ -375,7 +373,7 @@ def test_file_list(self) -> None: # noqa: PLR0915 # curator6 now only tagged 7 files response = self.client.get( reverse("api-v1-json:file_list"), - data={"taggers": [self.curator6.uuid]}, + data={"taggers": [str(self.curator6.uuid)]}, headers={"authorization": self.tokens[self.creator2]}, ) assert len(response.json()["bma_response"]) == 7 @@ -800,460 +798,3 @@ def test_file_missing_on_disk(self) -> None: ) self.assertEqual(response.status_code, 200) self.assertEqual(response.json()["bma_response"]["size_bytes"], 0) - - -class TestFileAdmin(BmaTestBase): - """Tests for the FileAdmin.""" - - def test_file_list_status_code(self) -> None: - """Test the access controls for the list page in the FileAdmin.""" - url = reverse("file_admin:files_basefile_changelist") - # try accessing the file_admin without a login - response = self.client.get(url) - self.assertEqual(response.status_code, 302) - # try accessing the file_admin with a user without permissions for it - self.client.login(username="user0", password="secret") - response = self.client.get(url) - self.assertEqual(response.status_code, 302) - # try accessing the file_admin with a user with is_creator=True - self.client.login(username="creator2", password="secret") - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - # try accessing the file_admin with a user with is_moderator=True - self.client.login(username="moderator4", password="secret") - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - # try accessing the file_admin with a user with is_curator=True - self.client.login(username="curator6", password="secret") - response = self.client.get(url) - self.assertEqual(response.status_code, 302) - - def test_file_list(self) -> None: - """Test the file list page in the FileAdmin.""" - # upload some files - self.files = [self.file_upload() for _ in range(10)] - for _ in range(10): - self.files.append(self.file_upload(uploader="creator3")) - - # the superuser can see all files - url = reverse("file_admin:files_basefile_changelist") - self.client.login(username="superuser", password="secret") - response = self.client.get(url) - self.assertInHTML( - '

20 files

', response.content.decode(), msg_prefix="superuser can not see 20 files" - ) - - # each creator can see 10 files - for c in ["creator2", "creator3"]: - self.client.login(username=c, password="secret") - response = self.client.get(url) - self.assertInHTML( - '

10 files

', - response.content.decode(), - msg_prefix=f"creator {c} can not see 10 files", - ) - - # each moderator can see all 20 files - for m in ["moderator4", "moderator5"]: - self.client.login(username=m, password="secret") - response = self.client.get(url) - self.assertInHTML( - '

20 files

', - response.content.decode(), - msg_prefix=f"moderator {m} can not see 20 files", - ) - - # make moderator4 approve 5 of the files owned by creator2 - data = {"action": "approve", "_selected_action": self.files[:5]} - self.client.login(username="moderator4", password="secret") - response = self.client.post(url, data, follow=True) - self.assertEqual(response.status_code, 200) - self.assertInHTML( - '

20 files

', - response.content.decode(), - msg_prefix=f"moderator {m} can not see 20 files", - ) - - # test filtering to see only approved files - response = self.client.get(url + "?approved__exact=1") - self.assertInHTML( - '

5 files

', response.content.decode(), msg_prefix="can not see 5 approved files" - ) - - # each creator can still see 10 files - for c in ["creator2", "creator3"]: - self.client.login(username=c, password="secret") - response = self.client.get(url) - self.assertInHTML( - '

10 files

', - response.content.decode(), - msg_prefix=f"creator {c} can not see 10 files", - ) - - # make creator2 publish the 5 approved files - data = {"action": "publish", "_selected_action": self.files[:5]} - self.client.login(username="creator2", password="secret") - response = self.client.post(url, data, follow=True) - self.assertEqual(response.status_code, 200) - self.assertInHTML( - '

10 files

', response.content.decode(), msg_prefix="creator2 can not see 10 files" - ) - response = self.client.get(url + "?published__exact=1") - self.assertInHTML( - '

5 files

', response.content.decode(), msg_prefix="can not see 5 published files" - ) - - # make creator2 unpublish the 5 approved files - data = {"action": "unpublish", "_selected_action": self.files[:5]} - response = self.client.post(url, data, follow=True) - self.assertEqual(response.status_code, 200) - self.assertInHTML( - "5 files selected to be unpublished, " - "out of those 5 files had needed permission, " - "and out of those 5 files were successfully unpublished", - response.content.decode(), - msg_prefix="unpublished message not found", - ) - self.assertInHTML( - '

10 files

', response.content.decode(), msg_prefix="creator2 can not see 10 files" - ) - response = self.client.get(url + "?published__exact=0") - self.assertInHTML( - '

10 files

', - response.content.decode(), - msg_prefix="creator2 can not see 10 unpublished files after unpublishing", - ) - - # make moderator4 unapprove 5 of the files owned by creator2 - data = {"action": "unapprove", "_selected_action": self.files[:5]} - self.client.login(username="moderator4", password="secret") - response = self.client.post(url, data, follow=True) - self.assertEqual(response.status_code, 200) - response = self.client.get(url + "?approved__exact=0") - self.assertInHTML( - '

20 files

', - response.content.decode(), - msg_prefix=f"moderator {m} can not see 20 files pending moderation", - ) - - # make creator2 softdelete the 5 approved and pubhlished files - data = {"action": "softdelete", "_selected_action": self.files[:5]} - self.client.login(username="creator2", password="secret") - response = self.client.post(url, data, follow=True) - self.assertEqual(response.status_code, 200) - self.assertInHTML( - '

10 files

', response.content.decode(), msg_prefix="creator2 can not see 10 files" - ) - response = self.client.get(url + "?deleted__exact=1") - self.assertInHTML( - '

5 files

', response.content.decode(), msg_prefix="can not see 5 deleted files" - ) - - -class TestFileViews(BmaTestBase): - """Unit tests for regular django views.""" - - @classmethod - def setUpTestData(cls) -> None: - """Add test data.""" - # first add users and other basics - super().setUpTestData() - # upload some files - cls.upload_initial_test_files() - - ######### FILE LIST ###################################### - def assert_file_list_rows(self, expected_rows: int, fail_message: str = "", qs: str = "") -> None: - """Make a file_list call and count the number of table rows (files).""" - url = reverse("files:file_list_table") - response = self.client.get(url + qs) - content = response.content.decode() - soup = BeautifulSoup(content, "html.parser") - rows = soup.select("div.table-container > table > tbody > tr") - if not fail_message: - fail_message = f"did not get {expected_rows} from file_list view with filter: '{qs}'" - self.assertEqual(len(rows), expected_rows, fail_message) - return content - - def test_file_list_view_perms(self) -> None: - """Test the permissions aspects of the file list view.""" - # the superuser can see all 24 files - self.client.login(username="superuser", password="secret") - self.assert_file_list_rows(24) - - # anonymous can see 0 files - self.client.logout() - self.assert_file_list_rows(0) - - # each creator can see their own files - for user, count in [("creator2", 15), ("creator3", 9)]: - self.client.login(username=user, password="secret") - self.assert_file_list_rows(count, f"creator {user} can not see {count} files") - - # each moderator can see all 25 files - for m in ["moderator4", "moderator5"]: - self.client.login(username=m, password="secret") - self.assert_file_list_rows(24, f"moderator {m} can not see 24 files") - - # each curator can see 0 files since none are approved yet - for m in ["curator6", "curator7"]: - self.client.login(username=m, password="secret") - self.assert_file_list_rows(0, f"curator {m} can not see 0 files") - - # change it up a bit - self.change_initial_test_files() - - # each curator can now see 3 files - for m in ["curator6", "curator7"]: - self.client.login(username=m, password="secret") - self.assert_file_list_rows(3, f"curator {m} can not see 3 files") - - # anonymous can now see 3 files - self.client.logout() - self.assert_file_list_rows(3) - - def test_file_list_view_attribution_filters(self) -> None: - """Test the attribution filter of the file list view.""" - # use moderator so all files are visible - self.client.login(username="moderator4", password="secret") - - # test attribution and attribution contains - self.assert_file_list_rows(24, qs="?attribution__icontains=foto") - self.assert_file_list_rows(0, qs="?attribution__icontains=notthere") - self.assert_file_list_rows(1, qs="?attribution__icontains=fotofonzy") - - def test_file_list_view_license_filters(self) -> None: - """Test the license filter of the file list view.""" - # use moderator so all files are visible - self.client.login(username="moderator4", password="secret") - - # test license filter - self.assert_file_list_rows(1, qs="?licenses=CC_BY_4_0") - self.assert_file_list_rows(1, qs="?licenses=CC_BY_SA_4_0") - self.assert_file_list_rows(2, qs="?licenses=CC_BY_4_0&licenses=CC_BY_SA_4_0") - - def test_file_list_view_file_size_filters(self) -> None: - """Test the size filter of the file list view.""" - # use moderator so all files are visible - self.client.login(username="moderator4", password="secret") - - # test file size filter - self.assert_file_list_rows(24, qs="?file_size=8424") - self.assert_file_list_rows(24, qs="?file_size__lt=100000") - self.assert_file_list_rows(0, qs="?file_size__lt=100") - self.assert_file_list_rows(0, qs="?file_size__gt=100000") - self.assert_file_list_rows(24, qs="?file_size__gt=100") - - def test_file_list_view_album_filters(self) -> None: - """Test the album filter of the file list view.""" - # use moderator so all files are visible - self.client.login(username="moderator4", password="secret") - - # test album filter - self.assert_file_list_rows(11, qs=f"?in_all_albums={self.creator2_album}") - self.assert_file_list_rows(11, qs=f"?in_all_albums={self.creator2_album}&in_all_albums={self.allfiles_album}") - self.assert_file_list_rows(11, qs=f"?in_any_albums={self.creator2_album}") - self.assert_file_list_rows(20, qs=f"?in_any_albums={self.creator2_album}&in_any_albums={self.creator3_album}") - self.assert_file_list_rows(13, qs=f"?not_in_albums={self.creator2_album}") - - def test_file_list_view_uploaders_filters(self) -> None: - """Test the uploaders filter of the file list view.""" - # use moderator so all files are visible - self.client.login(username="moderator4", password="secret") - - # test uploaders filter - self.assert_file_list_rows(15, qs="?uploaders=creator2") - self.assert_file_list_rows(24, qs="?uploaders=creator2&uploaders=creator3") - - def test_file_list_view_bool_filters(self) -> None: - """Test the bool filters (approved, published, deleted) of the file list view.""" - # use moderator so all files are visible - self.client.login(username="moderator4", password="secret") - - # test approved filter - self.assert_file_list_rows(0, qs="?approved=true") - self.assert_file_list_rows(24, qs="?approved=false") - - # test published filter - self.assert_file_list_rows(0, qs="?published=true") - self.assert_file_list_rows(24, qs="?published=false") - - # test deleted filter - self.assert_file_list_rows(0, qs="?deleted=true") - self.assert_file_list_rows(24, qs="?deleted=false") - - # change it up - self.change_initial_test_files() - - # test approved filter - self.assert_file_list_rows(5, qs="?approved=true") - self.assert_file_list_rows(19, qs="?approved=false") - - # test published filter - self.assert_file_list_rows(5, qs="?published=true") - self.assert_file_list_rows(19, qs="?published=false") - - # test deleted filter - self.assert_file_list_rows(5, qs="?deleted=true") - self.assert_file_list_rows(19, qs="?deleted=false") - - def test_file_list_view_tagged_filters(self) -> None: - """Test the tagged filter of the file list view.""" - # use moderator so all files are visible - self.client.login(username="moderator4", password="secret") - - # test tagged_all filter - self.assert_file_list_rows(0, qs="?tagged_all=foo&tagged_all=bar") - self.assert_file_list_rows(11, qs="?tagged_all=foo") - - # test tagged_any filter - self.assert_file_list_rows(20, qs="?tagged_any=foo&tagged_any=bar") - self.assert_file_list_rows(9, qs="?tagged_any=bar") - - # test not_tagged filter - self.assert_file_list_rows(22, qs="?not_tagged=tag1") - - def test_file_list_view_taggers_filters(self) -> None: - """Test the taggers filter of the file list view.""" - # use moderator so all files are visible - self.client.login(username="moderator4", password="secret") - - self.change_initial_test_files() - - # test taggers_all filter - self.assert_file_list_rows(0, qs="?taggers_all=creator2&taggers_all=creator3") - self.assert_file_list_rows(11, qs="?taggers_all=creator2") - self.assert_file_list_rows(3, qs="?taggers_all=creator2&taggers_all=curator6") - - # test taggers_any filter - self.assert_file_list_rows(12, qs="?taggers_any=creator3&taggers_any=curator6") - self.assert_file_list_rows(3, qs="?taggers_any=curator6") - - # test not_taggers filter - self.assert_file_list_rows(21, qs="?not_taggers=curator6") - - def test_file_list_view_tagged_emoji(self) -> None: - """Make sure searching for a slug with an emoji works.""" - self.client.login(username="moderator4", password="secret") - self.change_initial_test_files() - self.assert_file_list_rows(3, qs="?tagged_all=more-fire") - - ######### FILE MULTIPLE ACTION ###################################### - - def test_file_multiple_actions_view(self) -> None: - """Make sure the multiple file actions view works as intended.""" - # make sure we have some approved and published files so the curators can work - self.approve_files_api(files=self.files, user=self.superuser) - self.publish_files_api(files=self.files, user=self.superuser) - - url = reverse("files:file_multiple_action") - self.client.login(username="curator6", password="secret") - - # create a new album with the 3 published files - data = {"action": "create_album", "selection": self.files[2:5], "fromurl": "/"} - response = self.client.post(url, data, follow=True) - assert "Showing 3 files" in response.content.decode() - - # make sure the remove_from_album form renders correctly - self.client.login(username="creator2", password="secret") - data = {"action": "remove_from_album", "selection": [self.files[3]], "fromurl": "/"} - response = self.client.post(url, data, follow=True) - assert len(response.redirect_chain) == 0 - content = response.content.decode() - assert "Remove Files from Album" in content - soup = BeautifulSoup(content, "html.parser") - rows = soup.select("div#id_album > div.form-check") - # expect 1 albums in the form (only 1 albums have this file and perms for curator6) - assert len(rows) == 1, "Did not see 1 albums in the remove form as expected" - - # remove files from album - data = {"album": self.albums[0], "files_to_remove": [self.files[3]], "fromurl": "/"} - response = self.client.post(reverse("albums:remove_files_from_album"), data, follow=True) - assert response.status_code == 200 - content = response.content.decode() - assert "Removed 1 of 1 file(s)" in content - - # make sure the add_to_album form renders correctly - data = {"action": "add_to_album", "selection": [self.files[3]], "fromurl": "/"} - response = self.client.post(url, data, follow=True) - assert len(response.redirect_chain) == 0 - content = response.content.decode() - assert "Add Files to Album" in content - soup = BeautifulSoup(content, "html.parser") - rows = soup.select("div#id_album > div.form-check") - # there should only be 1 album because the other albums with perm already has the file - assert len(rows) == 1, "Did not see 1 albums in the add form as expected" - - # actually post the add_to_album form (add to existing album) - data = {"album": self.albums[0], "files_to_add": [self.files[3]], "fromurl": "/"} - response = self.client.post(reverse("albums:add_files_to_album"), data, follow=True) - content = response.content.decode() - assert "Added 1 of 1 file(s)" in content - - ######### FILE DETAIL #################################### - - def test_file_detail_view(self) -> None: - """Test the file detail view.""" - self.client.login(username="creator2", password="secret") - response = self.client.get(reverse("files:file_show", kwargs={"file_uuid": self.files[0]})) - content = response.content.decode() - assert "Image creator2 file 0" in content - - ######### FILE TAG LIST #################################### - - def test_file_tag_list_view(self) -> None: - """Test the file tag list view.""" - self.client.login(username="creator2", password="secret") - response = self.client.get(reverse("files:file_tags", kwargs={"file_uuid": self.files[0]})) - content = response.content.decode() - soup = BeautifulSoup(content, "html.parser") - rows = soup.select("div.table-container > table > tbody > tr") - assert len(rows) == 2, "did not get 2 rows in file tag list view" - - ######### FILE TAG CREATE #################################### - - def test_file_tag_create_view(self) -> None: - """Make sure the file tag create view works as intended.""" - url = reverse("files:file_tag_create", kwargs={"file_uuid": self.files[0]}) - self.client.login(username="creator2", password="secret") - # test GET - response = self.client.get(url) - content = response.content.decode() - assert "Add Tags to Image" in content - - # add new tags - data = {"tags": "testtag1 testtag2"} - response = self.client.post(url, data, follow=True) - content = response.content.decode() - assert "Tag(s) added." in content, "Tags added message not found" - soup = BeautifulSoup(content, "html.parser") - tag_card = soup.select_one("#tag-card") - tags = tag_card.select("form") - assert len(tags) == 4, "did not find 4 tags after adding 2" - - ######### FILE TAG DETAIL #################################### - - def test_file_tag_detail_view(self) -> None: - """Make sure the file tag detail view works.""" - url = reverse("files:file_tag_taggings_list", kwargs={"file_uuid": self.files[0], "tag_slug": "tag0"}) - self.client.login(username="creator2", password="secret") - # test GET - response = self.client.get(url) - content = response.content.decode() - soup = BeautifulSoup(content, "html.parser") - rows = soup.select("div.table-container > table > tbody > tr") - # only 1 tagging with this tag - assert len(rows) == 1 - - ######### FILE TAG DELETE #################################### - - def test_file_tag_delete_view(self) -> None: - """Make sure the file tag delete view works as intended.""" - url = reverse("files:file_tag_delete", kwargs={"file_uuid": self.files[0], "tag_slug": "tag0"}) - self.client.login(username="creator2", password="secret") - response = self.client.post(url, follow=True) - content = response.content.decode() - assert "Tag deleted." in content, "Tags deleted message not found" - soup = BeautifulSoup(content, "html.parser") - tag_card = soup.select_one("#tag-card") - tags = tag_card.select("form") - assert len(tags) == 1, "did not find 1 tags after removing 1" diff --git a/src/files/tests/test_views.py b/src/files/tests/test_views.py new file mode 100644 index 00000000..fe4dc168 --- /dev/null +++ b/src/files/tests/test_views.py @@ -0,0 +1,314 @@ +"""Tests for the file HTML views.""" + +from bs4 import BeautifulSoup +from django.urls import reverse + +from utils.tests import BmaTestBase + + +class TestFileViews(BmaTestBase): + """Unit tests for regular django views.""" + + @classmethod + def setUpTestData(cls) -> None: + """Add test data.""" + # first add users and other basics + super().setUpTestData() + # upload some files + cls.upload_initial_test_files() + + ######### FILE LIST ###################################### + def assert_file_list_rows(self, expected_rows: int, fail_message: str = "", qs: str = "") -> None: + """Make a file_list call and count the number of table rows (files).""" + url = reverse("files:file_list_table") + response = self.client.get(url + qs) + content = response.content.decode() + soup = BeautifulSoup(content, "html.parser") + rows = soup.select("div.table-container > table > tbody > tr") + if not fail_message: + fail_message = f"did not get {expected_rows} from file_list view with filter: '{qs}'" + self.assertEqual(len(rows), expected_rows, fail_message) + + def test_file_list_view_perms(self) -> None: + """Test the permissions aspects of the file list view.""" + # the superuser can see all 24 files + self.client.login(username="superuser", password="secret") + self.assert_file_list_rows(24) + + # anonymous can see 0 files + self.client.logout() + self.assert_file_list_rows(0) + + # each creator can see their own files + for user, count in [("creator2", 15), ("creator3", 9)]: + self.client.login(username=user, password="secret") + self.assert_file_list_rows(count, f"creator {user} can not see {count} files") + + # each moderator can see all 25 files + for m in ["moderator4", "moderator5"]: + self.client.login(username=m, password="secret") + self.assert_file_list_rows(24, f"moderator {m} can not see 24 files") + + # each curator can see 0 files since none are approved yet + for m in ["curator6", "curator7"]: + self.client.login(username=m, password="secret") + self.assert_file_list_rows(0, f"curator {m} can not see 0 files") + + # change it up a bit + self.change_initial_test_files() + + # each curator can now see 3 files + for m in ["curator6", "curator7"]: + self.client.login(username=m, password="secret") + self.assert_file_list_rows(3, f"curator {m} can not see 3 files") + + # anonymous can now see 3 files + self.client.logout() + self.assert_file_list_rows(3) + + def test_file_list_view_attribution_filters(self) -> None: + """Test the attribution filter of the file list view.""" + # use moderator so all files are visible + self.client.login(username="moderator4", password="secret") + + # test attribution and attribution contains + self.assert_file_list_rows(24, qs="?attribution__icontains=foto") + self.assert_file_list_rows(0, qs="?attribution__icontains=notthere") + self.assert_file_list_rows(1, qs="?attribution__icontains=fotofonzy") + + def test_file_list_view_license_filters(self) -> None: + """Test the license filter of the file list view.""" + # use moderator so all files are visible + self.client.login(username="moderator4", password="secret") + + # test license filter + self.assert_file_list_rows(1, qs="?licenses=CC_BY_4_0") + self.assert_file_list_rows(1, qs="?licenses=CC_BY_SA_4_0") + self.assert_file_list_rows(2, qs="?licenses=CC_BY_4_0&licenses=CC_BY_SA_4_0") + + def test_file_list_view_file_size_filters(self) -> None: + """Test the size filter of the file list view.""" + # use moderator so all files are visible + self.client.login(username="moderator4", password="secret") + + # test file size filter + self.assert_file_list_rows(24, qs="?file_size=8424") + self.assert_file_list_rows(24, qs="?file_size__lt=100000") + self.assert_file_list_rows(0, qs="?file_size__lt=100") + self.assert_file_list_rows(0, qs="?file_size__gt=100000") + self.assert_file_list_rows(24, qs="?file_size__gt=100") + + def test_file_list_view_album_filters(self) -> None: + """Test the album filter of the file list view.""" + # use moderator so all files are visible + self.client.login(username="moderator4", password="secret") + + # test album filter + self.assert_file_list_rows(11, qs=f"?in_all_albums={self.creator2_album}") + self.assert_file_list_rows(11, qs=f"?in_all_albums={self.creator2_album}&in_all_albums={self.allfiles_album}") + self.assert_file_list_rows(11, qs=f"?in_any_albums={self.creator2_album}") + self.assert_file_list_rows(20, qs=f"?in_any_albums={self.creator2_album}&in_any_albums={self.creator3_album}") + self.assert_file_list_rows(13, qs=f"?not_in_albums={self.creator2_album}") + + def test_file_list_view_uploaders_filters(self) -> None: + """Test the uploaders filter of the file list view.""" + # use moderator so all files are visible + self.client.login(username="moderator4", password="secret") + + # test uploaders filter + self.assert_file_list_rows(15, qs="?uploaders=creator2") + self.assert_file_list_rows(24, qs="?uploaders=creator2&uploaders=creator3") + + def test_file_list_view_bool_filters(self) -> None: + """Test the bool filters (approved, published, deleted) of the file list view.""" + # use moderator so all files are visible + self.client.login(username="moderator4", password="secret") + + # test approved filter + self.assert_file_list_rows(0, qs="?approved=true") + self.assert_file_list_rows(24, qs="?approved=false") + + # test published filter + self.assert_file_list_rows(0, qs="?published=true") + self.assert_file_list_rows(24, qs="?published=false") + + # test deleted filter + self.assert_file_list_rows(0, qs="?deleted=true") + self.assert_file_list_rows(24, qs="?deleted=false") + + # change it up + self.change_initial_test_files() + + # test approved filter + self.assert_file_list_rows(5, qs="?approved=true") + self.assert_file_list_rows(19, qs="?approved=false") + + # test published filter + self.assert_file_list_rows(5, qs="?published=true") + self.assert_file_list_rows(19, qs="?published=false") + + # test deleted filter + self.assert_file_list_rows(5, qs="?deleted=true") + self.assert_file_list_rows(19, qs="?deleted=false") + + def test_file_list_view_tagged_filters(self) -> None: + """Test the tagged filter of the file list view.""" + # use moderator so all files are visible + self.client.login(username="moderator4", password="secret") + + # test tagged_all filter + self.assert_file_list_rows(0, qs="?tagged_all=foo&tagged_all=bar") + self.assert_file_list_rows(11, qs="?tagged_all=foo") + + # test tagged_any filter + self.assert_file_list_rows(20, qs="?tagged_any=foo&tagged_any=bar") + self.assert_file_list_rows(9, qs="?tagged_any=bar") + + # test not_tagged filter + self.assert_file_list_rows(22, qs="?not_tagged=tag1") + + def test_file_list_view_taggers_filters(self) -> None: + """Test the taggers filter of the file list view.""" + # use moderator so all files are visible + self.client.login(username="moderator4", password="secret") + + self.change_initial_test_files() + + # test taggers_all filter + self.assert_file_list_rows(0, qs="?taggers_all=creator2&taggers_all=creator3") + self.assert_file_list_rows(11, qs="?taggers_all=creator2") + self.assert_file_list_rows(3, qs="?taggers_all=creator2&taggers_all=curator6") + + # test taggers_any filter + self.assert_file_list_rows(12, qs="?taggers_any=creator3&taggers_any=curator6") + self.assert_file_list_rows(3, qs="?taggers_any=curator6") + + # test not_taggers filter + self.assert_file_list_rows(21, qs="?not_taggers=curator6") + + def test_file_list_view_tagged_emoji(self) -> None: + """Make sure searching for a slug with an emoji works.""" + self.client.login(username="moderator4", password="secret") + self.change_initial_test_files() + self.assert_file_list_rows(3, qs="?tagged_all=more-fire") + + ######### FILE MULTIPLE ACTION ###################################### + + def test_file_multiple_actions_view(self) -> None: + """Make sure the multiple file actions view works as intended.""" + # make sure we have some approved and published files so the curators can work + self.approve_files_api(files=self.files, user=self.superuser) + self.publish_files_api(files=self.files, user=self.superuser) + + url = reverse("files:file_multiple_action") + self.client.login(username="curator6", password="secret") + + # create a new album with the 3 published files + data = {"action": "create_album", "selection": self.files[2:5], "fromurl": "/"} + response = self.client.post(url, data, follow=True) + assert "Showing 3 files" in response.content.decode() + + # make sure the remove_from_album form renders correctly + self.client.login(username="creator2", password="secret") + data = {"action": "remove_from_album", "selection": [self.files[3]], "fromurl": "/"} + response = self.client.post(url, data, follow=True) + assert len(response.redirect_chain) == 0 + content = response.content.decode() + assert "Remove Files from Album" in content + soup = BeautifulSoup(content, "html.parser") + rows = soup.select("div#id_album > div.form-check") + # expect 1 albums in the form (only 1 albums have this file and perms for curator6) + assert len(rows) == 1, "Did not see 1 albums in the remove form as expected" + + # remove files from album + data = {"album": self.albums[0], "files_to_remove": [self.files[3]], "fromurl": "/"} + response = self.client.post(reverse("albums:remove_files_from_album"), data, follow=True) + assert response.status_code == 200 + content = response.content.decode() + assert "Removed 1 of 1 file(s)" in content + + # make sure the add_to_album form renders correctly + data = {"action": "add_to_album", "selection": [self.files[3]], "fromurl": "/"} + response = self.client.post(url, data, follow=True) + assert len(response.redirect_chain) == 0 + content = response.content.decode() + assert "Add Files to Album" in content + soup = BeautifulSoup(content, "html.parser") + rows = soup.select("div#id_album > div.form-check") + # there should only be 1 album because the other albums with perm already has the file + assert len(rows) == 1, "Did not see 1 albums in the add form as expected" + + # actually post the add_to_album form (add to existing album) + data = {"album": self.albums[0], "files_to_add": [self.files[3]], "fromurl": "/"} + response = self.client.post(reverse("albums:add_files_to_album"), data, follow=True) + content = response.content.decode() + assert "Added 1 of 1 file(s)" in content + + ######### FILE DETAIL #################################### + + def test_file_detail_view(self) -> None: + """Test the file detail view.""" + self.client.login(username="creator2", password="secret") + response = self.client.get(reverse("files:file_show", kwargs={"file_uuid": self.files[0]})) + content = response.content.decode() + assert "Image creator2 file 0" in content + + ######### FILE TAG LIST #################################### + + def test_file_tag_list_view(self) -> None: + """Test the file tag list view.""" + self.client.login(username="creator2", password="secret") + response = self.client.get(reverse("files:file_tags", kwargs={"file_uuid": self.files[0]})) + content = response.content.decode() + soup = BeautifulSoup(content, "html.parser") + rows = soup.select("div.table-container > table > tbody > tr") + assert len(rows) == 2, "did not get 2 rows in file tag list view" + + ######### FILE TAG CREATE #################################### + + def test_file_tag_create_view(self) -> None: + """Make sure the file tag create view works as intended.""" + url = reverse("files:file_tag_create", kwargs={"file_uuid": self.files[0]}) + self.client.login(username="creator2", password="secret") + # test GET + response = self.client.get(url) + content = response.content.decode() + assert "Add Tags to Image" in content + + # add new tags + data = {"tags": "testtag1 testtag2"} + response = self.client.post(url, data, follow=True) + content = response.content.decode() + assert "Tag(s) added." in content, "Tags added message not found" + soup = BeautifulSoup(content, "html.parser") + tag_card = soup.select_one("#tag-card") + tags = tag_card.select("form") + assert len(tags) == 4, "did not find 4 tags after adding 2" + + ######### FILE TAG DETAIL #################################### + + def test_file_tag_detail_view(self) -> None: + """Make sure the file tag detail view works.""" + url = reverse("files:file_tag_taggings_list", kwargs={"file_uuid": self.files[0], "tag_slug": "tag0"}) + self.client.login(username="creator2", password="secret") + # test GET + response = self.client.get(url) + content = response.content.decode() + soup = BeautifulSoup(content, "html.parser") + rows = soup.select("div.table-container > table > tbody > tr") + # only 1 tagging with this tag + assert len(rows) == 1 + + ######### FILE TAG DELETE #################################### + + def test_file_tag_delete_view(self) -> None: + """Make sure the file tag delete view works as intended.""" + url = reverse("files:file_tag_delete", kwargs={"file_uuid": self.files[0], "tag_slug": "tag0"}) + self.client.login(username="creator2", password="secret") + response = self.client.post(url, follow=True) + content = response.content.decode() + assert "Tag deleted." in content, "Tags deleted message not found" + soup = BeautifulSoup(content, "html.parser") + tag_card = soup.select_one("#tag-card") + tags = tag_card.select("form") + assert len(tags) == 1, "did not find 1 tags after removing 1" diff --git a/src/pictures/tests/test_utils.py b/src/pictures/tests/test_utils.py index cd1fcfd1..03cadd00 100644 --- a/src/pictures/tests/test_utils.py +++ b/src/pictures/tests/test_utils.py @@ -12,7 +12,7 @@ class TestGrid: - def test_default(self): + def test_default(self) -> None: assert list(utils._grid(columns=12, settings=settings)) == [ ("xs", 1.0), ("s", 1.0), @@ -21,7 +21,7 @@ def test_default(self): ("xl", 1.0), ] - def test_small_up(self): + def test_small_up(self) -> None: assert list(utils._grid(columns=12, settings=settings, xs=6)) == [ ("xs", 0.5), ("s", 0.5), @@ -30,7 +30,7 @@ def test_small_up(self): ("xl", 0.5), ] - def test_mixed(self): + def test_mixed(self) -> None: assert list(utils._grid(columns=12, settings=settings, s=6, l=9)) == [ ("xs", 1.0), ("s", 0.5), @@ -39,35 +39,35 @@ def test_mixed(self): ("xl", 0.75), ] - def test_key_error(self): + def test_key_error(self) -> None: with pytest.raises(KeyError) as e: list(utils._grid(columns=12, settings=settings, xxxxl=6)) assert "Invalid breakpoint 'xxxxl' - available breakpoints: xs,s,m,l,xl" in str(e.value) class TestSizes: - def test_default(self): + def test_default(self) -> None: assert utils.sizes(columns=12, settings=settings) == "100vw" - def test_default__container(self): + def test_default__container(self) -> None: assert ( utils.sizes(columns=12, settings=settings, container_width=1200) == "(min-width: 0px) and (max-width: 1199px) 100vw, 1200px" ) - def test_bottom_up(self): + def test_bottom_up(self) -> None: assert utils.sizes(columns=12, settings=settings, xs=6) == "50vw" - def test_bottom_up__container(self): + def test_bottom_up__container(self) -> None: assert ( utils.sizes(columns=12, settings=settings, container_width=1200, xs=6) == "(min-width: 0px) and (max-width: 1199px) 50vw, 600px" ) - def test_medium_up(self): + def test_medium_up(self) -> None: assert utils.sizes(columns=12, settings=settings, s=6) == "(min-width: 0px) and (max-width: 767px) 100vw, 50vw" - def test_medium_up__container(self): + def test_medium_up__container(self) -> None: assert ( utils.sizes(columns=12, settings=settings, container_width=1200, s=6) == "(min-width: 0px) and (max-width: 767px) 100vw," @@ -75,14 +75,14 @@ def test_medium_up__container(self): " 600px" ) - def test_mixed(self): + def test_mixed(self) -> None: assert ( utils.sizes(columns=12, settings=settings, s=6, l=9) == "(min-width: 0px) and (max-width: 767px) 100vw," " (min-width: 768px) and (max-width: 1199px) 50vw," " 75vw" ) - def test_mixed__container(self): + def test_mixed__container(self) -> None: assert ( utils.sizes(columns=12, settings=settings, container_width=1200, s=6, l=9) == "(min-width: 0px) and (max-width: 767px) 100vw," @@ -90,7 +90,7 @@ def test_mixed__container(self): " 600px" ) - def test_container__smaller_than_breakpoint(self): + def test_container__smaller_than_breakpoint(self) -> None: with pytest.warns() as records: assert ( utils.sizes(columns=12, settings=settings, container_width=500) @@ -100,9 +100,9 @@ def test_container__smaller_than_breakpoint(self): class TestGetWidths: - def test_default(self): + def test_default(self) -> None: assert utils.get_widths( - original_size=(800, 600), ratio=3 / 2, max_width=1200, columns=12, pixel_densities=[1, 2] + original_size=(800, 600), ratio="3/2", max_width=1200, columns=12, pixel_densities=[1, 2] ) == { 800, 100, @@ -114,9 +114,9 @@ def test_default(self): 700, } - def test_different_aspect(self): + def test_different_aspect(self) -> None: assert utils.get_widths( - original_size=(800, 600), ratio=1 / 1, max_width=1200, columns=12, pixel_densities=[1, 2] + original_size=(800, 600), ratio="1/1", max_width=1200, columns=12, pixel_densities=[1, 2] ) == { 100, 200, @@ -126,9 +126,9 @@ def test_different_aspect(self): 600, } - def test_very_large_img(self): + def test_very_large_img(self) -> None: size = 6000, 4000 # 24MP - assert utils.get_widths(original_size=size, ratio=1 / 1, max_width=1200, columns=6, pixel_densities=[1, 2]) == { + assert utils.get_widths(original_size=size, ratio="1/1", max_width=1200, columns=6, pixel_densities=[1, 2]) == { 800, 1600, 2400, diff --git a/src/tags/tests/__init__.py b/src/tags/tests/__init__.py new file mode 100644 index 00000000..6909d92f --- /dev/null +++ b/src/tags/tests/__init__.py @@ -0,0 +1 @@ +"""Unit tests for the tags app.""" diff --git a/src/tags/tests.py b/src/tags/tests/test_api.py similarity index 100% rename from src/tags/tests.py rename to src/tags/tests/test_api.py