Skip to content
Snippets Groups Projects
Forked from funkwhale / funkwhale
7360 commits behind the upstream repository.
test_serializers.py 14.09 KiB
import arrow

from django.urls import reverse
from django.core.paginator import Paginator

from funkwhale_api.federation import actors
from funkwhale_api.federation import keys
from funkwhale_api.federation import models
from funkwhale_api.federation import serializers
from funkwhale_api.federation import utils


def test_actor_serializer_from_ap(db):
    payload = {
        'id': 'https://test.federation/user',
        'type': 'Person',
        'following': 'https://test.federation/user/following',
        'followers': 'https://test.federation/user/followers',
        'inbox': 'https://test.federation/user/inbox',
        'outbox': 'https://test.federation/user/outbox',
        'preferredUsername': 'user',
        'name': 'Real User',
        'summary': 'Hello world',
        'url': 'https://test.federation/@user',
        'manuallyApprovesFollowers': False,
        'publicKey': {
            'id': 'https://test.federation/user#main-key',
            'owner': 'https://test.federation/user',
            'publicKeyPem': 'yolo'
        },
        'endpoints': {
            'sharedInbox': 'https://test.federation/inbox'
        },
    }

    serializer = serializers.ActorSerializer(data=payload)
    assert serializer.is_valid(raise_exception=True)

    actor = serializer.build()

    assert actor.url == payload['id']
    assert actor.inbox_url == payload['inbox']
    assert actor.outbox_url == payload['outbox']
    assert actor.shared_inbox_url == payload['endpoints']['sharedInbox']
    assert actor.followers_url == payload['followers']
    assert actor.following_url == payload['following']
    assert actor.public_key == payload['publicKey']['publicKeyPem']
    assert actor.preferred_username == payload['preferredUsername']
    assert actor.name == payload['name']
    assert actor.domain == 'test.federation'
    assert actor.summary == payload['summary']
    assert actor.type == 'Person'
    assert actor.manually_approves_followers == payload['manuallyApprovesFollowers']


def test_actor_serializer_only_mandatory_field_from_ap(db):
    payload = {
        'id': 'https://test.federation/user',
        'type': 'Person',
        'following': 'https://test.federation/user/following',
        'followers': 'https://test.federation/user/followers',
        'inbox': 'https://test.federation/user/inbox',
        'outbox': 'https://test.federation/user/outbox',
        'preferredUsername': 'user',
    }

    serializer = serializers.ActorSerializer(data=payload)
    assert serializer.is_valid(raise_exception=True)

    actor = serializer.build()

    assert actor.url == payload['id']
    assert actor.inbox_url == payload['inbox']
    assert actor.outbox_url == payload['outbox']
    assert actor.followers_url == payload['followers']
    assert actor.following_url == payload['following']
    assert actor.preferred_username == payload['preferredUsername']
    assert actor.domain == 'test.federation'
    assert actor.type == 'Person'
    assert actor.manually_approves_followers is None


def test_actor_serializer_to_ap():
    expected = {
        '@context': [
            'https://www.w3.org/ns/activitystreams',
            'https://w3id.org/security/v1',
            {},
        ],
        'id': 'https://test.federation/user',
        'type': 'Person',
        'following': 'https://test.federation/user/following',
        'followers': 'https://test.federation/user/followers',
        'inbox': 'https://test.federation/user/inbox',
        'outbox': 'https://test.federation/user/outbox',
        'preferredUsername': 'user',
        'name': 'Real User',
        'summary': 'Hello world',
        'manuallyApprovesFollowers': False,
        'publicKey': {
            'id': 'https://test.federation/user#main-key',
            'owner': 'https://test.federation/user',
            'publicKeyPem': 'yolo'
        },
        'endpoints': {
            'sharedInbox': 'https://test.federation/inbox'
        },
    }
    ac = models.Actor(
        url=expected['id'],
        inbox_url=expected['inbox'],
        outbox_url=expected['outbox'],
        shared_inbox_url=expected['endpoints']['sharedInbox'],
        followers_url=expected['followers'],
        following_url=expected['following'],
        public_key=expected['publicKey']['publicKeyPem'],
        preferred_username=expected['preferredUsername'],
        name=expected['name'],
        domain='test.federation',
        summary=expected['summary'],
        type='Person',
        manually_approves_followers=False,

    )
    serializer = serializers.ActorSerializer(ac)

    assert serializer.data == expected


