kopia lustrzana https://dev.funkwhale.audio/funkwhale/funkwhale
				
				
				
			See #170: reel2bits compat
							rodzic
							
								
									1d37a2c819
								
							
						
					
					
						commit
						9e8983bb60
					
				|  | @ -164,14 +164,18 @@ def receive(activity, on_behalf_of, inbox_actor=None): | ||||||
|         ) |         ) | ||||||
|         return |         return | ||||||
| 
 | 
 | ||||||
|     local_to_recipients = get_actors_from_audience(activity.get("to", [])) |     local_to_recipients = get_actors_from_audience( | ||||||
|  |         serializer.validated_data.get("to", []) | ||||||
|  |     ) | ||||||
|     local_to_recipients = local_to_recipients.local() |     local_to_recipients = local_to_recipients.local() | ||||||
|     local_to_recipients = local_to_recipients.values_list("pk", flat=True) |     local_to_recipients = local_to_recipients.values_list("pk", flat=True) | ||||||
|     local_to_recipients = list(local_to_recipients) |     local_to_recipients = list(local_to_recipients) | ||||||
|     if inbox_actor: |     if inbox_actor: | ||||||
|         local_to_recipients.append(inbox_actor.pk) |         local_to_recipients.append(inbox_actor.pk) | ||||||
| 
 | 
 | ||||||
|     local_cc_recipients = get_actors_from_audience(activity.get("cc", [])) |     local_cc_recipients = get_actors_from_audience( | ||||||
|  |         serializer.validated_data.get("cc", []) | ||||||
|  |     ) | ||||||
|     local_cc_recipients = local_cc_recipients.local() |     local_cc_recipients = local_cc_recipients.local() | ||||||
|     local_cc_recipients = local_cc_recipients.values_list("pk", flat=True) |     local_cc_recipients = local_cc_recipients.values_list("pk", flat=True) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -232,16 +232,18 @@ class JsonLdSerializer(serializers.Serializer): | ||||||
|     def __init__(self, *args, **kwargs): |     def __init__(self, *args, **kwargs): | ||||||
|         self.jsonld_expand = kwargs.pop("jsonld_expand", True) |         self.jsonld_expand = kwargs.pop("jsonld_expand", True) | ||||||
|         super().__init__(*args, **kwargs) |         super().__init__(*args, **kwargs) | ||||||
|  |         self.jsonld_context = [] | ||||||
| 
 | 
 | ||||||
|     def run_validation(self, data=empty): |     def run_validation(self, data=empty): | ||||||
|         if data and data is not empty: |         if data and data is not empty: | ||||||
| 
 | 
 | ||||||
|  |             self.jsonld_context = data.get("@context", []) | ||||||
|             if self.context.get("expand", self.jsonld_expand): |             if self.context.get("expand", self.jsonld_expand): | ||||||
|                 try: |                 try: | ||||||
|                     data = expand(data) |                     data = expand(data) | ||||||
|                 except ValueError: |                 except ValueError as e: | ||||||
|                     raise serializers.ValidationError( |                     raise serializers.ValidationError( | ||||||
|                         "{} is not a valid jsonld document".format(data) |                         "{} is not a valid jsonld document: {}".format(data, e) | ||||||
|                     ) |                     ) | ||||||
|             try: |             try: | ||||||
|                 config = self.Meta.jsonld_mapping |                 config = self.Meta.jsonld_mapping | ||||||
|  | @ -294,3 +296,15 @@ def first_obj(property, aliases=[]): | ||||||
| 
 | 
 | ||||||
| def raw(property, aliases=[]): | def raw(property, aliases=[]): | ||||||
|     return {"property": property, "aliases": aliases} |     return {"property": property, "aliases": aliases} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def is_present_recursive(data, key): | ||||||
|  |     if isinstance(data, (dict, list)): | ||||||
|  |         for v in data: | ||||||
|  |             if is_present_recursive(v, key): | ||||||
|  |                 return True | ||||||
|  |     else: | ||||||
|  |         if data == key: | ||||||
|  |             return True | ||||||
|  | 
 | ||||||
