diff --git a/sas/tests/test_update_album.py b/sas/tests/test_update_album.py new file mode 100644 index 00000000..cc3fbd7a --- /dev/null +++ b/sas/tests/test_update_album.py @@ -0,0 +1,218 @@ +import random +import string +from pathlib import Path +from typing import Callable +from unittest.mock import patch + +import pytest +from django.conf import settings +from django.core.files.base import ContentFile +from django.core.files.uploadedfile import SimpleUploadedFile +from django.test import Client +from django.urls import reverse +from django.utils.datastructures import MultiValueDict +from django.utils.timezone import localdate +from model_bakery import baker +from PIL import Image +from pytest_django.asserts import assertInHTML, assertRedirects + +from core.baker_recipes import subscriber_user +from core.models import Group, User +from core.utils import RED_PIXEL_PNG +from sas.baker_recipes import picture_recipe +from sas.forms import AlbumEditForm +from sas.models import Album + + +@pytest.fixture +def sas_root(db) -> Album: + return Album.objects.get(id=settings.SITH_SAS_ROOT_DIR_ID) + + +@pytest.fixture +def album(db) -> Album: + name = "".join( + random.choice(string.ascii_letters) for _ in range(Album.NAME_MAX_LENGTH) + ) + return baker.make( + Album, name=name, parent_id=settings.SITH_SAS_ROOT_DIR_ID, is_moderated=True + ) + + +@pytest.mark.parametrize("user", [None, lambda: baker.make(User), subscriber_user.make]) +@pytest.mark.django_db +def test_permission_denied( + client: Client, album: Album, user: Callable[[], User] | None +): + if user: + client.force_login(user()) + url = reverse("sas:album_edit", kwargs={"album_id": album.pk}) + for method in client.get, client.post: + assert method(url).status_code == 403 + + +@pytest.mark.django_db +def test_sas_root_read_only(client: Client, sas_root: Album): + moderator = baker.make( + User, groups=[Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)] + ) + client.force_login(moderator) + url = reverse("sas:album_edit", kwargs={"album_id": sas_root.pk}) + for method in client.get, client.post: + assert method(url).status_code == 404 + + +@pytest.mark.parametrize( + ("excluded", "is_valid"), + [ + ("name", False), + ("date", False), + ("file", True), + ("parent", False), + ("edit_groups", True), + ("recursive", True), + ], +) +@pytest.mark.django_db +def test_form_required(album: Album, excluded: str, is_valid: bool): # noqa: FBT001 + data = { + "name": album.name, + "parent": baker.make(Album, parent=album.parent, is_moderated=True).pk, + "date": localdate(), + "file": "/random/path", + "edit_groups": [settings.SITH_GROUP_SAS_ADMIN_ID], + "recursive": False, + } + del data[excluded] + assert AlbumEditForm(data=data).is_valid() == is_valid + + +@pytest.mark.django_db +def test_form_album_name(album: Album): + data = { + "name": "a" * Album.NAME_MAX_LENGTH, + "parent": album.pk, + "date": localdate(), + } + assert AlbumEditForm(data=data).is_valid() + + data["name"] = "a" * (Album.NAME_MAX_LENGTH + 1) + assert not AlbumEditForm(data=data).is_valid() + + +@pytest.mark.django_db +def test_update_recursive_parent(client: Client, album: Album): + client.force_login(baker.make(User, is_superuser=True)) + + payload = {"name": album.name, "parent": album.pk, "date": localdate()} + response = client.post( + reverse("sas:album_edit", kwargs={"album_id": album.pk}), payload + ) + assertInHTML("
  • Boucle dans l'arborescence des dossiers
  • ", response.text) + assert response.status_code == 200 + + +@pytest.mark.parametrize( + "user", + [ + lambda: baker.make(User, is_superuser=True), + lambda: baker.make( + User, groups=[Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)] + ), + ], +) +@pytest.mark.parametrize( + "parent", + [ + lambda: baker.make( + Album, parent_id=settings.SITH_SAS_ROOT_DIR_ID, is_moderated=True + ), + lambda: Album.objects.get(id=settings.SITH_SAS_ROOT_DIR_ID), + ], +) +@pytest.mark.django_db +def test_update( + client: Client, + album: Album, + sas_root: Album, + user: Callable[[], User], + parent: Callable[[], Album], +): + client.force_login(user()) + expected_redirect = reverse("sas:album", kwargs={"album_id": album.pk}) + payload = { + "name": "foo", + "parent": parent().id, + "date": localdate(), + "recursive": False, + } + response = client.post( + reverse("sas:album_edit", kwargs={"album_id": album.pk}), payload + ) + assertRedirects(response, expected_redirect) + album.refresh_from_db() + assert album.name == "foo" + assert album.parent.id == payload["parent"] + assert localdate(album.date) == localdate() + + +class TestAlbumThumbnail: + @pytest.fixture + def files(self): + return MultiValueDict( + {"file": [SimpleUploadedFile(name="foo.png", content=RED_PIXEL_PNG)]} + ) + + def test_thumbnail_resized(self, album, files): + """Test that album thumbnails are resized to the correct dimensions.""" + form = AlbumEditForm( + data={"name": album.name, "date": localdate(), "parent": album.parent.id}, + files=files, + instance=album, + ) + assert form.is_valid() + form.save() + album.refresh_from_db() + assert album.file.name == f"SAS/{album.name}/thumb.webp" + assert Image.open(album.file).size == (200, 200) + + def test_thumbnail_removed(self, album): + """Test the case where the user checks the box to remove the thumbnail""" + album.file = ContentFile(name="foo.png", content=RED_PIXEL_PNG) + album.save() + previous_filename = album.file.name + form = AlbumEditForm( + data={ + "name": "foo", + "date": localdate(), + "parent": album.parent.id, + "file-clear": True, + }, + instance=album, + ) + # as there is now no picture, a thumbnail should be generated + with patch.object(Album, "generate_thumbnail") as mock: + assert form.is_valid() + form.save() + album.refresh_from_db() + assert album.file.storage.exists(album.file.name) + assert not album.file.storage.exists(previous_filename) + mock.assert_called_once() + + def test_generate_thumbnail(self, album): + """Test that if no image is given and the album has pictures, + the thumbnail is automatically generated. + """ + picture = picture_recipe.make( + parent=album, thumbnail=ContentFile(name="foo.png", content=RED_PIXEL_PNG) + ) + form = AlbumEditForm( + data={"name": "foo", "date": localdate(), "parent": album.parent.id}, + instance=album, + ) + assert form.is_valid() + form.save() + album.refresh_from_db() + assert Path(album.file.name) == Path("SAS/foo/thumb.webp") + assert album.file.storage.exists(album.file.name) + assert Image.open(album.file) == Image.open(picture.thumbnail) diff --git a/sas/tests/test_views.py b/sas/tests/test_views.py index ffa48beb..57a69750 100644 --- a/sas/tests/test_views.py +++ b/sas/tests/test_views.py @@ -20,14 +20,12 @@ from django.conf import settings from django.core.cache import cache from django.test import Client, TestCase from django.urls import reverse -from django.utils.timezone import localdate from model_bakery import baker from pytest_django.asserts import assertHTMLEqual, assertInHTML, assertRedirects from core.baker_recipes import old_subscriber_user, subscriber_user from core.models import Group, User from sas.baker_recipes import picture_recipe -from sas.forms import AlbumEditForm from sas.models import Album, Picture # Create your tests here. @@ -97,6 +95,7 @@ def test_main_page_content_anonymous(client: Client): @pytest.mark.django_db def test_album_access_non_subscriber(client: Client): """Test that non-subscribers can only access albums where they are identified.""" + cache.clear() album = baker.make(Album, parent_id=settings.SITH_SAS_ROOT_DIR_ID) user = baker.make(User) client.force_login(user) @@ -163,140 +162,6 @@ class TestAlbumUpload: assert not album.children.exists() -@pytest.mark.django_db -class TestAlbumEdit: - @pytest.fixture - def sas_root(self) -> Album: - return Album.objects.get(id=settings.SITH_SAS_ROOT_DIR_ID) - - @pytest.fixture - def album(self) -> Album: - return baker.make( - Album, parent_id=settings.SITH_SAS_ROOT_DIR_ID, is_moderated=True - ) - - @pytest.mark.parametrize( - "user", - [None, lambda: baker.make(User), subscriber_user.make], - ) - def test_permission_denied( - self, - client: Client, - album: Album, - user: Callable[[], User] | None, - ): - if user: - client.force_login(user()) - - url = reverse("sas:album_edit", kwargs={"album_id": album.pk}) - response = client.get(url) - assert response.status_code == 403 - response = client.post(url) - assert response.status_code == 403 - - def test_sas_root_read_only(self, client: Client, sas_root: Album): - moderator = baker.make( - User, groups=[Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)] - ) - client.force_login(moderator) - url = reverse("sas:album_edit", kwargs={"album_id": sas_root.pk}) - response = client.get(url) - assert response.status_code == 404 - response = client.post(url) - assert response.status_code == 404 - - @pytest.mark.parametrize( - ("excluded", "is_valid"), - [ - ("name", False), - ("date", False), - ("file", True), - ("parent", False), - ("edit_groups", True), - ("recursive", True), - ], - ) - def test_form_required(self, album: Album, excluded: str, is_valid: bool): # noqa: FBT001 - data = { - "name": album.name[: Album.NAME_MAX_LENGTH], - "parent": baker.make(Album, parent=album.parent, is_moderated=True).pk, - "date": localdate().strftime("%Y-%m-%d"), - "file": "/random/path", - "edit_groups": [settings.SITH_GROUP_SAS_ADMIN_ID], - "recursive": False, - } - del data[excluded] - assert AlbumEditForm(data=data).is_valid() == is_valid - - def test_form_album_name(self, album: Album): - data = { - "name": album.name[: Album.NAME_MAX_LENGTH], - "parent": album.pk, - "date": localdate().strftime("%Y-%m-%d"), - } - assert AlbumEditForm(data=data).is_valid() - - data["name"] = album.name[: Album.NAME_MAX_LENGTH + 1] - assert not AlbumEditForm(data=data).is_valid() - - def test_update_recursive_parent(self, client: Client, album: Album): - client.force_login(baker.make(User, is_superuser=True)) - - payload = { - "name": album.name[: Album.NAME_MAX_LENGTH], - "parent": album.pk, - "date": localdate().strftime("%Y-%m-%d"), - } - response = client.post( - reverse("sas:album_edit", kwargs={"album_id": album.pk}), payload - ) - assertInHTML("
  • Boucle dans l'arborescence des dossiers
  • ", response.text) - assert response.status_code == 200 - - @pytest.mark.parametrize( - "user", - [ - lambda: baker.make(User, is_superuser=True), - lambda: baker.make( - User, groups=[Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)] - ), - ], - ) - @pytest.mark.parametrize( - "parent", - [ - lambda: baker.make( - Album, parent_id=settings.SITH_SAS_ROOT_DIR_ID, is_moderated=True - ), - lambda: Album.objects.get(id=settings.SITH_SAS_ROOT_DIR_ID), - ], - ) - def test_update( - self, - client: Client, - album: Album, - sas_root: Album, - user: Callable[[], User], - parent: Callable[[], Album], - ): - client.force_login(user()) - expected_redirect = reverse("sas:album", kwargs={"album_id": album.pk}) - payload = { - "name": album.name[: Album.NAME_MAX_LENGTH], - "parent": parent().id, - "date": localdate().strftime("%Y-%m-%d"), - "recursive": False, - } - response = client.post( - reverse("sas:album_edit", kwargs={"album_id": album.pk}), payload - ) - assertRedirects(response, expected_redirect) - album.refresh_from_db() - assert album.name == payload["name"] - assert album.parent.id == payload["parent"] - assert localdate(album.date) == localdate() - - class TestSasModeration(TestCase): @classmethod def setUpTestData(cls):