def test_webfinger_serializer():
    expected = {
        'subject': 'acct:service@test.federation',
        'links': [
            {
                'rel': 'self',
                'href': 'https://test.federation/federation/instance/actor',
                'type': 'application/activity+json',
            }
        ],
        'aliases': [
            'https://test.federation/federation/instance/actor',
        ]
    }
    actor = models.Actor(
        url=expected['links'][0]['href'],
        preferred_username='service',
        domain='test.federation',
    )
    serializer = serializers.ActorWebfingerSerializer(actor)

    assert serializer.data == expected


def test_follow_serializer_to_ap(factories):
    follow = factories['federation.Follow'](local=True)
    serializer = serializers.FollowSerializer(follow)

    expected = {
        '@context': [
            'https://www.w3.org/ns/activitystreams',
            'https://w3id.org/security/v1',
            {},
        ],
        'id': follow.get_federation_url(),
        'type': 'Follow',
        'actor': follow.actor.url,
        'object': follow.target.url,
    }

    assert serializer.data == expected


def test_paginated_collection_serializer(factories):
    tfs = factories['music.TrackFile'].create_batch(size=5)
    actor = factories['federation.Actor'](local=True)

    conf = {
        'id': 'https://test.federation/test',
        'items': tfs,
        'item_serializer': serializers.AudioSerializer,
        'actor': actor,
        'page_size': 2,
    }
    expected = {
        '@context': [
            'https://www.w3.org/ns/activitystreams',
            'https://w3id.org/security/v1',
            {},
        ],
        'type': 'Collection',
        'id': conf['id'],
        'actor': actor.url,
        'totalItems': len(tfs),
        'current': conf['id'] + '?page=1',
        'last': conf['id'] + '?page=3',
        'first': conf['id'] + '?page=1',
    }

    serializer = serializers.PaginatedCollectionSerializer(conf)

    assert serializer.data == expected


def test_paginated_collection_serializer_validation():
    data = {
        'type': 'Collection',
        'id': 'https://test.federation/test',
        'totalItems': 5,
        'actor': 'http://test.actor',
        'items': []
    }

    serializer = serializers.PaginatedCollectionSerializer(
        data=data
    )

    assert serializer.is_valid(raise_exception=True) is True
    assert serializer.validated_data['totalItems'] == 5
    assert serializer.validated_data['id'] == data['id']
    assert serializer.validated_data['actor'] == data['actor']
    assert serializer.validated_data['items'] == []


def test_collection_page_serializer_validation():
    base = 'https://test.federation/test'
    data = {
        'type': 'CollectionPage',
        'id': base + '?page=2',
        'totalItems': 5,
        'actor': 'https://test.actor',
        'items': [],
        'prev': base + '?page=1',
        'next': base + '?page=3',
        'partOf': base,
    }

    serializer = serializers.CollectionPageSerializer(
        data=data
    )

    assert serializer.is_valid(raise_exception=True) is True
    assert serializer.validated_data['totalItems'] == 5
    assert serializer.validated_data['id'] == data['id']
    assert serializer.validated_data['actor'] == data['actor']
    assert serializer.validated_data['items'] == []
    assert serializer.validated_data['prev'] == data['prev']
    assert serializer.validated_data['next'] == data['next']
    assert serializer.validated_data['partOf'] == data['partOf']


def test_collection_page_serializer(factories):
    tfs = factories['music.TrackFile'].create_batch(size=5)
    actor = factories['federation.Actor'](local=True)

    conf = {
        'id': 'https://test.federation/test',
        'item_serializer': serializers.AudioSerializer,
        'actor': actor,
        'page': Paginator(tfs, 2).page(2),
    }
    expected = {
        '@context': [
            'https://www.w3.org/ns/activitystreams',
            'https://w3id.org/security/v1',
            {},
        ],
        'type': 'CollectionPage',
        'id': conf['id'] + '?page=2',
        'actor': actor.url,
        'totalItems': len(tfs),
        'partOf': conf['id'],
        'prev': conf['id'] + '?page=1',
        'next': conf['id'] + '?page=3',
        'first': conf['id'] + '?page=1',
        'last': conf['id'] + '?page=3',
        'items': [
            conf['item_serializer'](
                i,
                context={'actor': actor, 'include_ap_context': False}
            ).data
            for i in conf['page'].object_list
        ]
    }

    serializer = serializers.CollectionPageSerializer(conf)

    assert serializer.data == expected


