kopia lustrzana https://dev.funkwhale.audio/funkwhale/funkwhale
443 wiersze
14 KiB
Python
443 wiersze
14 KiB
Python
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']
|
|
|
|
|
|
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
|