Adds actions for all verbs

This commit is contained in:
Mouse Reeve 2021-02-15 20:49:23 -08:00
parent 12a3aa9667
commit a16b81a6eb
7 changed files with 507 additions and 506 deletions

View file

@ -50,6 +50,7 @@ def naive_parse(activity_objects, activity_json):
return serializer(activity_objects=activity_objects, **activity_json)
@dataclass(init=False)
class ActivityObject:
''' actor activitypub json '''
@ -79,7 +80,7 @@ class ActivityObject:
setattr(self, field.name, value)
def to_model(self, instance=None, save=True):
def to_model(self, instance=None, allow_create=True, save=True):
''' convert from an activity to a model instance '''
# figure out the right model -- wish I had a better way for this
models = apps.get_models()
@ -95,7 +96,10 @@ class ActivityObject:
return instance
# check for an existing instance, if we're not updating a known obj
instance = instance or model.find_existing(self.serialize()) or model()
instance = instance or model.find_existing(self.serialize())
if not instance and not allow_create:
return None
instance = instance or model()
for field in instance.simple_fields:
field.set_field_from_activity(instance, self)

View file

@ -12,6 +12,10 @@ class Verb(ActivityObject):
actor: str
object: ActivityObject
def action(self):
''' usually we just want to save, this can be overridden as needed '''
self.object.to_model()
@dataclass(init=False)
class Create(Verb):
@ -21,10 +25,6 @@ class Create(Verb):
signature: Signature = None
type: str = 'Create'
def action(self):
''' create the model instance from the dataclass '''
self.object.to_model()
@dataclass(init=False)
class Delete(Verb):
@ -33,6 +33,12 @@ class Delete(Verb):
cc: List
type: str = 'Delete'
def action(self):
''' find and delete the activity object '''
obj = self.object.to_model(save=False, allow_create=False)
obj.delete()
@dataclass(init=False)
class Update(Verb):
@ -40,12 +46,21 @@ class Update(Verb):
to: List
type: str = 'Update'
def action(self):
''' update a model instance from the dataclass '''
self.object.to_model(allow_create=False)
@dataclass(init=False)
class Undo(Verb):
''' Undo an activity '''
type: str = 'Undo'
def action(self):
''' find and remove the activity object '''
obj = self.object.to_model(save=False, allow_create=False)
obj.delete()
@dataclass(init=False)
class Follow(Verb):
@ -53,18 +68,25 @@ class Follow(Verb):
object: str
type: str = 'Follow'
@dataclass(init=False)
class Block(Verb):
''' Block activity '''
object: str
type: str = 'Block'
@dataclass(init=False)
class Accept(Verb):
''' Accept activity '''
object: Follow
type: str = 'Accept'
def action(self):
''' find and remove the activity object '''
obj = self.object.to_model(save=False, allow_create=False)
obj.accept()
@dataclass(init=False)
class Reject(Verb):
@ -72,6 +94,11 @@ class Reject(Verb):
object: Follow
type: str = 'Reject'
def action(self):
''' find and remove the activity object '''
obj = self.object.to_model(save=False, allow_create=False)
obj.reject()
@dataclass(init=False)
class Add(Verb):
@ -101,3 +128,8 @@ class Remove(Verb):
'''Remove activity '''
target: ActivityObject
type: str = 'Remove'
def action(self):
''' find and remove the activity object '''
obj = self.object.to_model(save=False, allow_create=False)
obj.delete()

View file