|  |     return False | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | from rest_framework.negotiation import BaseContentNegotiation | ||||||
| from rest_framework.renderers import JSONRenderer | from rest_framework.renderers import JSONRenderer | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -15,5 +16,19 @@ def get_ap_renderers(): | ||||||
|     ] |     ] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | class IgnoreClientContentNegotiation(BaseContentNegotiation): | ||||||
|  |     def select_parser(self, request, parsers): | ||||||
|  |         """ | ||||||
|  |         Select the first parser in the `.parser_classes` list. | ||||||
|  |         """ | ||||||
|  |         return parsers[0] | ||||||
|  | 
 | ||||||
|  |     def select_renderer(self, request, renderers, format_suffix): | ||||||
|  |         """ | ||||||
|  |         Select the first renderer in the `.renderer_classes` list. | ||||||
|  |         """ | ||||||
|  |         return (renderers[0], renderers[0].media_type) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class WebfingerRenderer(JSONRenderer): | class WebfingerRenderer(JSONRenderer): | ||||||
|     media_type = "application/jrd+json" |     media_type = "application/jrd+json" | ||||||
|  |  | ||||||
|  | @ -134,19 +134,19 @@ def outbox_follow(context): | ||||||
| def outbox_create_audio(context): | def outbox_create_audio(context): | ||||||
|     upload = context["upload"] |     upload = context["upload"] | ||||||
|     channel = upload.library.get_channel() |     channel = upload.library.get_channel() | ||||||
|     upload_serializer = ( |  | ||||||
|         serializers.ChannelUploadSerializer if channel else serializers.UploadSerializer |  | ||||||
|     ) |  | ||||||
|     followers_target = channel.actor if channel else upload.library |     followers_target = channel.actor if channel else upload.library | ||||||
|     actor = channel.actor if channel else upload.library.actor |     actor = channel.actor if channel else upload.library.actor | ||||||
| 
 |     if channel: | ||||||
