Sith/core/tests/test_files.py

293 lines
10 KiB
Python

from io import BytesIO
from itertools import cycle
from typing import Callable
from uuid import uuid4
import pytest
from django.core.cache import cache
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import Client, TestCase
from django.urls import reverse
from model_bakery import baker
from model_bakery.recipe import Recipe, foreign_key
from PIL import Image
from pytest_django.asserts import assertNumQueries
from core.baker_recipes import board_user, old_subscriber_user, subscriber_user
from core.models import Group, SithFile, User
from sas.models import Picture
from sith import settings
@pytest.mark.django_db
class TestImageAccess:
@pytest.mark.parametrize(
"user_factory",
[
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, groups=[Group.objects.get(pk=settings.SITH_GROUP_COM_ADMIN_ID)]
),
],
)
def test_sas_image_access(self, user_factory: Callable[[], User]):
"""Test that only authorized users can access the sas image."""
user = user_factory()
picture: SithFile = baker.make(
Picture, parent=SithFile.objects.get(pk=settings.SITH_SAS_ROOT_DIR_ID)
)
assert picture.is_owned_by(user)
def test_sas_image_access_owner(self):
"""Test that the owner of the image can access it."""
user = baker.make(User)
picture: Picture = baker.make(Picture, owner=user)
assert picture.is_owned_by(user)
@pytest.mark.parametrize(
"user_factory",
[
lambda: baker.make(User),
subscriber_user.make,
old_subscriber_user.make,
board_user.make,
],
)
def test_sas_image_access_forbidden(self, user_factory: Callable[[], User]):
cache.clear()
user = user_factory()
owner = baker.make(User)
picture: Picture = baker.make(Picture, owner=owner)
assert not picture.is_owned_by(user)
@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(
"core: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(
"core:user_pictures",
kwargs={"user_id": User.objects.get(username="sli").pk},
)
)
assert response.status_code == status
# TODO: many tests on the pages:
# - renaming a page
# - changing a page's parent --> check that page's children's full_name
# - changing the different groups of the page
class TestFileHandling(TestCase):
@classmethod
def setUpTestData(cls):
cls.subscriber = User.objects.get(username="subscriber")
def setUp(self):
self.client.login(username="subscriber", password="plop")
def test_create_folder_home(self):
response = self.client.post(
reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}),
{"folder_name": "GUY_folder_test"},
)
assert response.status_code == 302
response = self.client.get(
reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id})
)
assert response.status_code == 200
assert "GUY_folder_test</a>" in str(response.content)
def test_upload_file_home(self):
with open("/bin/ls", "rb") as f:
response = self.client.post(
reverse(
"core:file_detail", kwargs={"file_id": self.subscriber.home.id}
),
{"file_field": f},
)
assert response.status_code == 302
response = self.client.get(
reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id})
)
assert response.status_code == 200
assert "ls</a>" in str(response.content)
@pytest.mark.django_db
class TestFileModerationView:
"""Test access to file moderation view"""
@pytest.mark.parametrize(
("user_factory", "status_code"),
[
(lambda: None, 403), # Anonymous user
(lambda: baker.make(User, is_superuser=True), 200),
(lambda: baker.make(User), 403),
(lambda: subscriber_user.make(), 403),
(lambda: old_subscriber_user.make(), 403),
(lambda: board_user.make(), 403),
],
)
def test_view_access(
self, client: Client, user_factory: Callable[[], User | None], status_code: int
):
user = user_factory()
if user: # if None, then it's an anonymous user
client.force_login(user_factory())
assert client.get(reverse("core:file_moderation")).status_code == status_code
@pytest.mark.django_db
class TestUserProfilePicture:
"""Test interactions with user's profile picture."""
@pytest.fixture
def user(self) -> User:
pict = foreign_key(Recipe(SithFile), one_to_one=True)
return subscriber_user.extend(profile_pict=pict).make()
@staticmethod
def delete_picture_request(user: User, client: Client):
return client.post(
reverse(
"core:file_delete",
kwargs={"file_id": user.profile_pict.pk, "popup": ""},
)
+ f"?next={user.get_absolute_url()}"
)
@pytest.mark.parametrize(
"user_factory",
[lambda: baker.make(User, is_superuser=True), board_user.make],
)
def test_delete_picture_successful(
self, user: User, user_factory: Callable[[], User], client: Client
):
"""Test that root and board members can delete a user's profile picture."""
cache.clear()
operator = user_factory()
client.force_login(operator)
res = self.delete_picture_request(user, client)
assert res.status_code == 302
assert res.url == user.get_absolute_url()
user.refresh_from_db()
assert user.profile_pict is None
@pytest.mark.parametrize(
"user_factory",
[lambda: baker.make(User), subscriber_user.make],
)
def test_delete_picture_unauthorized(
self, user: User, user_factory, client: Client
):
"""Test that regular users can't delete a user's profile picture."""
cache.clear()
operator = user_factory()
client.force_login(operator)
original_picture = user.profile_pict
res = self.delete_picture_request(user, client)
assert res.status_code == 403
user.refresh_from_db()
assert user.profile_pict is not None
assert user.profile_pict == original_picture
def test_user_cannot_delete_own_picture(self, user: User, client: Client):
"""Test that a user can't delete their own profile picture."""
cache.clear()
client.force_login(user)
original_picture = user.profile_pict
res = self.delete_picture_request(user, client)
assert res.status_code == 403
user.refresh_from_db()
assert user.profile_pict is not None
assert user.profile_pict == original_picture
def test_user_set_own_picture(self, user: User, client: Client):
"""Test that a user can set their own profile picture if they have none."""
user.profile_pict.delete()
user.profile_pict = None
user.save()
cache.clear()
client.force_login(user)
img = Image.new("RGB", (10, 10))
content = BytesIO()
img.save(content, format="JPEG")
name = str(uuid4())
res = client.post(
reverse("core:user_edit", kwargs={"user_id": user.pk}),
data={
# birthdate, email and tshirt_size are required by the form
"date_of_birth": "1990-01-01",
"email": f"{uuid4()}@gmail.com",
"tshirt_size": "M",
"profile_pict": SimpleUploadedFile(
f"{name}.jpg", content.getvalue(), content_type="image/jpeg"
),
},
)
assert res.status_code == 302
user.refresh_from_db()
assert user.profile_pict is not None
# uploaded images should be converted to WEBP
assert Image.open(user.profile_pict.file).format == "WEBP"
@pytest.mark.django_db
def test_apply_rights_recursively():
"""Test that the apply_rights_recursively method works as intended."""
files = [baker.make(SithFile)]
files.extend(baker.make(SithFile, _quantity=3, parent=files[0], _bulk_create=True))
files.extend(
baker.make(SithFile, _quantity=3, parent=iter(files[1:4]), _bulk_create=True)
)
files.extend(
baker.make(SithFile, _quantity=6, parent=cycle(files[4:7]), _bulk_create=True)
)
groups = list(baker.make(Group, _quantity=7))
files[0].view_groups.set(groups[:3])
files[0].edit_groups.set(groups[2:6])
# those groups should be erased after the function call
files[1].view_groups.set(groups[6:])
with assertNumQueries(10):
# 1 query for each level of depth (here 4)
# 1 query to get the view_groups of the first file
# 1 query to delete the previous view_groups
# 1 query apply the new view_groups
# same 3 queries for the edit_groups
files[0].apply_rights_recursively()
for file in SithFile.objects.filter(pk__in=[f.pk for f in files]).prefetch_related(
"view_groups", "edit_groups"
):
assert set(file.view_groups.all()) == set(groups[:3])
assert set(file.edit_groups.all()) == set(groups[2:6])