254 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			254 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Python
		
	
	
	
| import os
 | |
| 
 | |
| import django_filters
 | |
| import PIL
 | |
| from django.core.files.uploadedfile import SimpleUploadedFile
 | |
| from django.urls import reverse
 | |
| 
 | |
| from funkwhale_api.common import serializers, utils
 | |
| from funkwhale_api.federation import utils as federation_utils
 | |
| from funkwhale_api.users import models
 | |
| 
 | |
| 
 | |
| class FilterSetFixture(django_filters.FilterSet):
 | |
|     class Meta:
 | |
|         model = models.User
 | |
|         fields = ["is_active"]
 | |
| 
 | |
| 
 | |
| class SerializerFixture(serializers.ActionSerializer):
 | |
|     actions = [serializers.Action("test", allow_all=True)]
 | |
|     filterset_class = FilterSetFixture
 | |
| 
 | |
|     def handle_test(self, objects):
 | |
|         return {"hello": "world"}
 | |
| 
 | |
| 
 | |
| class DangerousSerializerFixture(serializers.ActionSerializer):
 | |
|     actions = [
 | |
|         serializers.Action("test", allow_all=True),
 | |
|         serializers.Action("test_dangerous"),
 | |
|     ]
 | |
| 
 | |
|     def handle_test(self, objects):
 | |
|         pass
 | |
| 
 | |
|     def handle_test_dangerous(self, objects):
 | |
|         pass
 | |
| 
 | |
| 
 | |
| class DeleteOnlyInactiveSerializerFixture(serializers.ActionSerializer):
 | |
|     actions = [
 | |
|         serializers.Action(
 | |
|             "test", allow_all=True, qs_filter=lambda qs: qs.filter(is_active=False)
 | |
|         )
 | |
|     ]
 | |
|     filterset_class = FilterSetFixture
 | |
| 
 | |
|     def handle_test(self, objects):
 | |
|         pass
 | |
| 
 | |
| 
 | |
| def test_action_serializer_validates_action():
 | |
|     data = {"objects": "all", "action": "nope"}
 | |
|     serializer = SerializerFixture(data, queryset=models.User.objects.none())
 | |
| 
 | |
|     assert serializer.is_valid() is False
 | |
|     assert "action" in serializer.errors
 | |
| 
 | |
| 
 | |
| def test_action_serializer_validates_objects():
 | |
|     data = {"objects": "nope", "action": "test"}
 | |
|     serializer = SerializerFixture(data, queryset=models.User.objects.none())
 | |
| 
 | |
|     assert serializer.is_valid() is False
 | |
|     assert "objects" in serializer.errors
 | |
| 
 | |
| 
 | |
| def test_action_serializers_objects_clean_ids(factories):
 | |
|     user1 = factories["users.User"]()
 | |
|     factories["users.User"]()
 | |
| 
 | |
|     data = {"objects": [user1.pk], "action": "test"}
 | |
|     serializer = SerializerFixture(data, queryset=models.User.objects.all())
 | |
| 
 | |
|     assert serializer.is_valid(raise_exception=True) is True
 | |
|     assert list(serializer.validated_data["objects"]) == [user1]
 | |
| 
 | |
| 
 | |
| def test_action_serializers_objects_clean_all(factories):
 | |
|     user1 = factories["users.User"]()
 | |
|     user2 = factories["users.User"]()
 | |
| 
 | |
|     data = {"objects": "all", "action": "test"}
 | |
|     serializer = SerializerFixture(data, queryset=models.User.objects.all())
 | |
| 
 | |
|     assert serializer.is_valid(raise_exception=True) is True
 | |
|     assert list(serializer.validated_data["objects"]) == [user1, user2]
 | |
| 
 | |
| 
 | |
| def test_action_serializers_save(factories, mocker):
 | |
|     handler = mocker.spy(SerializerFixture, "handle_test")
 | |
|     factories["users.User"]()
 | |
|     factories["users.User"]()
 | |
| 
 | |
|     data = {"objects": "all", "action": "test"}
 | |
|     serializer = SerializerFixture(data, queryset=models.User.objects.all())
 | |
| 
 | |
|     assert serializer.is_valid(raise_exception=True) is True
 | |
|     result = serializer.save()
 | |
|     assert result == {"updated": 2, "action": "test", "result": {"hello": "world"}}
 | |
|     handler.assert_called_once()
 | |
| 
 | |
| 
 | |
| def test_action_serializers_filterset(factories):
 | |
|     factories["users.User"](is_active=False)
 | |
|     user2 = factories["users.User"](is_active=True)
 | |
| 
 | |
|     data = {"objects": "all", "action": "test", "filters": {"is_active": True}}
 | |
|     serializer = SerializerFixture(data, queryset=models.User.objects.all())
 | |
| 
 | |
|     assert serializer.is_valid(raise_exception=True) is True
 | |
|     assert list(serializer.validated_data["objects"]) == [user2]
 | |
| 
 | |
| 
 | |
| def test_action_serializers_validates_at_least_one_object():
 | |
|     data = {"objects": "all", "action": "test"}
 | |
|     serializer = SerializerFixture(data, queryset=models.User.objects.none())
 | |
| 
 | |
|     assert serializer.is_valid() is False
 | |
|     assert "non_field_errors" in serializer.errors
 | |
| 
 | |
| 
 | |
| def test_dangerous_actions_refuses_all(factories):
 | |
|     factories["users.User"]()
 | |
|     data = {"objects": "all", "action": "test_dangerous"}
 | |
|     serializer = DangerousSerializerFixture(data, queryset=models.User.objects.all())
 | |
