# # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr # # This file is part of the website of the UTBM Student Association (AE UTBM), # https://ae.utbm.fr. # # You can find the source code of the website at https://github.com/ae-utbm/sith # # LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # SEE : https://raw.githubusercontent.com/ae-utbm/sith/master/LICENSE # OR WITHIN THE LOCAL FILE "LICENSE" # # from typing import Callable, Literal from unittest.mock import patch import pytest from bs4 import BeautifulSoup from django.conf import settings from django.core.cache import cache from django.core.files.base import ContentFile from django.test import Client, TestCase from django.urls import reverse from model_bakery import baker from PIL import Image 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 core.utils import RED_PIXEL_PNG from sas.baker_recipes import picture_recipe from sas.models import Album, Picture # Create your tests here. @pytest.mark.django_db @pytest.mark.parametrize( "user_factory", [ subscriber_user.make, old_subscriber_user.make, lambda: baker.make(User, is_superuser=True), lambda: baker.make( User, groups=[Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)] ), lambda: baker.make(User), lambda: None, ], ) def test_load_main_page(client: Client, user_factory: Callable[[], User]): """Just check that the SAS doesn't crash.""" user = user_factory() if user is not None: client.force_login(user) res = client.get(reverse("sas:main")) assert res.status_code == 200 @pytest.mark.django_db def test_main_page_no_form_for_regular_users(client: Client): """Test that subscribed users see no form on the sas main page""" client.force_login(subscriber_user.make()) res = client.get(reverse("sas:main")) soup = BeautifulSoup(res.text, "lxml") forms = soup.find("main").find_all("form") assert len(forms) == 0 @pytest.mark.django_db def test_main_page_displayed_albums(client: Client): """Test that the right data is displayed on the SAS main page""" sas = Album.objects.get(id=settings.SITH_SAS_ROOT_DIR_ID) Album.objects.exclude(id=sas.id).delete() album_a = baker.make(Album, parent=sas, is_moderated=True) album_b = baker.make(Album, parent=album_a, is_moderated=True) album_c = baker.make(Album, parent=sas, is_moderated=True) baker.make(Album, parent=sas, is_moderated=False) client.force_login(subscriber_user.make()) res = client.get(reverse("sas:main")) # album_b is not a direct child of the SAS, so it shouldn't be displayed # in the categories, but it should appear in the latest albums. # album_d isn't moderated, so it shouldn't appear at all for a simple user. # Also, the SAS itself shouldn't be listed in the albums. assert res.context_data["latest"] == [album_c, album_b, album_a] assert res.context_data["categories"] == [album_a, album_c] @pytest.mark.django_db def test_main_page_content_anonymous(client: Client): """Test that public users see only an incentive to login""" res = client.get(reverse("sas:main")) soup = BeautifulSoup(res.text, "lxml") expected = "

SAS

Vous devez être connecté pour voir les photos.