|     serializer = serializers.ActivitySerializer( |         serializer = serializers.ChannelCreateUploadSerializer(upload) | ||||||
|         { |     else: | ||||||
|             "type": "Create", |         upload_serializer = serializers.UploadSerializer | ||||||
|             "actor": actor.fid, |         serializer = serializers.ActivitySerializer( | ||||||
|             "object": upload_serializer(upload).data, |             { | ||||||
|         } |                 "type": "Create", | ||||||
|     ) |                 "actor": actor.fid, | ||||||
|  |                 "object": upload_serializer(upload).data, | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|     yield { |     yield { | ||||||
|         "type": "Create", |         "type": "Create", | ||||||
|         "actor": actor, |         "actor": actor, | ||||||
|  | @ -163,7 +163,7 @@ def inbox_create_audio(payload, context): | ||||||
|     is_channel = "library" not in payload["object"] |     is_channel = "library" not in payload["object"] | ||||||
|     if is_channel: |     if is_channel: | ||||||
|         channel = context["actor"].get_channel() |         channel = context["actor"].get_channel() | ||||||
|         serializer = serializers.ChannelUploadSerializer( |         serializer = serializers.ChannelCreateUploadSerializer( | ||||||
|             data=payload["object"], context={"channel": channel}, |             data=payload["object"], context={"channel": channel}, | ||||||
|         ) |         ) | ||||||
|     else: |     else: | ||||||
|  |  | ||||||
|  | @ -436,8 +436,8 @@ class ActorSerializer(jsonld.JsonLdSerializer): | ||||||
|         ) |         ) | ||||||
|         if rss_url: |         if rss_url: | ||||||
|             rss_url = rss_url["href"] |             rss_url = rss_url["href"] | ||||||
|         attributed_to = self.validated_data.get("attributedTo") |         attributed_to = self.validated_data.get("attributedTo", actor.fid) | ||||||
|         if rss_url and attributed_to: |         if rss_url: | ||||||
|             # if the actor is attributed to another actor, and there is a RSS url, |             # if the actor is attributed to another actor, and there is a RSS url, | ||||||
|             # then we consider it's a channel |             # then we consider it's a channel | ||||||
|             create_or_update_channel( |             create_or_update_channel( | ||||||
|  | @ -533,6 +533,7 @@ class BaseActivitySerializer(serializers.Serializer): | ||||||
|     id = serializers.URLField(max_length=500, required=False) |     id = serializers.URLField(max_length=500, required=False) | ||||||
|     type = serializers.CharField(max_length=100) |     type = serializers.CharField(max_length=100) | ||||||
|     actor = serializers.URLField(max_length=500) |     actor = serializers.URLField(max_length=500) | ||||||
|  |     object = serializers.JSONField(required=False, allow_null=True) | ||||||
| 
 | 
 | ||||||
|     def validate_actor(self, v): |     def validate_actor(self, v): | ||||||
|         expected = self.context.get("actor") |         expected = self.context.get("actor") | ||||||
|  | @ -555,17 +556,30 @@ class BaseActivitySerializer(serializers.Serializer): | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|     def validate(self, data): |     def validate(self, data): | ||||||
|         data["recipients"] = self.validate_recipients(self.initial_data) |         self.validate_recipients(data, self.initial_data) | ||||||
|         return super().validate(data) |         return super().validate(data) | ||||||
| 
 | 
 | ||||||
|     def validate_recipients(self, payload): |     def validate_recipients(self, data, payload): | ||||||
|         """ |         """ | ||||||
|         Ensure we have at least a to/cc field with valid actors |         Ensure we have at least a to/cc field with valid actors | ||||||
|         """ |         """ | ||||||
|         to = payload.get("to", []) |         data["to"] = payload.get("to", []) | ||||||
|         cc = payload.get("cc", []) |         data["cc"] = payload.get("cc", []) | ||||||
| 
 | 
 | ||||||
|         if not to and not cc and not self.context.get("recipients"): |         if ( | ||||||
|  |             not data["to"] | ||||||
|  |             and data.get("type") in ["Follow", "Accept"] | ||||||
|  |             and data.get("object") | ||||||
|  |         ): | ||||||
|  |             # there isn't always a to field for Accept/Follow | ||||||
|  |             # in their follow activity, so we consider the recipient | ||||||
|  |             # to be the follow object | ||||||
|  |             if data["type"] == "Follow": | ||||||
|  |                 data["to"].append(str(data.get("object"))) | ||||||
|  |             else: | ||||||
|  |                 data["to"].append(data.get("object", {}).get("actor")) | ||||||
|  | 
 | ||||||
|  |         if not data["to"] and not data["cc"] and not self.context.get("recipients"): | ||||||
|             raise serializers.ValidationError( |             raise serializers.ValidationError( | ||||||
|                 "We cannot handle an activity with no recipient" |                 "We cannot handle an activity with no recipient" | ||||||
|             ) |             ) | ||||||
|  | @ -1786,6 +1800,7 @@ class ChannelUploadSerializer(jsonld.JsonLdSerializer): | ||||||
|     content = TruncatedCharField( |     content = TruncatedCharField( | ||||||
|         truncate_length=common_models.CONTENT_TEXT_MAX_LENGTH, |         truncate_length=common_models.CONTENT_TEXT_MAX_LENGTH, | ||||||
|         required=False, |         required=False, | ||||||
|  |         allow_blank=True, | ||||||
|         allow_null=True, |         allow_null=True, | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|  | @ -1951,6 +1966,11 @@ class ChannelCreateUploadSerializer(jsonld.JsonLdSerializer): | ||||||
|         return { |         return { | ||||||
|             "@context": jsonld.get_default_context(), |             "@context": jsonld.get_default_context(), | ||||||
|             "type": "Create", |             "type": "Create", | ||||||
|  |             "id": utils.full_url( | ||||||
|  |                 reverse( | ||||||
|  |                     "federation:music:uploads-activity", kwargs={"uuid": upload.uuid} | ||||||
|  |                 ) | ||||||
|  |             ), | ||||||
|             "actor": upload.library.channel.actor.fid, |             "actor": upload.library.channel.actor.fid, | ||||||
|             "object": ChannelUploadSerializer( |             "object": ChannelUploadSerializer( | ||||||
|                 upload, context={"include_ap_context": False} |                 upload, context={"include_ap_context": False} | ||||||
|  |  | ||||||
|  | @ -404,19 +404,25 @@ def fetch(fetch_obj): | ||||||
|     if isinstance(obj, models.Actor) and obj.get_channel(): |     if isinstance(obj, models.Actor) and obj.get_channel(): | ||||||
|         obj = obj.get_channel() |         obj = obj.get_channel() | ||||||
|         if obj.actor.outbox_url: |         if obj.actor.outbox_url: | ||||||
|             # first page fetch is synchronous, so that at least some data is available |             try: | ||||||
|             # in the UI after subscription |                 # first page fetch is synchronous, so that at least some data is available | ||||||
|             result = fetch_collection( |                 # in the UI after subscription | ||||||
|                 obj.actor.outbox_url, channel_id=obj.pk, max_pages=1, |                 result = fetch_collection( | ||||||
|             ) |                     obj.actor.outbox_url, channel_id=obj.pk, max_pages=1, | ||||||
|             if result.get("next_page"): |  | ||||||
|                 # additional pages are fetched in the background |  | ||||||
|                 result = fetch_collection.delay( |  | ||||||
|                     result["next_page"], |  | ||||||
|                     channel_id=obj.pk, |  | ||||||
|                     max_pages=settings.FEDERATION_COLLECTION_MAX_PAGES - 1, |  | ||||||
|                     is_page=True, |  | ||||||
|                 ) |                 ) | ||||||
|  |             except Exception: | ||||||
|  |                 logger.exception( | ||||||
|  |                     "Error while fetching actor outbox: %s", obj.actor.outbox.url | ||||||
|  |                 ) | ||||||
|  |             else: | ||||||
|  |                 if result.get("next_page"): | ||||||
|  |                     # additional pages are fetched in the background | ||||||
|  |                     result = fetch_collection.delay( | ||||||
|  |                         result["next_page"], | ||||||
|  |                         channel_id=obj.pk, | ||||||
|  |                         max_pages=settings.FEDERATION_COLLECTION_MAX_PAGES - 1, | ||||||
|  |                         is_page=True, | ||||||
|  |                     ) | ||||||
| 
 | 
 | ||||||
|     fetch_obj.object = obj |     fetch_obj.object = obj | ||||||
|     fetch_obj.status = "finished" |     fetch_obj.status = "finished" | ||||||
|  |  | ||||||
|  | @ -52,7 +52,11 @@ class SharedViewSet(FederationMixin, viewsets.GenericViewSet): | ||||||
|     authentication_classes = [authentication.SignatureAuthentication] |     authentication_classes = [authentication.SignatureAuthentication] | ||||||
|     renderer_classes = renderers.get_ap_renderers() |     renderer_classes = renderers.get_ap_renderers() | ||||||
| 
 | 
 | ||||||
|     @action(methods=["post"], detail=False) |     @action( | ||||||
|  |         methods=["post"], | ||||||
|  |         detail=False, | ||||||
|  |         content_negotiation_class=renderers.IgnoreClientContentNegotiation, | ||||||
|  |     ) | ||||||
|     def inbox(self, request, *args, **kwargs): |     def inbox(self, request, *args, **kwargs): | ||||||
|         if request.method.lower() == "post" and request.actor is None: |         if request.method.lower() == "post" and request.actor is None: | ||||||
|             raise exceptions.AuthenticationFailed( |             raise exceptions.AuthenticationFailed( | ||||||
|  | @ -88,7 +92,11 @@ class ActorViewSet(FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericV | ||||||
|         serializer = self.get_serializer(instance) |         serializer = self.get_serializer(instance) | ||||||
|         return response.Response(serializer.data) |         return response.Response(serializer.data) | ||||||
| 
 | 
 | ||||||
|     @action(methods=["get", "post"], detail=True) |     @action( | ||||||
|  |         methods=["get", "post"], | ||||||
|  |         detail=True, | ||||||
|  |         content_negotiation_class=renderers.IgnoreClientContentNegotiation, | ||||||
|  |     ) | ||||||
|     def inbox(self, request, *args, **kwargs): |     def inbox(self, request, *args, **kwargs): | ||||||
|         inbox_actor = self.get_object() |         inbox_actor = self.get_object() | ||||||
|         if request.method.lower() == "post" and request.actor is None: |         if request.method.lower() == "post" and request.actor is None: | ||||||
|  | @ -352,6 +360,16 @@ class MusicUploadViewSet( | ||||||
|             return serializers.ChannelUploadSerializer(obj) |             return serializers.ChannelUploadSerializer(obj) | ||||||
|         return super().get_serializer(obj) |         return super().get_serializer(obj) | ||||||
| 
 | 
 | ||||||
|  |     @action( | ||||||
|  |         methods=["get"], | ||||||
|  |         detail=True, | ||||||
|  |         content_negotiation_class=renderers.IgnoreClientContentNegotiation, | ||||||
|  |     ) | ||||||
|  |     def activity(self, request, *args, **kwargs): | ||||||
|  |         object = self.get_object() | ||||||
|  |         serializer = serializers.ChannelCreateUploadSerializer(object) | ||||||
|  |         return response.Response(serializer.data) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class MusicArtistViewSet( | class MusicArtistViewSet( | ||||||
|     FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet |     FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet | ||||||
|  |  | ||||||
|  | @ -659,7 +659,7 @@ class OembedSerializer(serializers.Serializer): | ||||||
|             if track.attachment_cover: |             if track.attachment_cover: | ||||||
|                 data[ |                 data[ | ||||||
|                     "thumbnail_url" |                     "thumbnail_url" | ||||||
|                 ] = track.album.attachment_cover.download_url_medium_square_crop |                 ] = track.attachment_cover.download_url_medium_square_crop | ||||||
|                 data["thumbnail_width"] = 200 |                 data["thumbnail_width"] = 200 | ||||||
|                 data["thumbnail_height"] = 200 |                 data["thumbnail_height"] = 200 | ||||||
|             elif track.album and track.album.attachment_cover: |             elif track.album and track.album.attachment_cover: | ||||||
|  |  | ||||||
|  | @ -69,6 +69,28 @@ def test_receive_validates_basic_attributes_and_stores_activity( | ||||||
|     assert serializer_init.call_args[1]["data"] == a |     assert serializer_init.call_args[1]["data"] == a | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def test_receive_uses_follow_object_if_no_audience_provided( | ||||||
|  |     mrf_inbox_registry, factories, now, mocker | ||||||
|  | ): | ||||||
|  |     mocker.patch.object( | ||||||
|  |         activity.InboxRouter, "get_matching_handlers", return_value=True | ||||||
|  |     ) | ||||||
|  |     mocker.patch("funkwhale_api.common.utils.on_commit") | ||||||
|  |     local_to_actor = factories["users.User"]().create_actor() | ||||||
|  |     remote_actor = factories["federation.Actor"]() | ||||||
|  |     a = { | ||||||
|  |         "@context": [], | ||||||
|  |         "actor": remote_actor.fid, | ||||||
|  |         "type": "Follow", | ||||||
|  |         "id": "https://test.activity", | ||||||
|  |         "object": local_to_actor.fid, | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     activity.receive(activity=a, on_behalf_of=remote_actor, inbox_actor=None) | ||||||
|  | 
 | ||||||
|  |     assert models.InboxItem.objects.filter(actor=local_to_actor, type="to").exists() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def test_receive_uses_mrf_returned_payload(mrf_inbox_registry, factories, now, mocker): | def test_receive_uses_mrf_returned_payload(mrf_inbox_registry, factories, now, mocker): | ||||||
|     mocker.patch.object( |     mocker.patch.object( | ||||||
|         activity.InboxRouter, "get_matching_handlers", return_value=True |         activity.InboxRouter, "get_matching_handlers", return_value=True | ||||||
|  |  | ||||||
|  | @ -305,13 +305,7 @@ def test_outbox_create_audio_channel(factories, mocker): | ||||||
|     channel = factories["audio.Channel"]() |     channel = factories["audio.Channel"]() | ||||||
|     upload = factories["music.Upload"](library=channel.library) |     upload = factories["music.Upload"](library=channel.library) | ||||||
|     activity = list(routes.outbox_create_audio({"upload": upload}))[0] |     activity = list(routes.outbox_create_audio({"upload": upload}))[0] | ||||||
|     serializer = serializers.ActivitySerializer( |     serializer = serializers.ChannelCreateUploadSerializer(upload) | ||||||
|         { |  | ||||||
|             "type": "Create", |  | ||||||
|             "object": serializers.ChannelUploadSerializer(upload).data, |  | ||||||
|             "actor": channel.actor.fid, |  | ||||||
|         } |  | ||||||
|     ) |  | ||||||
|     expected = serializer.data |     expected = serializer.data | ||||||
|     expected["to"] = [{"type": "followers", "target": upload.library.channel.actor}] |     expected["to"] = [{"type": "followers", "target": upload.library.channel.actor}] | ||||||
| 
 | 
 | ||||||
|  | @ -360,11 +354,11 @@ def test_inbox_create_audio_channel(factories, mocker): | ||||||
|         "@context": jsonld.get_default_context(), |         "@context": jsonld.get_default_context(), | ||||||
|         "type": "Create", |         "type": "Create", | ||||||
|         "actor": channel.actor.fid, |         "actor": channel.actor.fid, | ||||||
|         "object": serializers.ChannelUploadSerializer(upload).data, |         "object": serializers.ChannelCreateUploadSerializer(upload).data, | ||||||
|     } |     } | ||||||
|     upload.delete() |     upload.delete() | ||||||
|     init = mocker.spy(serializers.ChannelUploadSerializer, "__init__") |     init = mocker.spy(serializers.ChannelCreateUploadSerializer, "__init__") | ||||||
|     save = mocker.spy(serializers.ChannelUploadSerializer, "save") |     save = mocker.spy(serializers.ChannelCreateUploadSerializer, "save") | ||||||
|     result = routes.inbox_create_audio( |     result = routes.inbox_create_audio( | ||||||
|         payload, |         payload, | ||||||
|         context={"actor": channel.actor, "raise_exception": True, "activity": activity}, |         context={"actor": channel.actor, "raise_exception": True, "activity": activity}, | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ import pytest | ||||||
| import uuid | import uuid | ||||||
| 
 | 
 | ||||||
| from django.core.paginator import Paginator | from django.core.paginator import Paginator | ||||||
|  | from django.urls import reverse | ||||||
| from django.utils import timezone | from django.utils import timezone | ||||||
| 
 | 
 | ||||||
| from funkwhale_api.common import utils as common_utils | from funkwhale_api.common import utils as common_utils | ||||||
|  | @ -1399,19 +1400,19 @@ def test_activity_serializer_validate_recipients_empty(db): | ||||||
|     s = serializers.BaseActivitySerializer() |     s = serializers.BaseActivitySerializer() | ||||||
| 
 | 
 | ||||||
|     with pytest.raises(serializers.serializers.ValidationError): |     with pytest.raises(serializers.serializers.ValidationError): | ||||||
|         s.validate_recipients({}) |         s.validate_recipients({}, {}) | ||||||
| 
 | 
 | ||||||
|     with pytest.raises(serializers.serializers.ValidationError): |     with pytest.raises(serializers.serializers.ValidationError): | ||||||
|         s.validate_recipients({"to": []}) |         s.validate_recipients({"to": []}, {}) | ||||||
| 
 | 
 | ||||||
|     with pytest.raises(serializers.serializers.ValidationError): |     with pytest.raises(serializers.serializers.ValidationError): | ||||||
|         s.validate_recipients({"cc": []}) |         s.validate_recipients({"cc": []}, {}) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_activity_serializer_validate_recipients_context(db): | def test_activity_serializer_validate_recipients_context(db): | ||||||
|     s = serializers.BaseActivitySerializer(context={"recipients": ["dummy"]}) |     s = serializers.BaseActivitySerializer(context={"recipients": ["dummy"]}) | ||||||
| 
 | 
 | ||||||
|     assert s.validate_recipients({}) is None |     assert s.validate_recipients({}, {}) is None | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_track_serializer_update_license(factories): | def test_track_serializer_update_license(factories): | ||||||
|  | @ -1879,6 +1880,9 @@ def test_channel_create_upload_serializer(factories): | ||||||
|     expected = { |     expected = { | ||||||
|         "@context": jsonld.get_default_context(), |         "@context": jsonld.get_default_context(), | ||||||
|         "type": "Create", |         "type": "Create", | ||||||
|  |         "id": utils.full_url( | ||||||
|  |             reverse("federation:music:uploads-activity", kwargs={"uuid": upload.uuid}) | ||||||
|  |         ), | ||||||
|         "actor": upload.library.channel.actor.fid, |         "actor": upload.library.channel.actor.fid, | ||||||
|         "object": serializers.ChannelUploadSerializer( |         "object": serializers.ChannelUploadSerializer( | ||||||
|             upload, context={"include_ap_context": False} |             upload, context={"include_ap_context": False} | ||||||
|  |  | ||||||
|  | @ -56,3 +56,88 @@ def test_pleroma_actor_from_ap(factories): | ||||||
|     assert actor.private_key is None |     assert actor.private_key is None | ||||||
|     assert actor.public_key == payload["publicKey"]["publicKeyPem"] |     assert actor.public_key == payload["publicKey"]["publicKeyPem"] | ||||||
|     assert actor.domain_id == "test.federation" |     assert actor.domain_id == "test.federation" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_reel2bits_channel_from_actor_ap(db, mocker): | ||||||
|  |     mocker.patch("funkwhale_api.federation.tasks.update_domain_nodeinfo") | ||||||
|  |     payload = { | ||||||
|  |         "@context": [ | ||||||
|  |             "https://www.w3.org/ns/activitystreams", | ||||||
|  |             "https://w3id.org/security/v1", | ||||||
|  |             { | ||||||
|  |                 "Hashtag": "as:Hashtag", | ||||||
|  |                 "PropertyValue": "schema:PropertyValue", | ||||||
|  |                 "artwork": "reel2bits:artwork", | ||||||
|  |                 "featured": "toot:featured", | ||||||
|  |                 "genre": "reel2bits:genre", | ||||||
|  |                 "licence": "reel2bits:licence", | ||||||
|  |                 "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", | ||||||
|  |                 "reel2bits": "http://reel2bits.org/ns#", | ||||||
|  |                 "schema": "http://schema.org#", | ||||||
|  |                 "sensitive": "as:sensitive", | ||||||
|  |                 "tags": "reel2bits:tags", | ||||||
|  |                 "toot": "http://joinmastodon.org/ns#", | ||||||
|  |                 "transcode_url": "reel2bits:transcode_url", | ||||||
|  |                 "transcoded": "reel2bits:transcoded", | ||||||
|  |                 "value": "schema:value", | ||||||
|  |             }, | ||||||
|  |         ], | ||||||
|  |         "endpoints": {"sharedInbox": "https://r2b.example/inbox"}, | ||||||
|  |         "followers": "https://r2b.example/user/anna/followers", | ||||||
|  |         "following": "https://r2b.example/user/anna/followings", | ||||||
|  |         "icon": { | ||||||
|  |             "type": "Image", | ||||||
|  |             "url": "https://r2b.example/uploads/avatars/anna/f4930.jpg", | ||||||
|  |         }, | ||||||
|  |         "id": "https://r2b.example/user/anna", | ||||||
|  |         "inbox": "https://r2b.example/user/anna/inbox", | ||||||
|  |         "manuallyApprovesFollowers": False, | ||||||
|  |         "name": "Anna", | ||||||
|  |         "outbox": "https://r2b.example/user/anna/outbox", | ||||||
|  |         "preferredUsername": "anna", | ||||||
|  |         "publicKey": { | ||||||
|  |             "id": "https://r2b.example/user/anna#main-key", | ||||||
|  |             "owner": "https://r2b.example/user/anna", | ||||||
|  |             "publicKeyPem": "MIIBIxaeikqh", | ||||||
|  |         }, | ||||||
|  |         "type": "Person", | ||||||
|  |         "url": [ | ||||||
|  |             { | ||||||
|  |                 "type": "Link", | ||||||
|  |                 "mediaType": "text/html", | ||||||
|  |                 "href": "https://r2b.example/@anna", | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 "type": "Link", | ||||||
|  |                 "mediaType": "application/rss+xml", | ||||||
|  |                 "href": "https://r2b.example/@anna.rss", | ||||||
|  |             }, | ||||||
|  |         ], | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     serializer = serializers.ActorSerializer(data=payload) | ||||||
|  |     assert serializer.is_valid(raise_exception=True) | ||||||
|  |     actor = serializer.save() | ||||||
|  | 
 | ||||||
|  |     assert actor.fid == payload["id"] | ||||||
|  |     assert actor.url == payload["url"][0]["href"] | ||||||
|  |     assert actor.inbox_url == payload["inbox"] | ||||||
|  |     assert actor.shared_inbox_url == payload["endpoints"]["sharedInbox"] | ||||||
|  |     assert actor.outbox_url is payload["outbox"] | ||||||
|  |     assert actor.following_url == payload["following"] | ||||||
|  |     assert actor.followers_url == payload["followers"] | ||||||
|  |     assert actor.followers_url == payload["followers"] | ||||||
|  |     assert actor.type == payload["type"] | ||||||
|  |     assert actor.preferred_username == payload["preferredUsername"] | ||||||
|  |     assert actor.name == payload["name"] | ||||||
|  |     assert actor.manually_approves_followers is payload["manuallyApprovesFollowers"] | ||||||
|  |     assert actor.private_key is None | ||||||
|  |     assert actor.public_key == payload["publicKey"]["publicKeyPem"] | ||||||
|  |     assert actor.domain_id == "r2b.example" | ||||||
|  | 
 | ||||||
|  |     channel = actor.get_channel() | ||||||
|  | 
 | ||||||
|  |     assert channel.attributed_to == actor | ||||||
|  |     assert channel.rss_url == payload["url"][1]["href"] | ||||||
|  |     assert channel.artist.name == actor.name | ||||||
|  |     assert channel.artist.attributed_to == actor | ||||||
|  |  | ||||||
|  | @ -260,7 +260,7 @@ def test_channel_outbox_retrieve_page(factories, api_client): | ||||||
| def test_channel_upload_retrieve(factories, api_client): | def test_channel_upload_retrieve(factories, api_client): | ||||||
|     channel = factories["audio.Channel"](local=True) |     channel = factories["audio.Channel"](local=True) | ||||||
|     upload = factories["music.Upload"](library=channel.library, playable=True) |     upload = factories["music.Upload"](library=channel.library, playable=True) | ||||||
|     url = reverse("federation:music:uploads-detail", kwargs={"uuid": upload.uuid},) |     url = reverse("federation:music:uploads-detail", kwargs={"uuid": upload.uuid}) | ||||||
| 
 | 
 | ||||||
|     expected = serializers.ChannelUploadSerializer(upload).data |     expected = serializers.ChannelUploadSerializer(upload).data | ||||||
| 
 | 
 | ||||||
|  | @ -270,6 +270,19 @@ def test_channel_upload_retrieve(factories, api_client): | ||||||
|     assert response.data == expected |     assert response.data == expected | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def test_channel_upload_retrieve_activity(factories, api_client): | ||||||
|  |     channel = factories["audio.Channel"](local=True) | ||||||
|  |     upload = factories["music.Upload"](library=channel.library, playable=True) | ||||||
|  |     url = reverse("federation:music:uploads-activity", kwargs={"uuid": upload.uuid}) | ||||||
|  | 
 | ||||||
|  |     expected = serializers.ChannelCreateUploadSerializer(upload).data | ||||||
|  | 
 | ||||||
|  |     response = api_client.get(url) | ||||||
|  | 
 | ||||||
|  |     assert response.status_code == 200 | ||||||
|  |     assert response.data == expected | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| @pytest.mark.parametrize("privacy_level", ["me", "instance"]) | @pytest.mark.parametrize("privacy_level", ["me", "instance"]) | ||||||
| def test_music_library_retrieve_page_private(factories, api_client, privacy_level): | def test_music_library_retrieve_page_private(factories, api_client, privacy_level): | ||||||
|     library = factories["music.Library"](privacy_level=privacy_level, actor__local=True) |     library = factories["music.Library"](privacy_level=privacy_level, actor__local=True) | ||||||
|  |  | ||||||
		Ładowanie…
	
		Reference in New Issue
	
	 Eliot Berriot
						Eliot Berriot