@ -56,11 +56,9 @@ class UserRelationship(BookWyrmModel):
return '%s#%s/%d' % (base_path, status, self.id)
class UserFollows(ActivitypubMixin, UserRelationship):
class UserFollows(UserRelationship):
''' Following a user '''
status = 'follows'
activity_serializer = activitypub.Follow
@classmethod
def from_request(cls, follow_request):
@ -101,9 +99,13 @@ class UserFollowRequest(ActivitypubMixin, UserRelationship):
self.broadcast(self.to_activity(), self.user_subject)
if self.user_object.local:
manually_approves = self.user_object.manually_approves_followers
if manually_approves:
self.accept()
model = apps.get_model('bookwyrm.Notification', require_ready=True)
notification_type = 'FOLLOW_REQUEST' \
if self.user_object.manually_approves_followers else 'FOLLOW'
notification_type = 'FOLLOW_REQUEST' if \
manually_approves else 'FOLLOW'
model.objects.create(
user=self.user_object,
related_user=self.user_subject,

View file

@ -1,493 +0,0 @@
''' test incoming activities '''
from datetime import datetime
import json
import pathlib
from unittest.mock import patch
from django.http import HttpResponseBadRequest, HttpResponseNotAllowed, \
HttpResponseNotFound
from django.test import TestCase
from django.test.client import RequestFactory
import responses
from bookwyrm import models, incoming
#pylint: disable=too-many-public-methods
class Incoming(TestCase):
''' a lot here: all handlers for receiving activitypub requests '''
def setUp(self):
''' we need basic things, like users '''
self.local_user = models.User.objects.create_user(
'mouse@example.com', 'mouse@mouse.com', 'mouseword',
local=True, localname='mouse')
self.local_user.remote_id = 'https://example.com/user/mouse'
self.local_user.save(broadcast=False)
with patch('bookwyrm.models.user.set_remote_server.delay'):
self.remote_user = models.User.objects.create_user(
'rat', 'rat@rat.com', 'ratword',
local=False,
remote_id='https://example.com/users/rat',
inbox='https://example.com/users/rat/inbox',
outbox='https://example.com/users/rat/outbox',
)
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
self.status = models.Status.objects.create(
user=self.local_user,
content='Test status',
remote_id='https://example.com/status/1',
)
self.factory = RequestFactory()
def test_handle_follow(self):
''' remote user wants to follow local user '''
activity = {
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/users/rat/follows/123",
"type": "Follow",
"actor": "https://example.com/users/rat",
"object": "https://example.com/user/mouse"
}
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
incoming.handle_follow(activity)
# notification created
notification = models.Notification.objects.get()
self.assertEqual(notification.user, self.local_user)
self.assertEqual(notification.notification_type, 'FOLLOW')
# the request should have been deleted
requests = models.UserFollowRequest.objects.all()
self.assertEqual(list(requests), [])
# the follow relationship should exist
follow = models.UserFollows.objects.get(user_object=self.local_user)
self.assertEqual(follow.user_subject, self.remote_user)
def test_handle_follow_manually_approved(self):
''' needs approval before following '''
activity = {
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/users/rat/follows/123",
"type": "Follow",
"actor": "https://example.com/users/rat",
"object": "https://example.com/user/mouse"
}
self.local_user.manually_approves_followers = True
self.local_user.save(broadcast=False)
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
incoming.handle_follow(activity)
# notification created
notification = models.Notification.objects.get()
self.assertEqual(notification.user, self.local_user)
self.assertEqual(notification.notification_type, 'FOLLOW_REQUEST')
# the request should exist
request = models.UserFollowRequest.objects.get()
self.assertEqual(request.user_subject, self.remote_user)
self.assertEqual(request.user_object, self.local_user)
# the follow relationship should not exist
follow = models.UserFollows.objects.all()
self.assertEqual(list(follow), [])
def test_handle_unfollow(self):
''' remove a relationship '''
activity = {
"type": "Undo",
"@context": "https://www.w3.org/ns/activitystreams",
"object": {
"id": "https://example.com/users/rat/follows/123",
"type": "Follow",
"actor": "https://example.com/users/rat",
"object": "https://example.com/user/mouse"
}
}
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
models.UserFollows.objects.create(
user_subject=self.remote_user, user_object=self.local_user)
self.assertEqual(self.remote_user, self.local_user.followers.first())
incoming.handle_unfollow(activity)
self.assertIsNone(self.local_user.followers.first())
def test_handle_follow_accept(self):
''' a remote user approved a follow request from local '''
activity = {
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/users/rat/follows/123#accepts",
"type": "Accept",
"actor": "https://example.com/users/rat",
"object": {
"id": "https://example.com/users/rat/follows/123",
"type": "Follow",
"actor": "https://example.com/user/mouse",
"object": "https://example.com/users/rat"
}
}
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
models.UserFollowRequest.objects.create(
user_subject=self.local_user,
user_object=self.remote_user
)
self.assertEqual(models.UserFollowRequest.objects.count(), 1)
incoming.handle_follow_accept(activity)
# request should be deleted
self.assertEqual(models.UserFollowRequest.objects.count(), 0)
# relationship should be created
follows = self.remote_user.followers
self.assertEqual(follows.count(), 1)
self.assertEqual(follows.first(), self.local_user)
def test_handle_follow_reject(self):
''' turn down a follow request '''
activity = {
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/users/rat/follows/123#accepts",
"type": "Reject",
"actor": "https://example.com/users/rat",
"object": {
"id": "https://example.com/users/rat/follows/123",
"type": "Follow",
"actor": "https://example.com/user/mouse",
"object": "https://example.com/users/rat"
}
}
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
models.UserFollowRequest.objects.create(
user_subject=self.local_user,
user_object=self.remote_user
)
self.assertEqual(models.UserFollowRequest.objects.count(), 1)
incoming.handle_follow_reject(activity)
# request should be deleted
self.assertEqual(models.UserFollowRequest.objects.count(), 0)
# relationship should be created
follows = self.remote_user.followers
self.assertEqual(follows.count(), 0)
def test_handle_update_list(self):
''' a new list '''
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
book_list = models.List.objects.create(
name='hi', remote_id='https://example.com/list/22',
user=self.local_user)
activity = {
'object': {
"id": "https://example.com/list/22",
"type": "BookList",
"totalItems": 1,
"first": "https://example.com/list/22?page=1",
"last": "https://example.com/list/22?page=1",
"name": "Test List",
"owner": "https://example.com/user/mouse",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"https://example.com/user/mouse/followers"
],
"summary": "summary text",
"curation": "curated",
"@context": "https://www.w3.org/ns/activitystreams"
}
}
incoming.handle_update_list(activity)
book_list.refresh_from_db()
self.assertEqual(book_list.name, 'Test List')
self.assertEqual(book_list.curation, 'curated')
self.assertEqual(book_list.description, 'summary text')
self.assertEqual(book_list.remote_id, 'https://example.com/list/22')
def test_handle_delete_status(self):
''' remove a status '''
self.status.user = self.remote_user
self.status.save(broadcast=False)
self.assertFalse(self.status.deleted)
activity = {
'type': 'Delete',
'id': '%s/activity' % self.status.remote_id,
'actor': self.remote_user.remote_id,
'object': {'id': self.status.remote_id},
}
incoming.handle_delete_status(activity)
# deletion doens't remove the status, it turns it into a tombstone
status = models.Status.objects.get()
self.assertTrue(status.deleted)
self.assertIsInstance(status.deleted_date, datetime)
def test_handle_delete_status_notifications(self):
''' remove a status with related notifications '''
self.status.user = self.remote_user
self.status.save(broadcast=False)
models.Notification.objects.create(
related_status=self.status,
user=self.local_user,
notification_type='MENTION'
)
# this one is innocent, don't delete it
notif = models.Notification.objects.create(
user=self.local_user,
notification_type='MENTION'
)
self.assertFalse(self.status.deleted)
self.assertEqual(models.Notification.objects.count(), 2)
activity = {
'type': 'Delete',
'id': '%s/activity' % self.status.remote_id,
'actor': self.remote_user.remote_id,
'object': {'id': self.status.remote_id},
}
incoming.handle_delete_status(activity)
# deletion doens't remove the status, it turns it into a tombstone
status = models.Status.objects.get()
self.assertTrue(status.deleted)
self.assertIsInstance(status.deleted_date, datetime)
# notifications should be truly deleted
self.assertEqual(models.Notification.objects.count(), 1)
self.assertEqual(models.Notification.objects.get(), notif)
def test_handle_favorite(self):
''' fav a status '''
activity = {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': 'https://example.com/fav/1',
'actor': 'https://example.com/users/rat',
'published': 'Mon, 25 May 2020 19:31:20 GMT',
'object': 'https://example.com/status/1',
}
incoming.handle_favorite(activity)
fav = models.Favorite.objects.get(remote_id='https://example.com/fav/1')
self.assertEqual(fav.status, self.status)
self.assertEqual(fav.remote_id, 'https://example.com/fav/1')
self.assertEqual(fav.user, self.remote_user)
def test_handle_unfavorite(self):
''' fav a status '''
activity = {
'id': 'https://example.com/fav/1#undo',
'type': 'Undo',
'object': {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': 'https://example.com/fav/1',
'actor': 'https://example.com/users/rat',
'published': 'Mon, 25 May 2020 19:31:20 GMT',
'object': 'https://example.com/fav/1',
}
}
models.Favorite.objects.create(
status=self.status,
user=self.remote_user,
remote_id='https://example.com/fav/1')
self.assertEqual(models.Favorite.objects.count(), 1)
incoming.handle_unfavorite(activity)
self.assertEqual(models.Favorite.objects.count(), 0)
def test_handle_boost(self):
''' boost a status '''
self.assertEqual(models.Notification.objects.count(), 0)
activity = {
'type': 'Announce',
'id': '%s/boost' % self.status.remote_id,
'actor': self.remote_user.remote_id,
'object': self.status.to_activity(),
}
with patch('bookwyrm.models.status.Status.ignore_activity') \
as discarder:
discarder.return_value = False
incoming.handle_boost(activity)
boost = models.Boost.objects.get()
self.assertEqual(boost.boosted_status, self.status)
notification = models.Notification.objects.get()
self.assertEqual(notification.user, self.local_user)
self.assertEqual(notification.related_status, self.status)
@responses.activate
def test_handle_discarded_boost(self):
''' test a boost of a mastodon status that will be discarded '''
activity = {
'type': 'Announce',
'id': 'http://www.faraway.com/boost/12',
'actor': self.remote_user.remote_id,
'object': self.status.to_activity(),
}
responses.add(
responses.GET,
'http://www.faraway.com/boost/12',
json={'id': 'http://www.faraway.com/boost/12'},
status=200)
incoming.handle_boost(activity)
self.assertEqual(models.Boost.objects.count(), 0)
def test_handle_unboost(self):
''' undo a boost '''
activity = {
'type': 'Undo',
'object': {
'type': 'Announce',
'id': '%s/boost' % self.status.remote_id,
'actor': self.local_user.remote_id,
'object': self.status.to_activity(),
}
}
models.Boost.objects.create(
boosted_status=self.status, user=self.remote_user)
incoming.handle_unboost(activity)
def test_handle_add_book(self):
''' shelving a book '''
book = models.Edition.objects.create(
title='Test', remote_id='https://bookwyrm.social/book/37292')
shelf = models.Shelf.objects.create(
user=self.remote_user, name='Test Shelf')
shelf.remote_id = 'https://bookwyrm.social/user/mouse/shelf/to-read'
shelf.save()
activity = {
"id": "https://bookwyrm.social/shelfbook/6189#add",
"type": "Add",
"actor": "https://example.com/users/rat",
"object": "https://bookwyrm.social/book/37292",
"target": "https://bookwyrm.social/user/mouse/shelf/to-read",
"@context": "https://www.w3.org/ns/activitystreams"
}
incoming.handle_add(activity)
self.assertEqual(shelf.books.first(), book)
def test_handle_update_user(self):
''' update an existing user '''
# we only do this with remote users
self.local_user.local = False
self.local_user.save()
datafile = pathlib.Path(__file__).parent.joinpath(
'data/ap_user.json')
userdata = json.loads(datafile.read_bytes())
del userdata['icon']
self.assertIsNone(self.local_user.name)
incoming.handle_update_user({'object': userdata})
user = models.User.objects.get(id=self.local_user.id)
self.assertEqual(user.name, 'MOUSE?? MOUSE!!')
self.assertEqual(user.username, 'mouse@example.com')
self.assertEqual(user.localname, 'mouse')
def test_handle_update_edition(self):
''' update an existing edition '''
datafile = pathlib.Path(__file__).parent.joinpath(
'data/bw_edition.json')
bookdata = json.loads(datafile.read_bytes())
models.Work.objects.create(
title='Test Work', remote_id='https://bookwyrm.social/book/5988')
book = models.Edition.objects.create(
title='Test Book', remote_id='https://bookwyrm.social/book/5989')
del bookdata['authors']
self.assertEqual(book.title, 'Test Book')
with patch(
'bookwyrm.activitypub.base_activity.set_related_field.delay'):
incoming.handle_update_edition({'object': bookdata})
book = models.Edition.objects.get(id=book.id)
self.assertEqual(book.title, 'Piranesi')
def test_handle_update_work(self):
''' update an existing edition '''
datafile = pathlib.Path(__file__).parent.joinpath(
'data/bw_work.json')
bookdata = json.loads(datafile.read_bytes())
book = models.Work.objects.create(
title='Test Book', remote_id='https://bookwyrm.social/book/5988')
del bookdata['authors']
self.assertEqual(book.title, 'Test Book')
with patch(
'bookwyrm.activitypub.base_activity.set_related_field.delay'):
incoming.handle_update_work({'object': bookdata})
book = models.Work.objects.get(id=book.id)
self.assertEqual(book.title, 'Piranesi')
def test_handle_blocks(self):
''' create a "block" database entry from an activity '''
self.local_user.followers.add(self.remote_user)
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
models.UserFollowRequest.objects.create(
user_subject=self.local_user,
user_object=self.remote_user)
self.assertTrue(models.UserFollows.objects.exists())
self.assertTrue(models.UserFollowRequest.objects.exists())
activity = {
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/9e1f41ac-9ddd-4159",
"type": "Block",
"actor": "https://example.com/users/rat",
"object": "https://example.com/user/mouse"
}
incoming.handle_block(activity)
block = models.UserBlocks.objects.get()
self.assertEqual(block.user_subject, self.remote_user)
self.assertEqual(block.user_object, self.local_user)
self.assertEqual(
block.remote_id, 'https://example.com/9e1f41ac-9ddd-4159')
self.assertFalse(models.UserFollows.objects.exists())
self.assertFalse(models.UserFollowRequest.objects.exists())
def test_handle_unblock(self):
''' unblock a user '''
self.remote_user.blocks.add(self.local_user)
block = models.UserBlocks.objects.get()
block.remote_id = 'https://example.com/9e1f41ac-9ddd-4159'
block.save()
self.assertEqual(block.user_subject, self.remote_user)
self.assertEqual(block.user_object, self.local_user)
activity = {'type': 'Undo', 'object': {
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/9e1f41ac-9ddd-4159",
"type": "Block",
"actor": "https://example.com/users/rat",
"object": "https://example.com/user/mouse"
}}
incoming.handle_unblock(activity)
self.assertFalse(models.UserBlocks.objects.exists())

View file

@ -105,6 +105,8 @@ class BookViews(TestCase):
request.user = self.local_user
self.remote_user.followers.add(self.local_user)
self.assertEqual(self.remote_user.followers.count(), 1)
# need to see if this ACTUALLY broadcasts
raise ValueError()
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
views.unfollow(request)

View file

@ -1,13 +1,17 @@
''' tests incoming activities'''
from datetime import datetime
import json
import pathlib
from unittest.mock import patch
from django.http import HttpResponseNotAllowed, HttpResponseNotFound
from django.test import TestCase, Client
import responses
from bookwyrm import models, views
#pylint: disable=too-many-public-methods
class Inbox(TestCase):
''' readthrough tests '''
def setUp(self):
@ -230,3 +234,455 @@ class Inbox(TestCase):
self.assertEqual(book_list.curation, 'curated')
self.assertEqual(book_list.description, 'summary text')
self.assertEqual(book_list.remote_id, 'https://example.com/list/22')
def test_handle_follow(self):
''' remote user wants to follow local user '''
activity = {
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/users/rat/follows/123",
"type": "Follow",
"actor": "https://example.com/users/rat",
"object": "https://example.com/user/mouse"
}
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
views.inbox.activity_task(activity)
# notification created
notification = models.Notification.objects.get()
self.assertEqual(notification.user, self.local_user)
self.assertEqual(notification.notification_type, 'FOLLOW')
# the request should have been deleted
requests = models.UserFollowRequest.objects.all()
self.assertEqual(list(requests), [])
# the follow relationship should exist
follow = models.UserFollows.objects.get(user_object=self.local_user)
self.assertEqual(follow.user_subject, self.remote_user)
def test_handle_follow_manually_approved(self):
''' needs approval before following '''
activity = {
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/users/rat/follows/123",
"type": "Follow",
"actor": "https://example.com/users/rat",
"object": "https://example.com/user/mouse"
}
self.local_user.manually_approves_followers = True
self.local_user.save(broadcast=False)
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
views.inbox.activity_task(activity)
# notification created
notification = models.Notification.objects.get()
self.assertEqual(notification.user, self.local_user)
self.assertEqual(notification.notification_type, 'FOLLOW_REQUEST')
# the request should exist
request = models.UserFollowRequest.objects.get()
self.assertEqual(request.user_subject, self.remote_user)
self.assertEqual(request.user_object, self.local_user)
# the follow relationship should not exist
follow = models.UserFollows.objects.all()
self.assertEqual(list(follow), [])
def test_handle_unfollow(self):
''' remove a relationship '''
activity = {
"type": "Undo",
"@context": "https://www.w3.org/ns/activitystreams",
"object": {
"id": "https://example.com/users/rat/follows/123",
"type": "Follow",
"actor": "https://example.com/users/rat",
"object": "https://example.com/user/mouse"
}
}
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
models.UserFollows.objects.create(
user_subject=self.remote_user, user_object=self.local_user)
self.assertEqual(self.remote_user, self.local_user.followers.first())
views.inbox.activity_task(activity)
self.assertIsNone(self.local_user.followers.first())
def test_handle_follow_accept(self):
''' a remote user approved a follow request from local '''
activity = {
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/users/rat/follows/123#accepts",
"type": "Accept",
"actor": "https://example.com/users/rat",
"object": {
"id": "https://example.com/users/rat/follows/123",
"type": "Follow",
"actor": "https://example.com/user/mouse",
"object": "https://example.com/users/rat"
}
}
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
models.UserFollowRequest.objects.create(
user_subject=self.local_user,
user_object=self.remote_user
)
self.assertEqual(models.UserFollowRequest.objects.count(), 1)
views.inbox.activity_task(activity)
# request should be deleted
self.assertEqual(models.UserFollowRequest.objects.count(), 0)
# relationship should be created
follows = self.remote_user.followers
self.assertEqual(follows.count(), 1)
self.assertEqual(follows.first(), self.local_user)
def test_handle_follow_reject(self):
''' turn down a follow request '''
activity = {
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/users/rat/follows/123#accepts",
"type": "Reject",
"actor": "https://example.com/users/rat",
"object": {
"id": "https://example.com/users/rat/follows/123",
"type": "Follow",
"actor": "https://example.com/user/mouse",
"object": "https://example.com/users/rat"
}
}
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
models.UserFollowRequest.objects.create(
user_subject=self.local_user,
user_object=self.remote_user
)
self.assertEqual(models.UserFollowRequest.objects.count(), 1)
views.inbox.activity_task(activity)
# request should be deleted
self.assertEqual(models.UserFollowRequest.objects.count(), 0)
# relationship should be created
follows = self.remote_user.followers
self.assertEqual(follows.count(), 0)
def test_handle_update_list(self):
''' a new list '''
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
book_list = models.List.objects.create(
name='hi', remote_id='https://example.com/list/22',
user=self.local_user)
activity = {
'object': {
"id": "https://example.com/list/22",
"type": "BookList",
"totalItems": 1,
"first": "https://example.com/list/22?page=1",
"last": "https://example.com/list/22?page=1",
"name": "Test List",
"owner": "https://example.com/user/mouse",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"https://example.com/user/mouse/followers"
],
"summary": "summary text",
"curation": "curated",
"@context": "https://www.w3.org/ns/activitystreams"
}
}
views.inbox.activity_task(activity)
book_list.refresh_from_db()
self.assertEqual(book_list.name, 'Test List')
self.assertEqual(book_list.curation, 'curated')
self.assertEqual(book_list.description, 'summary text')
self.assertEqual(book_list.remote_id, 'https://example.com/list/22')
def test_handle_delete_status(self):
''' remove a status '''
self.status.user = self.remote_user
self.status.save(broadcast=False)
self.assertFalse(self.status.deleted)
activity = {
'type': 'Delete',
'id': '%s/activity' % self.status.remote_id,
'actor': self.remote_user.remote_id,
'object': {'id': self.status.remote_id},
}
views.inbox.activity_task(activity)
# deletion doens't remove the status, it turns it into a tombstone
status = models.Status.objects.get()
self.assertTrue(status.deleted)
self.assertIsInstance(status.deleted_date, datetime)
def test_handle_delete_status_notifications(self):
''' remove a status with related notifications '''
self.status.user = self.remote_user
self.status.save(broadcast=False)
models.Notification.objects.create(
related_status=self.status,
user=self.local_user,
notification_type='MENTION'
)
# this one is innocent, don't delete it
notif = models.Notification.objects.create(
user=self.local_user,
notification_type='MENTION'
)
self.assertFalse(self.status.deleted)
self.assertEqual(models.Notification.objects.count(), 2)
activity = {
'type': 'Delete',
'id': '%s/activity' % self.status.remote_id,
'actor': self.remote_user.remote_id,
'object': {'id': self.status.remote_id},
}
views.inbox.activity_task(activity)
# deletion doens't remove the status, it turns it into a tombstone
status = models.Status.objects.get()
self.assertTrue(status.deleted)
self.assertIsInstance(status.deleted_date, datetime)
# notifications should be truly deleted
self.assertEqual(models.Notification.objects.count(), 1)
self.assertEqual(models.Notification.objects.get(), notif)
def test_handle_favorite(self):
''' fav a status '''
activity = {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': 'https://example.com/fav/1',
'actor': 'https://example.com/users/rat',
'published': 'Mon, 25 May 2020 19:31:20 GMT',
'object': 'https://example.com/status/1',
}
views.inbox.activity_task(activity)
fav = models.Favorite.objects.get(remote_id='https://example.com/fav/1')
self.assertEqual(fav.status, self.status)
self.assertEqual(fav.remote_id, 'https://example.com/fav/1')
self.assertEqual(fav.user, self.remote_user)
def test_handle_unfavorite(self):
''' fav a status '''
activity = {
'id': 'https://example.com/fav/1#undo',
'type': 'Undo',
'object': {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': 'https://example.com/fav/1',
'actor': 'https://example.com/users/rat',
'published': 'Mon, 25 May 2020 19:31:20 GMT',
'object': 'https://example.com/fav/1',
}
}
models.Favorite.objects.create(
status=self.status,
user=self.remote_user,
remote_id='https://example.com/fav/1')
self.assertEqual(models.Favorite.objects.count(), 1)
views.inbox.activity_task(activity)
self.assertEqual(models.Favorite.objects.count(), 0)
def test_handle_boost(self):
''' boost a status '''
self.assertEqual(models.Notification.objects.count(), 0)
activity = {
'type': 'Announce',
'id': '%s/boost' % self.status.remote_id,
'actor': self.remote_user.remote_id,
'object': self.status.to_activity(),
}
with patch('bookwyrm.models.status.Status.ignore_activity') \
as discarder:
discarder.return_value = False
views.inbox.activity_task(activity)
boost = models.Boost.objects.get()
self.assertEqual(boost.boosted_status, self.status)
notification = models.Notification.objects.get()
self.assertEqual(notification.user, self.local_user)
self.assertEqual(notification.related_status, self.status)
@responses.activate
def test_handle_discarded_boost(self):
''' test a boost of a mastodon status that will be discarded '''
activity = {
'type': 'Announce',
'id': 'http://www.faraway.com/boost/12',
'actor': self.remote_user.remote_id,
'object': self.status.to_activity(),
}
responses.add(
responses.GET,
'http://www.faraway.com/boost/12',
json={'id': 'http://www.faraway.com/boost/12'},
status=200)
views.inbox.activity_task(activity)
self.assertEqual(models.Boost.objects.count(), 0)
def test_handle_unboost(self):
''' undo a boost '''
activity = {
'type': 'Undo',
'object': {
'type': 'Announce',
'id': '%s/boost' % self.status.remote_id,
'actor': self.local_user.remote_id,
'object': self.status.to_activity(),
}
}
models.Boost.objects.create(
boosted_status=self.status, user=self.remote_user)
views.inbox.activity_task(activity)
def test_handle_add_book(self):
''' shelving a book '''
book = models.Edition.objects.create(
title='Test', remote_id='https://bookwyrm.social/book/37292')
shelf = models.Shelf.objects.create(
user=self.remote_user, name='Test Shelf')
shelf.remote_id = 'https://bookwyrm.social/user/mouse/shelf/to-read'
shelf.save()
activity = {
"id": "https://bookwyrm.social/shelfbook/6189#add",
"type": "Add",
"actor": "https://example.com/users/rat",
"object": "https://bookwyrm.social/book/37292",
"target": "https://bookwyrm.social/user/mouse/shelf/to-read",
"@context": "https://www.w3.org/ns/activitystreams"
}
views.inbox.activity_task(activity)
self.assertEqual(shelf.books.first(), book)
def test_handle_update_user(self):
''' update an existing user '''
# we only do this with remote users
self.local_user.local = False
self.local_user.save()
datafile = pathlib.Path(__file__).parent.joinpath(
'data/ap_user.json')
userdata = json.loads(datafile.read_bytes())
del userdata['icon']
self.assertIsNone(self.local_user.name)
views.inbox.activity_task({'object': userdata})
user = models.User.objects.get(id=self.local_user.id)
self.assertEqual(user.name, 'MOUSE?? MOUSE!!')
self.assertEqual(user.username, 'mouse@example.com')
self.assertEqual(user.localname, 'mouse')
def test_handle_update_edition(self):
''' update an existing edition '''
datafile = pathlib.Path(__file__).parent.joinpath(
'data/bw_edition.json')
bookdata = json.loads(datafile.read_bytes())
models.Work.objects.create(
title='Test Work', remote_id='https://bookwyrm.social/book/5988')
book = models.Edition.objects.create(
title='Test Book', remote_id='https://bookwyrm.social/book/5989')
del bookdata['authors']
self.assertEqual(book.title, 'Test Book')
with patch(
'bookwyrm.activitypub.base_activity.set_related_field.delay'):
views.inbox.activity_task({'object': bookdata})
book = models.Edition.objects.get(id=book.id)
self.assertEqual(book.title, 'Piranesi')
def test_handle_update_work(self):
''' update an existing edition '''
datafile = pathlib.Path(__file__).parent.joinpath(
'data/bw_work.json')
bookdata = json.loads(datafile.read_bytes())
book = models.Work.objects.create(
title='Test Book', remote_id='https://bookwyrm.social/book/5988')
del bookdata['authors']
self.assertEqual(book.title, 'Test Book')
with patch(
'bookwyrm.activitypub.base_activity.set_related_field.delay'):
views.inbox.activity_task({'object': bookdata})
book = models.Work.objects.get(id=book.id)
self.assertEqual(book.title, 'Piranesi')
def test_handle_blocks(self):
''' create a "block" database entry from an activity '''
self.local_user.followers.add(self.remote_user)
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
models.UserFollowRequest.objects.create(
user_subject=self.local_user,
user_object=self.remote_user)
self.assertTrue(models.UserFollows.objects.exists())
self.assertTrue(models.UserFollowRequest.objects.exists())
activity = {
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/9e1f41ac-9ddd-4159",
"type": "Block",
"actor": "https://example.com/users/rat",
"object": "https://example.com/user/mouse"
}
views.inbox.activity_task(activity)
block = models.UserBlocks.objects.get()
self.assertEqual(block.user_subject, self.remote_user)
self.assertEqual(block.user_object, self.local_user)
self.assertEqual(
block.remote_id, 'https://example.com/9e1f41ac-9ddd-4159')
self.assertFalse(models.UserFollows.objects.exists())
self.assertFalse(models.UserFollowRequest.objects.exists())
def test_handle_unblock(self):
''' unblock a user '''
self.remote_user.blocks.add(self.local_user)
block = models.UserBlocks.objects.get()
block.remote_id = 'https://example.com/9e1f41ac-9ddd-4159'
block.save()
self.assertEqual(block.user_subject, self.remote_user)
self.assertEqual(block.user_object, self.local_user)
activity = {'type': 'Undo', 'object': {
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/9e1f41ac-9ddd-4159",
"type": "Block",
"actor": "https://example.com/users/rat",
"object": "https://example.com/user/mouse"
}}
views.inbox.activity_task(activity)
self.assertFalse(models.UserBlocks.objects.exists())

View file

@ -22,8 +22,6 @@ def follow(request):
user_object=to_follow,
)
if to_follow.local and not to_follow.manually_approves_followers:
rel.accept()
return redirect(to_follow.local_path)