" assertHTMLEqual(soup.find("main").decode_contents(), expected) @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) res = client.get(reverse("sas:album", kwargs={"album_id": album.id})) assert res.status_code == 403 picture = picture_recipe.make(parent=album) picture.people.create(user=user) cache.clear() res = client.get(reverse("sas:album", kwargs={"album_id": album.id})) assert res.status_code == 200 @pytest.mark.django_db def test_accessing_sas_from_album_view_is_404(client: Client): """Test that trying to see the SAS with a regular album view isn't allowed.""" res = client.get( reverse("sas:album", kwargs={"album_id": settings.SITH_SAS_ROOT_DIR_ID}) ) assert res.status_code == 404 @pytest.mark.django_db class TestAlbumUpload: @staticmethod def assert_album_created(response, name, parent): assert response.headers.get("HX-Redirect", "") == parent.get_absolute_url() children = list(Album.objects.filter(parent=parent)) assert len(children) == 1 assert children[0].name == name def test_sas_admin(self, client: Client): user = baker.make( User, groups=[Group.objects.get(id=settings.SITH_GROUP_SAS_ADMIN_ID)] ) album = baker.make(Album, parent_id=settings.SITH_SAS_ROOT_DIR_ID) client.force_login(user) response = client.post( reverse("sas:album_create"), {"name": "new", "parent": album.id} ) self.assert_album_created(response, "new", album) def test_non_admin_user_with_edit_rights_on_parent(self, client: Client): group = baker.make(Group) user = subscriber_user.make(groups=[group]) album = baker.make( Album, parent_id=settings.SITH_SAS_ROOT_DIR_ID, edit_groups=[group] ) client.force_login(user) response = client.post( reverse("sas:album_create"), {"name": "new", "parent": album.id} ) self.assert_album_created(response, "new", album) def test_permission_denied(self, client: Client): album = baker.make(Album, parent_id=settings.SITH_SAS_ROOT_DIR_ID) client.force_login(subscriber_user.make()) response = client.post( reverse("sas:album_create"), {"name": "new", "parent": album.id} ) errors = BeautifulSoup(response.text, "lxml").find_all(class_="errorlist") assert len(errors) == 1 assert errors[0].text == "Vous n'avez pas la permission de faire cela" assert not album.children.exists() @pytest.mark.django_db class TestPictureRotation: @pytest.fixture def picture(self) -> Picture: return picture_recipe.make( parent_id=settings.SITH_SAS_ROOT_DIR_ID, file=ContentFile(name="foo.png", content=RED_PIXEL_PNG), compressed=ContentFile(name="foo.png", content=RED_PIXEL_PNG), thumbnail=ContentFile(name="foo.png", content=RED_PIXEL_PNG), ) @pytest.mark.parametrize( "user", [ None, lambda: baker.make(User), subscriber_user.make, old_subscriber_user.make, ], ) def test_permission_denied( self, client: Client, picture: Picture, user: Callable[[], User] | None ): if user: client.force_login(user()) url = reverse( "api:rotate_picture", kwargs={"picture_id": picture.id, "direction": "left"} ) response = client.post(url) assert response.status_code == 403 if user else 401 @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(("direction", "angle"), [("left", 90), ("right", 270)]) def test_rotation( self, client: Client, picture: Picture, user: Callable[[], User], direction: Literal["left", "right"], angle: Literal[90, 270], ): client.force_login(user()) url = reverse( "api:rotate_picture", kwargs={"picture_id": picture.id, "direction": direction}, ) with ( patch.object(Image.Image, "rotate") as mocked_rotate, patch.object(Picture, "generate_thumbnails") as mocked_thumb, ): response = client.post(url) assert response.status_code == 200 mocked_rotate.assert_called_once_with(angle) mocked_thumb.assert_called_once() class TestSasModeration(TestCase): @classmethod def setUpTestData(cls): album = baker.make( Album, parent_id=settings.SITH_SAS_ROOT_DIR_ID, is_moderated=True ) cls.pictures = picture_recipe.make( parent=album, _quantity=10, _bulk_create=True ) cls.to_moderate = cls.pictures[0] cls.to_moderate.is_moderated = False cls.to_moderate.save() cls.moderator = baker.make( User, groups=[Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)] ) cls.simple_user = subscriber_user.make() def setUp(self): cache.clear() def test_moderation_page_sas_admin(self): """Test that a moderator can see the pictures needing moderation.""" self.client.force_login(self.moderator) res = self.client.get(reverse("sas:moderation")) assert res.status_code == 200 assert len(res.context_data["pictures"]) == 1 assert res.context_data["pictures"][0] == self.to_moderate def test_moderation_page_forbidden(self): self.client.force_login(self.simple_user) res = self.client.get(reverse("sas:moderation")) assert res.status_code == 403 def test_moderate_album(self): self.client.force_login(self.moderator) url = reverse("sas:moderation") album = baker.make( Album, is_moderated=False, parent_id=settings.SITH_SAS_ROOT_DIR_ID ) res = self.client.post(url, data={"album_id": album.id, "moderate": ""}) assertRedirects(res, url) album.refresh_from_db() assert album.is_moderated def test_moderate_picture(self): self.client.force_login(self.moderator) res = self.client.get( reverse("core:file_moderate", kwargs={"file_id": self.to_moderate.id}), data={"next": self.pictures[1].get_absolute_url()}, ) assertRedirects(res, self.pictures[1].get_absolute_url()) self.to_moderate.refresh_from_db() assert self.to_moderate.is_moderated def test_delete_picture(self): self.client.force_login(self.moderator) res = self.client.post( reverse("core:file_delete", kwargs={"file_id": self.to_moderate.id}) ) assert res.status_code == 302 assert not Picture.objects.filter(pk=self.to_moderate.id).exists() def test_moderation_action_non_authorized_user(self): """Test that a non-authorized user cannot moderate a picture.""" self.client.force_login(self.simple_user) res = self.client.post( reverse("core:file_moderate", kwargs={"file_id": self.to_moderate.id}), ) assert res.status_code == 403 self.to_moderate.refresh_from_db() assert not self.to_moderate.is_moderated res = self.client.post( reverse("core:file_delete", kwargs={"file_id": self.to_moderate.id}), ) assert res.status_code == 403 assert Picture.objects.filter(pk=self.to_moderate.id).exists() def test_request_moderation_form_access(self): """Test that regular can access the form to ask for moderation.""" self.client.force_login(self.simple_user) res = self.client.get( reverse( "sas:picture_ask_removal", kwargs={"picture_id": self.pictures[1].id} ), ) assert res.status_code == 200 def test_request_moderation_form_submit(self): """Test that moderation requests are created.""" self.client.force_login(self.simple_user) message = "J'aime pas cette photo (ni la Cocarde)." url = reverse( "sas:picture_ask_removal", kwargs={"picture_id": self.pictures[1].id} ) res = self.client.post(url, data={"reason": message}) assertRedirects( res, reverse("sas:album", kwargs={"album_id": self.pictures[1].parent_id}) ) assert self.pictures[1].moderation_requests.count() == 1 assert self.pictures[1].moderation_requests.first().reason == message # test that the user cannot ask for moderation twice res = self.client.post(url, data={"reason": message}) assert res.status_code == 200 assert self.pictures[1].moderation_requests.count() == 1 assertInHTML( '", res.text, ) @pytest.mark.django_db class TestUserPicture: def test_anonymous_user_unauthorized(self, client): """An anonymous user shouldn't have access to an user's photo page.""" response = client.get( reverse( "sas:user_pictures", kwargs={"user_id": User.objects.get(username="sli").pk}, ) ) assert response.status_code == 403 @pytest.mark.parametrize( ("username", "status"), [ ("guy", 403), ("root", 200), ("skia", 200), ("sli", 200), ], ) def test_page_is_working(self, client, username, status): """Only user that subscribed (or admins) should be able to see the page.""" # Test for simple user client.force_login(User.objects.get(username=username)) response = client.get( reverse( "sas:user_pictures", kwargs={"user_id": User.objects.get(username="sli").pk}, ) ) assert response.status_code == status