| 
 | |
|     assert serializer.is_valid() is False
 | |
|     assert "non_field_errors" in serializer.errors
 | |
| 
 | |
| 
 | |
| def test_action_serializers_can_require_filter(factories):
 | |
|     user1 = factories["users.User"](is_active=False)
 | |
|     factories["users.User"](is_active=True)
 | |
| 
 | |
|     data = {"objects": "all", "action": "test"}
 | |
|     serializer = DeleteOnlyInactiveSerializerFixture(
 | |
|         data, queryset=models.User.objects.all()
 | |
|     )
 | |
| 
 | |
|     assert serializer.is_valid(raise_exception=True) is True
 | |
|     assert list(serializer.validated_data["objects"]) == [user1]
 | |
| 
 | |
| 
 | |
| def test_track_fields_for_update(mocker):
 | |
|     @serializers.track_fields_for_update("field1", "field2")
 | |
|     class S(serializers.serializers.Serializer):
 | |
|         field1 = serializers.serializers.CharField()
 | |
|         field2 = serializers.serializers.CharField()
 | |
| 
 | |
|         def update(self, obj, validated_data):
 | |
|             for key, value in validated_data.items():
 | |
|                 setattr(obj, key, value)
 | |
|             return obj
 | |
| 
 | |
|         on_updated_fields = mocker.stub()
 | |
| 
 | |
|     class Obj:
 | |
|         field1 = "value1"
 | |
|         field2 = "value2"
 | |
| 
 | |
|     obj = Obj()
 | |
|     serializer = S(obj, data={"field1": "newvalue1", "field2": "newvalue2"})
 | |
|     assert serializer.is_valid(raise_exception=True)
 | |
|     serializer.save()
 | |
| 
 | |
|     serializer.on_updated_fields.assert_called_once_with(
 | |
|         obj,
 | |
|         {"field1": "value1", "field2": "value2"},
 | |
|         {"field1": "newvalue1", "field2": "newvalue2"},
 | |
|     )
 | |
| 
 | |
| 
 | |
| def test_strip_exif_field():
 | |
|     source_path = os.path.join(os.path.dirname(__file__), "exif.jpg")
 | |
|     source = PIL.Image.open(source_path)
 | |
| 
 | |
|     assert bool(source._getexif())
 | |
| 
 | |
|     with open(source_path, "rb") as f:
 | |
|         uploaded = SimpleUploadedFile("source.jpg", f.read(), content_type="image/jpeg")
 | |
|     field = serializers.StripExifImageField()
 | |
| 
 | |
|     cleaned = PIL.Image.open(field.to_internal_value(uploaded))
 | |
|     assert cleaned._getexif() is None
 | |
| 
 | |
| 
 | |
| def test_attachment_serializer_existing_file(factories, to_api_date):
 | |
|     attachment = factories["common.Attachment"]()
 | |
|     expected = {
 | |
|         "uuid": str(attachment.uuid),
 | |
|         "size": attachment.size,
 | |
|         "mimetype": attachment.mimetype,
 | |
|         "creation_date": to_api_date(attachment.creation_date),
 | |
|         "urls": {
 | |
|             "source": attachment.url,
 | |
|             "original": federation_utils.full_url(attachment.file.url),
 | |
|             "medium_square_crop": federation_utils.full_url(
 | |
|                 attachment.file.crop["200x200"].url
 | |
|             ),
 | |
|             "large_square_crop": federation_utils.full_url(
 | |
|                 attachment.file.crop["600x600"].url
 | |
|             ),
 | |
|         },
 | |
|     }
 | |
| 
 | |
|     serializer = serializers.AttachmentSerializer(attachment)
 | |
| 
 | |
|     assert serializer.data == expected
 | |
| 
 | |
| 
 | |
| def test_attachment_serializer_remote_file(factories, to_api_date):
 | |
|     attachment = factories["common.Attachment"](file=None)
 | |
|     proxy_url = reverse("api:v1:attachments-proxy", kwargs={"uuid": attachment.uuid})
 | |
|     expected = {
 | |
|         "uuid": str(attachment.uuid),
 | |
|         "size": attachment.size,
 | |
|         "mimetype": attachment.mimetype,
 | |
|         "creation_date": to_api_date(attachment.creation_date),
 | |
|         # everything is the same, except for the urls field because:
 | |
|         #  - the file isn't available on the local pod
 | |
|         #  - we need to return different URLs so that the client can trigger
 | |
|         #    a fetch and get redirected to the desired version
 | |
|         #
 | |
|         "urls": {
 | |
|             "source": attachment.url,
 | |
|             "original": federation_utils.full_url(proxy_url + "?next=original"),
 | |
|             "medium_square_crop": federation_utils.full_url(
 | |
|                 proxy_url + "?next=medium_square_crop"
 | |
|             ),
 | |
|             "large_square_crop": federation_utils.full_url(
 | |
|                 proxy_url + "?next=large_square_crop"
 | |
|             ),
 | |
|         },
 | |
|     }
 | |
| 
 | |
|     serializer = serializers.AttachmentSerializer(attachment)
 | |
| 
 | |
|     assert serializer.data == expected
 | |
| 
 | |
| 
 | |
| def test_content_serializer(factories):
 | |
|     content = factories["common.Content"]()
 | |
| 
 | |
|     expected = {
 | |
|         "text": content.text,
 | |
|         "content_type": content.content_type,
 | |
|         "html": utils.render_html(content.text, content.content_type),
 | |
|     }
 | |
| 
 | |
|     serializer = serializers.ContentSerializer(content)
 | |
| 
 | |
|     assert serializer.data == expected
 |