def test_activity_pub_audio_serializer_to_library_track(factories):
    remote_library = factories['federation.Library']()
    audio = factories['federation.Audio']()
    serializer = serializers.AudioSerializer(
        data=audio, context={'library': remote_library})

    assert serializer.is_valid(raise_exception=True)

    lt = serializer.save()

    assert lt.pk is not None
    assert lt.url == audio['id']
    assert lt.library == remote_library
    assert lt.audio_url == audio['url']['href']
    assert lt.audio_mimetype == audio['url']['mediaType']
    assert lt.metadata == audio['metadata']
    assert lt.title == audio['metadata']['recording']['title']
    assert lt.artist_name == audio['metadata']['artist']['name']
    assert lt.album_title == audio['metadata']['release']['title']
    assert lt.published_date == arrow.get(audio['published'])


def test_activity_pub_audio_serializer_to_library_track_no_duplicate(
        factories):
    remote_library = factories['federation.Library']()
    audio = factories['federation.Audio']()
    serializer1 = serializers.AudioSerializer(
        data=audio, context={'library': remote_library})
    serializer2 = serializers.AudioSerializer(
        data=audio, context={'library': remote_library})

    assert serializer1.is_valid() is True
    assert serializer2.is_valid() is True

    lt1 = serializer1.save()
    lt2 = serializer2.save()

    assert lt1 == lt2
    assert models.LibraryTrack.objects.count() == 1


def test_activity_pub_audio_serializer_to_ap(factories):
    tf = factories['music.TrackFile'](mimetype='audio/mp3')
    library = actors.SYSTEM_ACTORS['library'].get_actor_instance()
    expected = {
        '@context': serializers.AP_CONTEXT,
        'type': 'Audio',
        'id': tf.get_federation_url(),
        'name': tf.track.full_name,
        'published': tf.creation_date.isoformat(),
        'updated': tf.modification_date.isoformat(),
        'metadata': {
            'artist': {
                'musicbrainz_id': tf.track.artist.mbid,
                'name': tf.track.artist.name,
            },
            'release': {
                'musicbrainz_id': tf.track.album.mbid,
                'title': tf.track.album.title,
            },
            'recording': {
                'musicbrainz_id': tf.track.mbid,
                'title': tf.track.title,
            },
        },
        'url': {
            'href': utils.full_url(tf.path),
            'type': 'Link',
            'mediaType': 'audio/mp3'
        },
        'attributedTo': [
            library.url
        ]
    }

    serializer = serializers.AudioSerializer(tf, context={'actor': library})

    assert serializer.data == expected


def test_activity_pub_audio_serializer_to_ap_no_mbid(factories):
    tf = factories['music.TrackFile'](
        mimetype='audio/mp3',
        track__mbid=None,
        track__album__mbid=None,
        track__album__artist__mbid=None,
    )
    library = actors.SYSTEM_ACTORS['library'].get_actor_instance()
    expected = {
        '@context': serializers.AP_CONTEXT,
        'type': 'Audio',
        'id': tf.get_federation_url(),
        'name': tf.track.full_name,
        'published': tf.creation_date.isoformat(),
        'updated': tf.modification_date.isoformat(),
        'metadata': {
            'artist': {
                'name': tf.track.artist.name,
                'musicbrainz_id': None,
            },
            'release': {
                'title': tf.track.album.title,
                'musicbrainz_id': None,
            },
            'recording': {
                'title': tf.track.title,
                'musicbrainz_id': None,
            },
        },
        'url': {
            'href': utils.full_url(tf.path),
            'type': 'Link',
            'mediaType': 'audio/mp3'
        },
        'attributedTo': [
            library.url
        ]
    }

    serializer = serializers.AudioSerializer(tf, context={'actor': library})

    assert serializer.data == expected


def test_collection_serializer_to_ap(factories):
    tf1 = factories['music.TrackFile'](mimetype='audio/mp3')
    tf2 = factories['music.TrackFile'](mimetype='audio/ogg')
    library = actors.SYSTEM_ACTORS['library'].get_actor_instance()
    expected = {
        '@context': serializers.AP_CONTEXT,
        'id': 'https://test.id',
        'actor': library.url,
        'totalItems': 2,
        'type': 'Collection',
        'items': [
            serializers.AudioSerializer(
                tf1, context={'actor': library, 'include_ap_context': False}
            ).data,
            serializers.AudioSerializer(
                tf2, context={'actor': library, 'include_ap_context': False}
            ).data,
        ]
    }

    collection = {
        'id': expected['id'],
        'actor': library,
        'items': [tf1, tf2],
        'item_serializer': serializers.AudioSerializer
    }
    serializer = serializers.CollectionSerializer(
        collection, context={'actor': library, 'id': 'https://test.id'})

    assert serializer.data == expected