Skip to content
Snippets Groups Projects
Verified Commit 0b2fe843 authored by Eliot Berriot's avatar Eliot Berriot
Browse files

Removed too complex FollowRequest model, we now use an aproved field on Follow

parent c97db31c
No related branches found
No related tags found
No related merge requests found
Showing
with 480 additions and 306 deletions
......@@ -6,8 +6,10 @@ import uuid
from django.conf import settings
from funkwhale_api.common import session
from funkwhale_api.common import utils as funkwhale_utils
from . import models
from . import serializers
from . import signing
logger = logging.getLogger(__name__)
......@@ -85,66 +87,9 @@ def deliver(activity, on_behalf_of, to=[]):
logger.debug('Remote answered with %s', response.status_code)
def get_follow(follow_id, follower, followed):
return {
'@context': [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
{}
],
'actor': follower.url,
'id': follower.url + '#follows/{}'.format(follow_id),
'object': followed.url,
'type': 'Follow'
}
def get_undo(id, actor, object):
return {
'@context': [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
{}
],
'type': 'Undo',
'id': id + '/undo',
'actor': actor.url,
'object': object,
}
def get_accept_follow(accept_id, accept_actor, follow, follow_actor):
return {
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{}
],
"id": accept_actor.url + '#accepts/follows/{}'.format(
accept_id),
"type": "Accept",
"actor": accept_actor.url,
"object": {
"id": follow['id'],
"type": "Follow",
"actor": follow_actor.url,
"object": accept_actor.url
},
}
def accept_follow(target, follow, actor):
accept_uuid = uuid.uuid4()
accept = get_accept_follow(
accept_id=accept_uuid,
accept_actor=target,
follow=follow,
follow_actor=actor)
def accept_follow(follow):
serializer = serializers.AcceptFollowSerializer(follow)
deliver(
accept,
to=[actor.url],
on_behalf_of=target)
return models.Follow.objects.get_or_create(
actor=actor,
target=target,
)
serializer.data,
to=[follow.actor.url],
on_behalf_of=follow.target)
......@@ -153,24 +153,32 @@ class SystemActor(object):
def handle_follow(self, ac, sender):
system_actor = self.get_actor_instance()
if self.manually_approves_followers:
fr, created = models.FollowRequest.objects.get_or_create(
actor=sender,
target=system_actor,
approved=None,
)
return fr
serializer = serializers.FollowSerializer(
data=ac, context={'follow_actor': sender})
if not serializer.is_valid():
return logger.info('Invalid follow payload')
approved = True if not self.manually_approves_followers else None
follow = serializer.save(approved=approved)
if follow.approved:
return activity.accept_follow(follow)
def handle_accept(self, ac, sender):
system_actor = self.get_actor_instance()
serializer = serializers.AcceptFollowSerializer(
data=ac,
context={'follow_target': sender, 'follow_actor': system_actor})
if not serializer.is_valid(raise_exception=True):
return logger.info('Received invalid payload')
return activity.accept_follow(
system_actor, ac, sender
)
serializer.save()
def handle_undo_follow(self, ac, sender):
actor = self.get_actor_instance()
models.Follow.objects.filter(
actor=sender,
target=actor,
).delete()
system_actor = self.get_actor_instance()
serializer = serializers.UndoFollowSerializer(
data=ac, context={'actor': sender, 'target': system_actor})
if not serializer.is_valid():
return logger.info('Received invalid payload')
serializer.save()
def handle_undo(self, ac, sender):
if ac['object']['type'] != 'Follow':
......@@ -206,20 +214,6 @@ class LibraryActor(SystemActor):
def manually_approves_followers(self):
return settings.FEDERATION_MUSIC_NEEDS_APPROVAL
def handle_follow(self, ac, sender):
system_actor = self.get_actor_instance()
if self.manually_approves_followers:
fr, created = models.FollowRequest.objects.get_or_create(
actor=sender,
target=system_actor,
approved=None,
)
return fr
return activity.accept_follow(
system_actor, ac, sender
)
@transaction.atomic
def handle_create(self, ac, sender):
try:
......@@ -360,15 +354,15 @@ class TestActor(SystemActor):
super().handle_follow(ac, sender)
# also, we follow back
test_actor = self.get_actor_instance()
follow_uuid = uuid.uuid4()
follow = activity.get_follow(
follow_id=follow_uuid,
follower=test_actor,
followed=sender)
follow_back = models.Follow.objects.get_or_create(
actor=test_actor,
target=sender,
approved=None,
)[0]
activity.deliver(
follow,
to=[ac['actor']],
on_behalf_of=test_actor)
serializers.FollowSerializer(follow_back).data,
to=[follow_back.target.url],
on_behalf_of=follow_back.actor)
def handle_undo_follow(self, ac, sender):
super().handle_undo_follow(ac, sender)
......@@ -381,11 +375,7 @@ class TestActor(SystemActor):
)
except models.Follow.DoesNotExist:
return
undo = activity.get_undo(
id=follow.get_federation_url(),
actor=actor,
object=serializers.FollowSerializer(follow).data,
)
undo = serializers.UndoFollowSerializer(follow).data
follow.delete()
activity.deliver(
undo,
......
......@@ -23,24 +23,13 @@ class FollowAdmin(admin.ModelAdmin):
list_display = [
'actor',
'target',
'approved',
'creation_date'
]
search_fields = ['actor__url', 'target__url']
list_select_related = True
@admin.register(models.FollowRequest)
class FollowRequestAdmin(admin.ModelAdmin):
list_display = [
'actor',
'target',
'creation_date',
'approved'
]
search_fields = ['actor__url', 'target__url']
list_filter = [
'approved'
]
search_fields = ['actor__url', 'target__url']
list_select_related = True
......
......@@ -113,15 +113,6 @@ class FollowFactory(factory.DjangoModelFactory):
)
@registry.register
class FollowRequestFactory(factory.DjangoModelFactory):
target = factory.SubFactory(ActorFactory)
actor = factory.SubFactory(ActorFactory)
class Meta:
model = models.FollowRequest
@registry.register
class LibraryFactory(factory.DjangoModelFactory):
actor = factory.SubFactory(ActorFactory)
......
......@@ -38,25 +38,21 @@ def scan_from_account_name(account_name):
actor__domain=domain,
actor__preferred_username=username
).select_related('actor').first()
follow_request = None
if library:
data['local']['following'] = True
data['local']['awaiting_approval'] = True
else:
follow_request = models.FollowRequest.objects.filter(
data['local'] = {
'following': False,
'awaiting_approval': False,
}
try:
follow = models.Follow.objects.get(
target__preferred_username=username,
target__domain=username,
actor=system_library,
).first()
data['local'] = {
'following': False,
'awaiting_approval': False,
}
if follow_request:
data['awaiting_approval'] = follow_request.approved is None
)
data['local']['awaiting_approval'] = not bool(follow.approved)
data['local']['following'] = True
except models.Follow.DoesNotExist:
pass
follow_request = models.Follow
try:
data['webfinger'] = webfinger.get_resource(
'acct:{}'.format(account_name))
......
# Generated by Django 2.0.3 on 2018-04-10 16:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('federation', '0003_auto_20180407_1010'),
]
operations = [
migrations.RemoveField(
model_name='followrequest',
name='actor',
),
migrations.RemoveField(
model_name='followrequest',
name='target',
),
migrations.AddField(
model_name='follow',
name='approved',
field=models.NullBooleanField(default=None),
),
migrations.DeleteModel(
name='FollowRequest',
),
]
......@@ -109,6 +109,7 @@ class Follow(models.Model):
creation_date = models.DateTimeField(default=timezone.now)
modification_date = models.DateTimeField(
auto_now=True)
approved = models.NullBooleanField(default=None)
class Meta:
unique_together = ['actor', 'target']
......@@ -117,49 +118,6 @@ class Follow(models.Model):
return '{}#follows/{}'.format(self.actor.url, self.uuid)
class FollowRequest(models.Model):
uuid = models.UUIDField(default=uuid.uuid4, unique=True)
actor = models.ForeignKey(
Actor,
related_name='emmited_follow_requests',
on_delete=models.CASCADE,
)
target = models.ForeignKey(
Actor,
related_name='received_follow_requests',
on_delete=models.CASCADE,
)
creation_date = models.DateTimeField(default=timezone.now)
modification_date = models.DateTimeField(
auto_now=True)
approved = models.NullBooleanField(default=None)
def approve(self):
from . import activity
from . import serializers
self.approved = True
self.save(update_fields=['approved'])
Follow.objects.get_or_create(
target=self.target,
actor=self.actor
)
if self.target.is_local:
follow = {
'@context': serializers.AP_CONTEXT,
'actor': self.actor.url,
'id': self.actor.url + '#follows/{}'.format(uuid.uuid4()),
'object': self.target.url,
'type': 'Follow'
}
activity.accept_follow(
self.target, follow, self.actor
)
def refuse(self):
self.approved = False
self.save(update_fields=['approved'])
class Library(models.Model):
creation_date = models.DateTimeField(default=timezone.now)
modification_date = models.DateTimeField(
......
......@@ -121,28 +121,132 @@ class LibraryActorSerializer(ActorSerializer):
return validated_data
class FollowSerializer(serializers.ModelSerializer):
# left maps to activitypub fields, right to our internal models
id = serializers.URLField(source='get_federation_url')
object = serializers.URLField(source='target.url')
actor = serializers.URLField(source='actor.url')
type = serializers.CharField(source='ap_type')
class FollowSerializer(serializers.Serializer):
id = serializers.URLField()
object = serializers.URLField()
actor = serializers.URLField()
type = serializers.ChoiceField(choices=['Follow'])
class Meta:
model = models.Actor
fields = [
'id',
'object',
'actor',
'type'
]
def validate_object(self, v):
expected = self.context.get('follow_target')
if expected and expected.url != v:
raise serializers.ValidationError('Invalid target')
try:
return models.Actor.objects.get(url=v)
except models.Actor.DoesNotExist:
raise serializers.ValidationError('Target not found')
def validate_actor(self, v):
expected = self.context.get('follow_actor')
if expected and expected.url != v:
raise serializers.ValidationError('Invalid actor')
try:
return models.Actor.objects.get(url=v)
except models.Actor.DoesNotExist:
raise serializers.ValidationError('Actor not found')
def save(self, **kwargs):
return models.Follow.objects.get_or_create(
actor=self.validated_data['actor'],
target=self.validated_data['object'],
**kwargs,
)[0]
def to_representation(self, instance):
ret = super().to_representation(instance)
ret['@context'] = AP_CONTEXT
return {
'@context': AP_CONTEXT,
'actor': instance.actor.url,
'id': instance.get_federation_url(),
'object': instance.target.url,
'type': 'Follow'
}
return ret
class AcceptFollowSerializer(serializers.Serializer):
id = serializers.URLField()
actor = serializers.URLField()
object = FollowSerializer()
type = serializers.ChoiceField(choices=['Accept'])
def validate_actor(self, v):
expected = self.context.get('follow_target')
if expected and expected.url != v:
raise serializers.ValidationError('Invalid actor')
try:
return models.Actor.objects.get(url=v)
except models.Actor.DoesNotExist:
raise serializers.ValidationError('Actor not found')
def validate(self, validated_data):
# we ensure the accept actor actually match the follow target
if validated_data['actor'] != validated_data['object']['object']:
raise serializers.ValidationError('Actor mismatch')
try:
validated_data['follow'] = models.Follow.objects.filter(
target=validated_data['actor'],
actor=validated_data['object']['actor']
).exclude(approved=True).get()
except models.Follow.DoesNotExist:
raise serializers.ValidationError('No follow to accept')
return validated_data
def to_representation(self, instance):
return {
"@context": AP_CONTEXT,
"id": instance.get_federation_url() + '/accept',
"type": "Accept",
"actor": instance.target.url,
"object": FollowSerializer(instance).data
}
def save(self):
self.validated_data['follow'].approved = True
self.validated_data['follow'].save()
return self.validated_data['follow']
class UndoFollowSerializer(serializers.Serializer):
id = serializers.URLField()
actor = serializers.URLField()
object = FollowSerializer()
type = serializers.ChoiceField(choices=['Undo'])
def validate_actor(self, v):
expected = self.context.get('follow_target')
if expected and expected.url != v:
raise serializers.ValidationError('Invalid actor')
try:
return models.Actor.objects.get(url=v)
except models.Actor.DoesNotExist:
raise serializers.ValidationError('Actor not found')
def validate(self, validated_data):
# we ensure the accept actor actually match the follow actor
if validated_data['actor'] != validated_data['object']['actor']:
raise serializers.ValidationError('Actor mismatch')
try:
validated_data['follow'] = models.Follow.objects.filter(
actor=validated_data['actor'],
target=validated_data['object']['object']
).get()
except models.Follow.DoesNotExist:
raise serializers.ValidationError('No follow to remove')
return validated_data
def to_representation(self, instance):
return {
"@context": AP_CONTEXT,
"id": instance.get_federation_url() + '/undo',
"type": "Undo",
"actor": instance.actor.url,
"object": FollowSerializer(instance).data
}
def save(self):
self.validated_data['follow'].delete()
class ActorWebfingerSerializer(serializers.Serializer):
subject = serializers.CharField()
aliases = serializers.ListField(child=serializers.URLField())
......
from django import forms
from django.conf import settings
from django.core import paginator
from django.db import transaction
from django.http import HttpResponse
from django.urls import reverse
......@@ -9,9 +10,12 @@ from rest_framework import response
from rest_framework import views
from rest_framework import viewsets
from rest_framework.decorators import list_route, detail_route
from rest_framework.serializers import ValidationError
from funkwhale_api.common import utils as funkwhale_utils
from funkwhale_api.music.models import TrackFile
from . import activity
from . import actors
from . import authentication
from . import library
......@@ -172,3 +176,29 @@ class LibraryViewSet(viewsets.GenericViewSet):
data = library.scan_from_account_name(account)
return response.Response(data)
@transaction.atomic
def create(self, request, *args, **kwargs):
try:
actor_url = request.data['actor_url']
except KeyError:
raise ValidationError('Missing actor_url')
try:
actor = actors.get_actor(actor_url)
library_data = library.get_library_data(actor.url)
except Exception as e:
raise ValidationError('Error while fetching actor and library')
library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
follow, created = models.Follow.objects.get_or_create(
actor=library_actor,
target=actor,
)
serializer = serializers.FollowSerializer(follow)
activity.deliver(
serializer.data,
on_behalf_of=library_actor,
to=[actor.url]
)
return response.Response({}, status=201)
import uuid
from funkwhale_api.federation import activity
from funkwhale_api.federation import serializers
def test_deliver(nodb_factories, r_mock, mocker):
......@@ -38,37 +39,9 @@ def test_deliver(nodb_factories, r_mock, mocker):
def test_accept_follow(mocker, factories):
deliver = mocker.patch(
'funkwhale_api.federation.activity.deliver')
actor = factories['federation.Actor']()
target = factories['federation.Actor'](local=True)
follow = {
'actor': actor.url,
'type': 'Follow',
'id': 'http://test.federation/user#follows/267',
'object': target.url,
}
uid = uuid.uuid4()
mocker.patch('uuid.uuid4', return_value=uid)
expected_accept = {
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{}
],
"id": target.url + '#accepts/follows/{}'.format(uid),
"type": "Accept",
"actor": target.url,
"object": {
"id": follow['id'],
"type": "Follow",
"actor": actor.url,
"object": target.url
},
}
activity.accept_follow(
target, follow, actor
)
follow = factories['federation.Follow'](approved=None)
expected_accept = serializers.AcceptFollowSerializer(follow).data
activity.accept_follow(follow)
deliver.assert_called_once_with(
expected_accept, to=[actor.url], on_behalf_of=target
expected_accept, to=[follow.actor.url], on_behalf_of=follow.target
)
follow_instance = actor.emitted_follows.first()
assert follow_instance.target == target
......@@ -7,6 +7,7 @@ from django.utils import timezone
from rest_framework import exceptions
from funkwhale_api.federation import activity
from funkwhale_api.federation import actors
from funkwhale_api.federation import models
from funkwhale_api.federation import serializers
......@@ -261,8 +262,6 @@ def test_test_actor_handles_follow(
deliver = mocker.patch(
'funkwhale_api.federation.activity.deliver')
actor = factories['federation.Actor']()
now = timezone.now()
mocker.patch('django.utils.timezone.now', return_value=now)
accept_follow = mocker.patch(
'funkwhale_api.federation.activity.accept_follow')
test_actor = actors.SYSTEM_ACTORS['test'].get_actor_instance()
......@@ -272,28 +271,15 @@ def test_test_actor_handles_follow(
'id': 'http://test.federation/user#follows/267',
'object': test_actor.url,
}
uid = uuid.uuid4()
mocker.patch('uuid.uuid4', return_value=uid)
expected_follow = {
'@context': serializers.AP_CONTEXT,
'actor': test_actor.url,
'id': test_actor.url + '#follows/{}'.format(uid),
'object': actor.url,
'type': 'Follow'
}
actors.SYSTEM_ACTORS['test'].post_inbox(data, actor=actor)
accept_follow.assert_called_once_with(
test_actor, data, actor
follow = models.Follow.objects.get(target=test_actor, approved=True)
follow_back = models.Follow.objects.get(actor=test_actor, approved=None)
accept_follow.assert_called_once_with(follow)
deliver.assert_called_once_with(
serializers.FollowSerializer(follow_back).data,
on_behalf_of=test_actor,
to=[actor.url]
)
expected_calls = [
mocker.call(
expected_follow,
to=[actor.url],
on_behalf_of=test_actor,
)
]
deliver.assert_has_calls(expected_calls)
def test_test_actor_handles_undo_follow(
......@@ -346,12 +332,10 @@ def test_library_actor_handles_follow_manual_approval(
}
library_actor.system_conf.post_inbox(data, actor=actor)
fr = library_actor.received_follow_requests.first()
follow = library_actor.received_follows.first()
assert library_actor.received_follow_requests.count() == 1
assert fr.target == library_actor
assert fr.actor == actor
assert fr.approved is None
assert follow.actor == actor
assert follow.approved is None
def test_library_actor_handles_follow_auto_approval(
......@@ -369,10 +353,27 @@ def test_library_actor_handles_follow_auto_approval(
}
library_actor.system_conf.post_inbox(data, actor=actor)
assert library_actor.received_follow_requests.count() == 0
accept_follow.assert_called_once_with(
library_actor, data, actor
follow = library_actor.received_follows.first()
assert follow.actor == actor
assert follow.approved is True
def test_library_actor_handles_accept(
mocker, factories):
library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
actor = factories['federation.Actor']()
pending_follow = factories['federation.Follow'](
actor=library_actor,
target=actor,
approved=None,
)
serializer = serializers.AcceptFollowSerializer(pending_follow)
library_actor.system_conf.post_inbox(serializer.data, actor=actor)
pending_follow.refresh_from_db()
assert pending_follow.approved is True
def test_library_actor_handle_create_audio_no_library(mocker, factories):
......
......@@ -35,50 +35,6 @@ def test_follow_federation_url(factories):
assert follow.get_federation_url() == expected
def test_follow_request_approve(mocker, factories):
uid = uuid.uuid4()
mocker.patch('uuid.uuid4', return_value=uid)
accept_follow = mocker.patch(
'funkwhale_api.federation.activity.accept_follow')
fr = factories['federation.FollowRequest'](target__local=True)
fr.approve()
follow = {
'@context': serializers.AP_CONTEXT,
'actor': fr.actor.url,
'id': fr.actor.url + '#follows/{}'.format(uid),
'object': fr.target.url,
'type': 'Follow'
}
assert fr.approved is True
assert list(fr.target.followers.all()) == [fr.actor]
accept_follow.assert_called_once_with(
fr.target, follow, fr.actor
)
def test_follow_request_approve_non_local(mocker, factories):
uid = uuid.uuid4()
mocker.patch('uuid.uuid4', return_value=uid)
accept_follow = mocker.patch(
'funkwhale_api.federation.activity.accept_follow')
fr = factories['federation.FollowRequest']()
fr.approve()
assert fr.approved is True
assert list(fr.target.followers.all()) == [fr.actor]
accept_follow.assert_not_called()
def test_follow_request_refused(mocker, factories):
fr = factories['federation.FollowRequest']()
fr.refuse()
assert fr.approved is False
assert fr.target.followers.count() == 0
def test_library_model_unique_per_actor(factories):
library = factories['federation.Library']()
with pytest.raises(db.IntegrityError):
......
import arrow
import pytest
from django.urls import reverse
from django.core.paginator import Paginator
......@@ -170,6 +171,184 @@ def test_follow_serializer_to_ap(factories):
assert serializer.data == expected
def test_follow_serializer_save(factories):
actor = factories['federation.Actor']()
target = factories['federation.Actor']()
data = expected = {
'id': 'https://test.follow',
'type': 'Follow',
'actor': actor.url,
'object': target.url,
}
serializer = serializers.FollowSerializer(data=data)
assert serializer.is_valid(raise_exception=True)
follow = serializer.save()
assert follow.pk is not None
assert follow.actor == actor
assert follow.target == target
assert follow.approved is None
def test_follow_serializer_save_validates_on_context(factories):
actor = factories['federation.Actor']()
target = factories['federation.Actor']()
impostor = factories['federation.Actor']()
data = expected = {
'id': 'https://test.follow',
'type': 'Follow',
'actor': actor.url,
'object': target.url,
}
serializer = serializers.FollowSerializer(
data=data,
context={'follow_actor': impostor, 'follow_target': impostor})
assert serializer.is_valid() is False
assert 'actor' in serializer.errors
assert 'object' in serializer.errors
def test_accept_follow_serializer_representation(factories):
follow = factories['federation.Follow'](approved=None)
expected = {
'@context': [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
{},
],
'id': follow.get_federation_url() + '/accept',
'type': 'Accept',
'actor': follow.target.url,
'object': serializers.FollowSerializer(follow).data,
}
serializer = serializers.AcceptFollowSerializer(follow)
assert serializer.data == expected
def test_accept_follow_serializer_save(factories):
follow = factories['federation.Follow'](approved=None)
data = {
'@context': [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
{},
],
'id': follow.get_federation_url() + '/accept',
'type': 'Accept',
'actor': follow.target.url,
'object': serializers.FollowSerializer(follow).data,
}
serializer = serializers.AcceptFollowSerializer(data=data)
assert serializer.is_valid(raise_exception=True)
serializer.save()
follow.refresh_from_db()
assert follow.approved is True
def test_accept_follow_serializer_validates_on_context(factories):
follow = factories['federation.Follow'](approved=None)
impostor = factories['federation.Actor']()
data = {
'@context': [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
{},
],
'id': follow.get_federation_url() + '/accept',
'type': 'Accept',
'actor': impostor.url,
'object': serializers.FollowSerializer(follow).data,
}
serializer = serializers.AcceptFollowSerializer(
data=data,
context={'follow_actor': impostor, 'follow_target': impostor})
assert serializer.is_valid() is False
assert 'actor' in serializer.errors['object']
assert 'object' in serializer.errors['object']
def test_undo_follow_serializer_representation(factories):
follow = factories['federation.Follow'](approved=True)
expected = {
'@context': [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
{},
],
'id': follow.get_federation_url() + '/undo',
'type': 'Undo',
'actor': follow.actor.url,
'object': serializers.FollowSerializer(follow).data,
}
serializer = serializers.UndoFollowSerializer(follow)
assert serializer.data == expected
def test_undo_follow_serializer_save(factories):
follow = factories['federation.Follow'](approved=True)
data = {
'@context': [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
{},
],
'id': follow.get_federation_url() + '/undo',
'type': 'Undo',
'actor': follow.actor.url,
'object': serializers.FollowSerializer(follow).data,
}
serializer = serializers.UndoFollowSerializer(data=data)
assert serializer.is_valid(raise_exception=True)
serializer.save()
with pytest.raises(models.Follow.DoesNotExist):
follow.refresh_from_db()
def test_undo_follow_serializer_validates_on_context(factories):
follow = factories['federation.Follow'](approved=True)
impostor = factories['federation.Actor']()
data = {
'@context': [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
{},
],
'id': follow.get_federation_url() + '/undo',
'type': 'Undo',
'actor': impostor.url,
'object': serializers.FollowSerializer(follow).data,
}
serializer = serializers.UndoFollowSerializer(
data=data,
context={'follow_actor': impostor, 'follow_target': impostor})
assert serializer.is_valid() is False
assert 'actor' in serializer.errors['object']
assert 'object' in serializer.errors['object']
def test_paginated_collection_serializer(factories):
tfs = factories['music.TrackFile'].create_batch(size=5)
actor = factories['federation.Actor'](local=True)
......
......@@ -4,6 +4,7 @@ from django.core.paginator import Paginator
import pytest
from funkwhale_api.federation import actors
from funkwhale_api.federation import models
from funkwhale_api.federation import serializers
from funkwhale_api.federation import utils
from funkwhale_api.federation import webfinger
......@@ -179,3 +180,35 @@ def test_can_scan_library(superuser_api_client, mocker):
assert response.status_code == 200
assert response.data == result
scan.assert_called_once_with('test@test.library')
def test_follow_library_manually(superuser_api_client, mocker, factories):
library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
actor = factories['federation.Actor'](manually_approves_followers=True)
follow = {'test': 'follow'}
deliver = mocker.patch(
'funkwhale_api.federation.activity.deliver')
actor_get = mocker.patch(
'funkwhale_api.federation.actors.get_actor',
return_value=actor)
library_get = mocker.patch(
'funkwhale_api.federation.library.get_library_data',
return_value={})
url = reverse('api:v1:federation:libraries-list')
response = superuser_api_client.post(
url, {'actor_url': actor.url})
assert response.status_code == 201
follow = models.Follow.objects.get(
actor=library_actor,
target=actor,
approved=None,
)
deliver.assert_called_once_with(
serializers.FollowSerializer(follow).data,
on_behalf_of=library_actor,
to=[actor.url]
)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment