Merge pull request #625 from mouse-reeve/inbox-refactor

Inbox refactor
This commit is contained in:
Mouse Reeve
2021-02-24 13:34:59 -08:00
committed by GitHub
43 changed files with 958 additions and 887 deletions

View File

@ -79,7 +79,7 @@ class BaseActivity(TestCase):
def test_resolve_remote_id(self):
''' look up or load remote data '''
# existing item
result = resolve_remote_id(models.User, 'http://example.com/a/b')
result = resolve_remote_id('http://example.com/a/b', model=models.User)
self.assertEqual(result, self.user)
# remote item
@ -91,7 +91,7 @@ class BaseActivity(TestCase):
with patch('bookwyrm.models.user.set_remote_server.delay'):
result = resolve_remote_id(
models.User, 'https://example.com/user/mouse')
'https://example.com/user/mouse', model=models.User)
self.assertIsInstance(result, models.User)
self.assertEqual(result.remote_id, 'https://example.com/user/mouse')
self.assertEqual(result.name, 'MOUSE?? MOUSE!!')
@ -100,46 +100,8 @@ class BaseActivity(TestCase):
''' catch mismatch between activity type and model type '''
instance = ActivityObject(id='a', type='b')
with self.assertRaises(ActivitySerializerError):
instance.to_model(models.User)
instance.to_model(model=models.User)
def test_to_model_simple_fields(self):
''' test setting simple fields '''
self.assertIsNone(self.user.name)
activity = activitypub.Person(
id=self.user.remote_id,
name='New Name',
preferredUsername='mouse',
inbox='http://www.com/',
outbox='http://www.com/',
followers='',
summary='',
publicKey=None,
endpoints={},
)
activity.to_model(models.User, self.user)
self.assertEqual(self.user.name, 'New Name')
def test_to_model_foreign_key(self):
''' test setting one to one/foreign key '''
activity = activitypub.Person(
id=self.user.remote_id,
name='New Name',
preferredUsername='mouse',
inbox='http://www.com/',
outbox='http://www.com/',
followers='',
summary='',
publicKey=self.user.key_pair.to_activity(),
endpoints={},
)
activity.publicKey['publicKeyPem'] = 'hi im secure'
activity.to_model(models.User, self.user)
self.assertEqual(self.user.key_pair.public_key, 'hi im secure')
@responses.activate
def test_to_model_image(self):
@ -152,9 +114,15 @@ class BaseActivity(TestCase):
outbox='http://www.com/',
followers='',
summary='',
publicKey=None,
publicKey={
'id': 'hi',
'owner': self.user.remote_id,
'publicKeyPem': 'hi'},
endpoints={},
icon={'url': 'http://www.example.com/image.jpg'}
icon={
'type': 'Image',
'url': 'http://www.example.com/image.jpg'
}
)
responses.add(
@ -169,9 +137,10 @@ class BaseActivity(TestCase):
# this would trigger a broadcast because it's a local user
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
activity.to_model(models.User, self.user)
self.assertIsNotNone(self.user.avatar.name)
activity.to_model(model=models.User, instance=self.user)
self.assertIsNotNone(self.user.avatar.file)
self.assertEqual(self.user.name, 'New Name')
self.assertEqual(self.user.key_pair.public_key, 'hi')
def test_to_model_many_to_many(self):
''' annoying that these all need special handling '''
@ -202,7 +171,7 @@ class BaseActivity(TestCase):
},
]
)
update_data.to_model(models.Status, instance=status)
update_data.to_model(model=models.Status, instance=status)
self.assertEqual(status.mention_users.first(), self.user)
self.assertEqual(status.mention_books.first(), book)
@ -239,7 +208,7 @@ class BaseActivity(TestCase):
# sets the celery task call to the function call
with patch(
'bookwyrm.activitypub.base_activity.set_related_field.delay'):
update_data.to_model(models.Status, instance=status)
update_data.to_model(model=models.Status, instance=status)
self.assertIsNone(status.attachments.first())

View File

@ -25,7 +25,7 @@ class Person(TestCase):
def test_user_to_model(self):
activity = activitypub.Person(**self.user_data)
with patch('bookwyrm.models.user.set_remote_server.delay'):
user = activity.to_model(models.User)
user = activity.to_model(model=models.User)
self.assertEqual(user.username, 'mouse@example.com')
self.assertEqual(user.remote_id, 'https://example.com/user/mouse')
self.assertFalse(user.local)

View File

@ -46,7 +46,7 @@ class Quotation(TestCase):
def test_activity_to_model(self):
''' create a model instance from an activity object '''
activity = activitypub.Quotation(**self.status_data)
quotation = activity.to_model(models.Quotation)
quotation = activity.to_model(model=models.Quotation)
self.assertEqual(quotation.book, self.book)
self.assertEqual(quotation.user, self.user)

View File

@ -92,11 +92,26 @@ class Openlibrary(TestCase):
responses.add(
responses.GET,
'https://openlibrary.org/authors/OL382982A',
json={'hi': 'there'},
json={
"name": "George Elliott",
"personal_name": "George Elliott",
"last_modified": {
"type": "/type/datetime",
"value": "2008-08-31 10:09:33.413686"
},
"key": "/authors/OL453734A",
"type": {
"key": "/type/author"
},
"id": 1259965,
"revision": 2
},
status=200)
results = self.connector.get_authors_from_data(self.work_data)
for result in results:
self.assertIsInstance(result, models.Author)
result = list(results)[0]
self.assertIsInstance(result, models.Author)
self.assertEqual(result.name, 'George Elliott')
self.assertEqual(result.openlibrary_key, 'OL453734A')
def test_get_cover_url(self):
@ -201,8 +216,11 @@ class Openlibrary(TestCase):
'https://openlibrary.org/authors/OL382982A',
json={'hi': 'there'},
status=200)
result = self.connector.create_edition_from_data(
work, self.edition_data)
with patch('bookwyrm.connectors.openlibrary.Connector.' \
'get_authors_from_data') as mock:
mock.return_value = []
result = self.connector.create_edition_from_data(
work, self.edition_data)
self.assertEqual(result.parent_work, work)
self.assertEqual(result.title, 'Sabriel')
self.assertEqual(result.isbn_10, '0060273224')

View File

@ -30,6 +30,12 @@ class ActivitypubMixins(TestCase):
outbox='https://example.com/users/rat/outbox',
)
self.object_mock = {
'to': 'to field', 'cc': 'cc field',
'content': 'hi', 'id': 'bip', 'type': 'Test',
'published': '2020-12-04T17:52:22.623807+00:00',
}
# ActivitypubMixin
def test_to_activity(self):
@ -290,40 +296,12 @@ class ActivitypubMixins(TestCase):
id=1, user=self.local_user, deleted=True).save()
def test_to_create_activity(self):
''' wrapper for ActivityPub "create" action '''
object_activity = {
'to': 'to field', 'cc': 'cc field',
'content': 'hi',
'published': '2020-12-04T17:52:22.623807+00:00',
}
MockSelf = namedtuple('Self', ('remote_id', 'to_activity'))
mock_self = MockSelf(
'https://example.com/status/1',
lambda *args: object_activity
)
activity = ObjectMixin.to_create_activity(
mock_self, self.local_user)
self.assertEqual(
activity['id'],
'https://example.com/status/1/activity'
)
self.assertEqual(activity['actor'], self.local_user.remote_id)
self.assertEqual(activity['type'], 'Create')
self.assertEqual(activity['to'], 'to field')
self.assertEqual(activity['cc'], 'cc field')
self.assertEqual(activity['object'], object_activity)
self.assertEqual(
activity['signature'].creator,
'%s#main-key' % self.local_user.remote_id
)
def test_to_delete_activity(self):
''' wrapper for Delete activity '''
MockSelf = namedtuple('Self', ('remote_id', 'to_activity'))
mock_self = MockSelf(
'https://example.com/status/1',
lambda *args: {}
lambda *args: self.object_mock
)
activity = ObjectMixin.to_delete_activity(
mock_self, self.local_user)
@ -346,7 +324,7 @@ class ActivitypubMixins(TestCase):
MockSelf = namedtuple('Self', ('remote_id', 'to_activity'))
mock_self = MockSelf(
'https://example.com/status/1',
lambda *args: {}
lambda *args: self.object_mock
)
activity = ObjectMixin.to_update_activity(
mock_self, self.local_user)
@ -361,7 +339,7 @@ class ActivitypubMixins(TestCase):
self.assertEqual(
activity['to'],
['https://www.w3.org/ns/activitystreams#Public'])
self.assertEqual(activity['object'], {})
self.assertIsInstance(activity['object'], dict)
# Activity mixin
@ -370,7 +348,7 @@ class ActivitypubMixins(TestCase):
MockSelf = namedtuple('Self', ('remote_id', 'to_activity', 'user'))
mock_self = MockSelf(
'https://example.com/status/1',
lambda *args: {},
lambda *args: self.object_mock,
self.local_user,
)
activity = ActivityMixin.to_undo_activity(mock_self)
@ -380,4 +358,4 @@ class ActivitypubMixins(TestCase):
)
self.assertEqual(activity['actor'], self.local_user.remote_id)
self.assertEqual(activity['type'], 'Undo')
self.assertEqual(activity['object'], {})
self.assertIsInstance(activity['object'], dict)

View File

@ -17,6 +17,7 @@ from django.db import models
from django.test import TestCase
from django.utils import timezone
from bookwyrm import activitypub
from bookwyrm.activitypub.base_activity import ActivityObject
from bookwyrm.models import fields, User, Status
from bookwyrm.models.base_model import BookWyrmModel
@ -275,7 +276,7 @@ class ActivitypubFields(TestCase):
'rat', 'rat@rat.rat', 'ratword',
local=True, localname='rat')
with patch('bookwyrm.models.user.set_remote_server.delay'):
value = instance.field_from_activity(userdata)
value = instance.field_from_activity(activitypub.Person(**userdata))
self.assertIsInstance(value, User)
self.assertNotEqual(value, unrelated_user)
self.assertEqual(value.remote_id, 'https://example.com/user/mouse')
@ -300,7 +301,7 @@ class ActivitypubFields(TestCase):
local=True, localname='rat')
with patch('bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast'):
value = instance.field_from_activity(userdata)
value = instance.field_from_activity(activitypub.Person(**userdata))
self.assertEqual(value, user)

View File

@ -171,6 +171,8 @@ class ImportJob(TestCase):
'bookwyrm.connectors.connector_manager.first_search_result'
) as search:
search.return_value = result
book = self.item_1.get_book_from_isbn()
with patch('bookwyrm.connectors.openlibrary.Connector.' \
'get_authors_from_data'):
book = self.item_1.get_book_from_isbn()
self.assertEqual(book.title, 'Sabriel')

View File

@ -23,19 +23,6 @@ class Relationship(TestCase):
self.local_user.remote_id = 'http://local.com/user/mouse'
self.local_user.save(broadcast=False)
def test_user_follows(self):
''' create a follow relationship '''
with patch('bookwyrm.models.activitypub_mixin.ActivityMixin.broadcast'):
rel = models.UserFollows.objects.create(
user_subject=self.local_user,
user_object=self.remote_user
)
activity = rel.to_activity()
self.assertEqual(activity['id'], rel.remote_id)
self.assertEqual(activity['actor'], self.local_user.remote_id)
self.assertEqual(activity['object'], self.remote_user.remote_id)
def test_user_follows_from_request(self):
''' convert a follow request into a follow '''
@ -116,13 +103,15 @@ class Relationship(TestCase):
self.assertEqual(user.remote_id, self.local_user.remote_id)
self.assertEqual(activity['type'], 'Accept')
self.assertEqual(activity['actor'], self.local_user.remote_id)
self.assertEqual(
activity['object']['id'], request.remote_id)
self.assertEqual(activity['object']['id'], 'https://www.hi.com/')
self.local_user.manually_approves_followers = True
self.local_user.save(broadcast=False)
models.UserFollowRequest.broadcast = mock_broadcast
request = models.UserFollowRequest.objects.create(
user_subject=self.remote_user,
user_object=self.local_user,
remote_id='https://www.hi.com/'
)
request.accept()
@ -145,6 +134,8 @@ class Relationship(TestCase):
activity['object']['id'], request.remote_id)
models.UserFollowRequest.broadcast = mock_reject
self.local_user.manually_approves_followers = True
self.local_user.save(broadcast=False)
request = models.UserFollowRequest.objects.create(
user_subject=self.remote_user,
user_object=self.local_user,

View File

@ -4,6 +4,7 @@ from django.test import TestCase
from bookwyrm import models, settings
#pylint: disable=unused-argument
class Shelf(TestCase):
''' some activitypub oddness ahead '''
def setUp(self):

View File

@ -63,7 +63,7 @@ class Status(TestCase):
self.assertEqual(models.Review().status_type, 'Review')
self.assertEqual(models.Quotation().status_type, 'Quotation')
self.assertEqual(models.Comment().status_type, 'Comment')
self.assertEqual(models.Boost().status_type, 'Boost')
self.assertEqual(models.Boost().status_type, 'Announce')
def test_boostable(self, _):
''' can a status be boosted, based on privacy '''
@ -284,3 +284,24 @@ class Status(TestCase):
with self.assertRaises(IntegrityError):
models.Notification.objects.create(
user=self.user, notification_type='GLORB')
def test_create_broadcast(self, broadcast_mock):
''' should send out two verions of a status on create '''
models.Comment.objects.create(
content='hi', user=self.user, book=self.book)
self.assertEqual(broadcast_mock.call_count, 2)
pure_call = broadcast_mock.call_args_list[0]
bw_call = broadcast_mock.call_args_list[1]
self.assertEqual(pure_call[1]['software'], 'other')
args = pure_call[0][0]
self.assertEqual(args['type'], 'Create')
self.assertEqual(args['object']['type'], 'Note')
self.assertTrue('content' in args['object'])
self.assertEqual(bw_call[1]['software'], 'bookwyrm')
args = bw_call[0][0]
self.assertEqual(args['type'], 'Create')
self.assertEqual(args['object']['type'], 'Comment')

View File

@ -1,3 +1,4 @@
''' getting and verifying signatures '''
import time
from collections import namedtuple
from urllib.parse import urlsplit
@ -12,31 +13,33 @@ import pytest
from django.test import TestCase, Client
from django.utils.http import http_date
from bookwyrm.models import User
from bookwyrm import models
from bookwyrm.activitypub import Follow
from bookwyrm.settings import DOMAIN
from bookwyrm.signatures import create_key_pair, make_signature, make_digest
def get_follow_data(follower, followee):
follow_activity = Follow(
def get_follow_activity(follower, followee):
''' generates a test activity '''
return Follow(
id='https://test.com/user/follow/id',
actor=follower.remote_id,
object=followee.remote_id,
).serialize()
return json.dumps(follow_activity)
KeyPair = namedtuple('KeyPair', ('private_key', 'public_key'))
Sender = namedtuple('Sender', ('remote_id', 'key_pair'))
class Signature(TestCase):
''' signature test '''
def setUp(self):
self.mouse = User.objects.create_user(
''' create users and test data '''
self.mouse = models.User.objects.create_user(
'mouse@%s' % DOMAIN, 'mouse@example.com', '',
local=True, localname='mouse')
self.rat = User.objects.create_user(
self.rat = models.User.objects.create_user(
'rat@%s' % DOMAIN, 'rat@example.com', '',
local=True, localname='rat')
self.cat = User.objects.create_user(
self.cat = models.User.objects.create_user(
'cat@%s' % DOMAIN, 'cat@example.com', '',
local=True, localname='cat')
@ -47,6 +50,8 @@ class Signature(TestCase):
KeyPair(private_key, public_key)
)
models.SiteSettings.objects.create()
def send(self, signature, now, data, digest):
''' test request '''
c = Client()
@ -63,7 +68,7 @@ class Signature(TestCase):
}
)
def send_test_request(
def send_test_request(#pylint: disable=too-many-arguments
self,
sender,
signer=None,
@ -72,15 +77,16 @@ class Signature(TestCase):
date=None):
''' sends a follow request to the "rat" user '''
now = date or http_date()
data = json.dumps(get_follow_data(sender, self.rat))
data = json.dumps(get_follow_activity(sender, self.rat))
digest = digest or make_digest(data)
signature = make_signature(
signer or sender, self.rat.inbox, now, digest)
with patch('bookwyrm.incoming.handle_follow.delay'):
with patch('bookwyrm.views.inbox.activity_task.delay'):
with patch('bookwyrm.models.user.set_remote_server.delay'):
return self.send(signature, now, send_data or data, digest)
def test_correct_signature(self):
''' this one should just work '''
response = self.send_test_request(sender=self.mouse)
self.assertEqual(response.status_code, 200)
@ -120,6 +126,7 @@ class Signature(TestCase):
@responses.activate
def test_key_needs_refresh(self):
''' an out of date key should be updated and the new key work '''
datafile = pathlib.Path(__file__).parent.joinpath('data/ap_user.json')
data = json.loads(datafile.read_bytes())
data['id'] = self.fake_remote.remote_id
@ -165,6 +172,7 @@ class Signature(TestCase):
@responses.activate
def test_nonexistent_signer(self):
''' fail when unable to look up signer '''
responses.add(
responses.GET,
self.fake_remote.remote_id,
@ -180,11 +188,12 @@ class Signature(TestCase):
with patch('bookwyrm.activitypub.resolve_remote_id'):
response = self.send_test_request(
self.mouse,
send_data=get_follow_data(self.mouse, self.cat))
send_data=get_follow_activity(self.mouse, self.cat))
self.assertEqual(response.status_code, 401)
@pytest.mark.integration
def test_invalid_digest(self):
''' signature digest must be valid '''
with patch('bookwyrm.activitypub.resolve_remote_id'):
response = self.send_test_request(
self.mouse,

View File

@ -77,7 +77,6 @@ class BookViews(TestCase):
self.assertEqual(rel.status, 'follow_request')
def test_handle_follow_local(self):
''' send a follow request '''
rat = models.User.objects.create_user(
@ -105,14 +104,18 @@ class BookViews(TestCase):
request.user = self.local_user
self.remote_user.followers.add(self.local_user)
self.assertEqual(self.remote_user.followers.count(), 1)
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay') \
as mock:
views.unfollow(request)
self.assertEqual(mock.call_count, 1)
self.assertEqual(self.remote_user.followers.count(), 0)
def test_handle_accept(self):
''' accept a follow request '''
self.local_user.manually_approves_followers = True
self.local_user.save(broadcast=False)
request = self.factory.post('', {'user': self.remote_user.username})
request.user = self.local_user
rel = models.UserFollowRequest.objects.create(
@ -132,6 +135,8 @@ class BookViews(TestCase):
def test_handle_reject(self):
''' reject a follow request '''
self.local_user.manually_approves_followers = True
self.local_user.save(broadcast=False)
request = self.factory.post('', {'user': self.remote_user.username})
request.user = self.local_user
rel = models.UserFollowRequest.objects.create(

View File

@ -1,23 +1,22 @@
''' test incoming activities '''
''' tests 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
from django.http import HttpResponseNotAllowed, HttpResponseNotFound
from django.test import TestCase, Client
import responses
from bookwyrm import models, incoming
from bookwyrm import models, views
#pylint: disable=too-many-public-methods
class Incoming(TestCase):
''' a lot here: all handlers for receiving activitypub requests '''
class Inbox(TestCase):
''' readthrough tests '''
def setUp(self):
''' we need basic things, like users '''
''' basic user and book data '''
self.client = Client()
self.local_user = models.User.objects.create_user(
'mouse@example.com', 'mouse@mouse.com', 'mouseword',
local=True, localname='mouse')
@ -37,78 +36,207 @@ class Incoming(TestCase):
content='Test status',
remote_id='https://example.com/status/1',
)
self.factory = RequestFactory()
self.create_json = {
'id': 'hi',
'type': 'Create',
'actor': 'hi',
"to": [
"https://www.w3.org/ns/activitystreams#public"
],
"cc": [
"https://example.com/user/mouse/followers"
],
'object': {}
}
models.SiteSettings.objects.create()
def test_inbox_invalid_get(self):
''' shouldn't try to handle if the user is not found '''
request = self.factory.get('https://www.example.com/')
self.assertIsInstance(
incoming.inbox(request, 'anything'), HttpResponseNotAllowed)
self.assertIsInstance(
incoming.shared_inbox(request), HttpResponseNotAllowed)
result = self.client.get(
'/inbox', content_type="application/json"
)
self.assertIsInstance(result, HttpResponseNotAllowed)
def test_inbox_invalid_user(self):
''' shouldn't try to handle if the user is not found '''
request = self.factory.post('https://www.example.com/')
self.assertIsInstance(
incoming.inbox(request, 'fish@tomato.com'), HttpResponseNotFound)
def test_inbox_invalid_no_object(self):
''' json is missing "object" field '''
request = self.factory.post(
self.local_user.shared_inbox, data={})
self.assertIsInstance(
incoming.shared_inbox(request), HttpResponseBadRequest)
result = self.client.post(
'/user/bleh/inbox',
'{"type": "Test", "object": "exists"}',
content_type="application/json"
)
self.assertIsInstance(result, HttpResponseNotFound)
def test_inbox_invalid_bad_signature(self):
''' bad request for invalid signature '''
request = self.factory.post(
self.local_user.shared_inbox,
'{"type": "Test", "object": "exists"}',
content_type='application/json')
with patch('bookwyrm.incoming.has_valid_signature') as mock_has_valid:
mock_has_valid.return_value = False
self.assertEqual(
incoming.shared_inbox(request).status_code, 401)
with patch('bookwyrm.views.inbox.has_valid_signature') as mock_valid:
mock_valid.return_value = False
result = self.client.post(
'/user/mouse/inbox',
'{"type": "Test", "object": "exists"}',
content_type="application/json"
)
self.assertEqual(result.status_code, 401)
def test_inbox_invalid_bad_signature_delete(self):
''' invalid signature for Delete is okay though '''
request = self.factory.post(
self.local_user.shared_inbox,
'{"type": "Delete", "object": "exists"}',
content_type='application/json')
with patch('bookwyrm.incoming.has_valid_signature') as mock_has_valid:
mock_has_valid.return_value = False
self.assertEqual(
incoming.shared_inbox(request).status_code, 200)
with patch('bookwyrm.views.inbox.has_valid_signature') as mock_valid:
mock_valid.return_value = False
result = self.client.post(
'/user/mouse/inbox',
'{"type": "Delete", "object": "exists"}',
content_type="application/json"
)
self.assertEqual(result.status_code, 200)
def test_inbox_unknown_type(self):
''' never heard of that activity type, don't have a handler for it '''
request = self.factory.post(
self.local_user.shared_inbox,
'{"type": "Fish", "object": "exists"}',
content_type='application/json')
with patch('bookwyrm.incoming.has_valid_signature') as mock_has_valid:
mock_has_valid.return_value = True
self.assertIsInstance(
incoming.shared_inbox(request), HttpResponseNotFound)
with patch('bookwyrm.views.inbox.has_valid_signature') as mock_valid:
result = self.client.post(
'/inbox',
'{"type": "Fish", "object": "exists"}',
content_type="application/json"
)
mock_valid.return_value = True
self.assertIsInstance(result, HttpResponseNotFound)
def test_inbox_success(self):
''' a known type, for which we start a task '''
request = self.factory.post(
self.local_user.shared_inbox,
'{"type": "Accept", "object": "exists"}',
content_type='application/json')
with patch('bookwyrm.incoming.has_valid_signature') as mock_has_valid:
mock_has_valid.return_value = True
activity = self.create_json
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"
}
with patch('bookwyrm.views.inbox.has_valid_signature') as mock_valid:
mock_valid.return_value = True
with patch('bookwyrm.incoming.handle_follow_accept.delay'):
self.assertEqual(
incoming.shared_inbox(request).status_code, 200)
with patch('bookwyrm.views.inbox.activity_task.delay'):
result = self.client.post(
'/inbox',
json.dumps(activity),
content_type="application/json"
)
self.assertEqual(result.status_code, 200)
def test_handle_follow(self):
def test_handle_create_status(self):
''' the "it justs works" mode '''
self.assertEqual(models.Status.objects.count(), 1)
datafile = pathlib.Path(__file__).parent.joinpath(
'../data/ap_quotation.json')
status_data = json.loads(datafile.read_bytes())
models.Edition.objects.create(
title='Test Book', remote_id='https://example.com/book/1')
activity = self.create_json
activity['object'] = status_data
views.inbox.activity_task(activity)
status = models.Quotation.objects.get()
self.assertEqual(
status.remote_id, 'https://example.com/user/mouse/quotation/13')
self.assertEqual(status.quote, 'quote body')
self.assertEqual(status.content, 'commentary')
self.assertEqual(status.user, self.local_user)
self.assertEqual(models.Status.objects.count(), 2)
# while we're here, lets ensure we avoid dupes
views.inbox.activity_task(activity)
self.assertEqual(models.Status.objects.count(), 2)
def test_handle_create_status_remote_note_with_mention(self):
''' should only create it under the right circumstances '''
self.assertEqual(models.Status.objects.count(), 1)
self.assertFalse(
models.Notification.objects.filter(user=self.local_user).exists())
datafile = pathlib.Path(__file__).parent.joinpath(
'../data/ap_note.json')
status_data = json.loads(datafile.read_bytes())
activity = self.create_json
activity['object'] = status_data
views.inbox.activity_task(activity)
status = models.Status.objects.last()
self.assertEqual(status.content, 'test content in note')
self.assertEqual(status.mention_users.first(), self.local_user)
self.assertTrue(
models.Notification.objects.filter(user=self.local_user).exists())
self.assertEqual(
models.Notification.objects.get().notification_type, 'MENTION')
def test_handle_create_status_remote_note_with_reply(self):
''' should only create it under the right circumstances '''
self.assertEqual(models.Status.objects.count(), 1)
self.assertFalse(
models.Notification.objects.filter(user=self.local_user))
datafile = pathlib.Path(__file__).parent.joinpath(
'../data/ap_note.json')
status_data = json.loads(datafile.read_bytes())
del status_data['tag']
status_data['inReplyTo'] = self.status.remote_id
activity = self.create_json
activity['object'] = status_data
views.inbox.activity_task(activity)
status = models.Status.objects.last()
self.assertEqual(status.content, 'test content in note')
self.assertEqual(status.reply_parent, self.status)
self.assertTrue(
models.Notification.objects.filter(user=self.local_user))
self.assertEqual(
models.Notification.objects.get().notification_type, 'REPLY')
def test_handle_create_list(self):
''' a new list '''
activity = self.create_json
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 = models.List.objects.get()
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_follow_x(self):
''' remote user wants to follow local user '''
activity = {
"@context": "https://www.w3.org/ns/activitystreams",
@ -118,8 +246,11 @@ class Incoming(TestCase):
"object": "https://example.com/user/mouse"
}
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
incoming.handle_follow(activity)
self.assertFalse(models.UserFollowRequest.objects.exists())
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay') \
as mock:
views.inbox.activity_task(activity)
self.assertEqual(mock.call_count, 1)
# notification created
notification = models.Notification.objects.get()
@ -127,8 +258,7 @@ class Incoming(TestCase):
self.assertEqual(notification.notification_type, 'FOLLOW')
# the request should have been deleted
requests = models.UserFollowRequest.objects.all()
self.assertEqual(list(requests), [])
self.assertFalse(models.UserFollowRequest.objects.exists())
# the follow relationship should exist
follow = models.UserFollows.objects.get(user_object=self.local_user)
@ -149,7 +279,7 @@ class Incoming(TestCase):
self.local_user.save(broadcast=False)
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
incoming.handle_follow(activity)
views.inbox.activity_task(activity)
# notification created
notification = models.Notification.objects.get()
@ -168,48 +298,52 @@ class Incoming(TestCase):
def test_handle_unfollow(self):
''' remove a relationship '''
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
rel = models.UserFollows.objects.create(
user_subject=self.remote_user, user_object=self.local_user)
activity = {
"type": "Undo",
"id": "bleh",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"cc": ["https://example.com/user/mouse/followers"],
'actor': self.remote_user.remote_id,
"@context": "https://www.w3.org/ns/activitystreams",
"object": {
"id": "https://example.com/users/rat/follows/123",
"id": rel.remote_id,
"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)
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 '''
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
rel = models.UserFollowRequest.objects.create(
user_subject=self.local_user,
user_object=self.remote_user
)
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",
"id": rel.remote_id,
"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)
views.inbox.activity_task(activity)
# request should be deleted
self.assertEqual(models.UserFollowRequest.objects.count(), 0)
@ -222,64 +356,31 @@ class Incoming(TestCase):
def test_handle_follow_reject(self):
''' turn down a follow request '''
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
rel = models.UserFollowRequest.objects.create(
user_subject=self.local_user,
user_object=self.remote_user
)
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",
"id": rel.remote_id,
"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)
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_create_list(self):
''' a new list '''
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_create_list(activity)
book_list = models.List.objects.get()
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')
self.assertFalse(models.UserFollowRequest.objects.exists())
self.assertFalse(self.remote_user.followers.exists())
def test_handle_update_list(self):
@ -289,6 +390,9 @@ class Incoming(TestCase):
name='hi', remote_id='https://example.com/list/22',
user=self.local_user)
activity = {
'type': 'Update',
'to': [], 'cc': [], 'actor': 'hi',
'id': 'sdkjf',
'object': {
"id": "https://example.com/list/22",
"type": "BookList",
@ -308,7 +412,7 @@ class Incoming(TestCase):
"@context": "https://www.w3.org/ns/activitystreams"
}
}
incoming.handle_update_list(activity)
views.inbox.activity_task(activity)
book_list.refresh_from_db()
self.assertEqual(book_list.name, 'Test List')
self.assertEqual(book_list.curation, 'curated')
@ -316,80 +420,6 @@ class Incoming(TestCase):
self.assertEqual(book_list.remote_id, 'https://example.com/list/22')
def test_handle_create_status(self):
''' the "it justs works" mode '''
self.assertEqual(models.Status.objects.count(), 1)
datafile = pathlib.Path(__file__).parent.joinpath(
'data/ap_quotation.json')
status_data = json.loads(datafile.read_bytes())
models.Edition.objects.create(
title='Test Book', remote_id='https://example.com/book/1')
activity = {'object': status_data, 'type': 'Create'}
incoming.handle_create_status(activity)
status = models.Quotation.objects.get()
self.assertEqual(
status.remote_id, 'https://example.com/user/mouse/quotation/13')
self.assertEqual(status.quote, 'quote body')
self.assertEqual(status.content, 'commentary')
self.assertEqual(status.user, self.local_user)
self.assertEqual(models.Status.objects.count(), 2)
# while we're here, lets ensure we avoid dupes
incoming.handle_create_status(activity)
self.assertEqual(models.Status.objects.count(), 2)
def test_handle_create_status_unknown_type(self):
''' folks send you all kinds of things '''
activity = {'object': {'id': 'hi'}, 'type': 'Fish'}
result = incoming.handle_create_status(activity)
self.assertIsNone(result)
def test_handle_create_status_remote_note_with_mention(self):
''' should only create it under the right circumstances '''
self.assertEqual(models.Status.objects.count(), 1)
self.assertFalse(
models.Notification.objects.filter(user=self.local_user).exists())
datafile = pathlib.Path(__file__).parent.joinpath(
'data/ap_note.json')
status_data = json.loads(datafile.read_bytes())
activity = {'object': status_data, 'type': 'Create'}
incoming.handle_create_status(activity)
status = models.Status.objects.last()
self.assertEqual(status.content, 'test content in note')
self.assertEqual(status.mention_users.first(), self.local_user)
self.assertTrue(
models.Notification.objects.filter(user=self.local_user).exists())
self.assertEqual(
models.Notification.objects.get().notification_type, 'MENTION')
def test_handle_create_status_remote_note_with_reply(self):
''' should only create it under the right circumstances '''
self.assertEqual(models.Status.objects.count(), 1)
self.assertFalse(
models.Notification.objects.filter(user=self.local_user))
datafile = pathlib.Path(__file__).parent.joinpath(
'data/ap_note.json')
status_data = json.loads(datafile.read_bytes())
del status_data['tag']
status_data['inReplyTo'] = self.status.remote_id
activity = {'object': status_data, 'type': 'Create'}
incoming.handle_create_status(activity)
status = models.Status.objects.last()
self.assertEqual(status.content, 'test content in note')
self.assertEqual(status.reply_parent, self.status)
self.assertTrue(
models.Notification.objects.filter(user=self.local_user))
self.assertEqual(
models.Notification.objects.get().notification_type, 'REPLY')
def test_handle_delete_status(self):
''' remove a status '''
self.status.user = self.remote_user
@ -398,11 +428,13 @@ class Incoming(TestCase):
self.assertFalse(self.status.deleted)
activity = {
'type': 'Delete',
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"cc": ["https://example.com/user/mouse/followers"],
'id': '%s/activity' % self.status.remote_id,
'actor': self.remote_user.remote_id,
'object': {'id': self.status.remote_id},
'object': {'id': self.status.remote_id, 'type': 'Tombstone'},
}
incoming.handle_delete_status(activity)
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)
@ -427,11 +459,13 @@ class Incoming(TestCase):
self.assertEqual(models.Notification.objects.count(), 2)
activity = {
'type': 'Delete',
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"cc": ["https://example.com/user/mouse/followers"],
'id': '%s/activity' % self.status.remote_id,
'actor': self.remote_user.remote_id,
'object': {'id': self.status.remote_id},
'object': {'id': self.status.remote_id, 'type': 'Tombstone'},
}
incoming.handle_delete_status(activity)
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)
@ -448,11 +482,12 @@ class Incoming(TestCase):
'@context': 'https://www.w3.org/ns/activitystreams',
'id': 'https://example.com/fav/1',
'actor': 'https://example.com/users/rat',
'type': 'Like',
'published': 'Mon, 25 May 2020 19:31:20 GMT',
'object': 'https://example.com/status/1',
}
incoming.handle_favorite(activity)
views.inbox.activity_task(activity)
fav = models.Favorite.objects.get(remote_id='https://example.com/fav/1')
self.assertEqual(fav.status, self.status)
@ -464,12 +499,16 @@ class Incoming(TestCase):
activity = {
'id': 'https://example.com/fav/1#undo',
'type': 'Undo',
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"cc": ["https://example.com/user/mouse/followers"],
'actor': self.remote_user.remote_id,
'object': {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': 'https://example.com/fav/1',
'actor': 'https://example.com/users/rat',
'type': 'Like',
'published': 'Mon, 25 May 2020 19:31:20 GMT',
'object': 'https://example.com/fav/1',
'object': self.status.remote_id,
}
}
models.Favorite.objects.create(
@ -478,7 +517,7 @@ class Incoming(TestCase):
remote_id='https://example.com/fav/1')
self.assertEqual(models.Favorite.objects.count(), 1)
incoming.handle_unfavorite(activity)
views.inbox.activity_task(activity)
self.assertEqual(models.Favorite.objects.count(), 0)
@ -489,12 +528,12 @@ class Incoming(TestCase):
'type': 'Announce',
'id': '%s/boost' % self.status.remote_id,
'actor': self.remote_user.remote_id,
'object': self.status.to_activity(),
'object': self.status.remote_id,
}
with patch('bookwyrm.models.status.Status.ignore_activity') \
as discarder:
discarder.return_value = False
incoming.handle_boost(activity)
views.inbox.activity_task(activity)
boost = models.Boost.objects.get()
self.assertEqual(boost.boosted_status, self.status)
notification = models.Notification.objects.get()
@ -505,41 +544,52 @@ class Incoming(TestCase):
@responses.activate
def test_handle_discarded_boost(self):
''' test a boost of a mastodon status that will be discarded '''
status = models.Status(
content='hi',
user=self.remote_user,
)
status.save(broadcast=False)
activity = {
'type': 'Announce',
'id': 'http://www.faraway.com/boost/12',
'actor': self.remote_user.remote_id,
'object': self.status.to_activity(),
'object': status.remote_id,
}
responses.add(
responses.GET,
'http://www.faraway.com/boost/12',
json={'id': 'http://www.faraway.com/boost/12'},
status.remote_id,
json=status.to_activity(),
status=200)
incoming.handle_boost(activity)
views.inbox.activity_task(activity)
self.assertEqual(models.Boost.objects.count(), 0)
def test_handle_unboost(self):
''' undo a boost '''
boost = models.Boost.objects.create(
boosted_status=self.status, user=self.remote_user)
activity = {
'type': 'Undo',
'actor': 'hi',
'id': 'bleh',
"to": ["https://www.w3.org/ns/activitystreams#public"],
"cc": ["https://example.com/user/mouse/followers"],
'object': {
'type': 'Announce',
'id': '%s/boost' % self.status.remote_id,
'actor': self.local_user.remote_id,
'object': self.status.to_activity(),
'id': boost.remote_id,
'actor': self.remote_user.remote_id,
'object': self.status.remote_id,
}
}
models.Boost.objects.create(
boosted_status=self.status, user=self.remote_user)
incoming.handle_unboost(activity)
views.inbox.activity_task(activity)
def test_handle_add_book(self):
def test_handle_add_book_to_shelf(self):
''' shelving a book '''
work = models.Work.objects.create(title='work title')
book = models.Edition.objects.create(
title='Test', remote_id='https://bookwyrm.social/book/37292')
title='Test', remote_id='https://bookwyrm.social/book/37292',
parent_work=work)
shelf = models.Shelf.objects.create(
user=self.remote_user, name='Test Shelf')
shelf.remote_id = 'https://bookwyrm.social/user/mouse/shelf/to-read'
@ -549,14 +599,113 @@ class Incoming(TestCase):
"id": "https://bookwyrm.social/shelfbook/6189#add",
"type": "Add",
"actor": "https://example.com/users/rat",
"object": "https://bookwyrm.social/book/37292",
"object": {
"type": "Edition",
"title": "Test Title",
"work": work.remote_id,
"id": "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)
views.inbox.activity_task(activity)
self.assertEqual(shelf.books.first(), book)
@responses.activate
def test_handle_add_book_to_list(self):
''' listing a book '''
work = models.Work.objects.create(title='work title')
book = models.Edition.objects.create(
title='Test', remote_id='https://bookwyrm.social/book/37292',
parent_work=work)
responses.add(
responses.GET,
'https://bookwyrm.social/user/mouse/list/to-read',
json={
"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"
}
)
activity = {
"id": "https://bookwyrm.social/listbook/6189#add",
"type": "Add",
"actor": "https://example.com/users/rat",
"object": {
"type": "Edition",
"title": "Test Title",
"work": work.remote_id,
"id": "https://bookwyrm.social/book/37292",
},
"target": "https://bookwyrm.social/user/mouse/list/to-read",
"@context": "https://www.w3.org/ns/activitystreams"
}
views.inbox.activity_task(activity)
booklist = models.List.objects.get()
self.assertEqual(booklist.name, 'Test List')
self.assertEqual(booklist.books.first(), book)
@responses.activate
def test_handle_tag_book(self):
''' listing a book '''
work = models.Work.objects.create(title='work title')
book = models.Edition.objects.create(
title='Test', remote_id='https://bookwyrm.social/book/37292',
parent_work=work)
responses.add(
responses.GET,
'https://www.example.com/tag/cool-tag',
json={
"id": "https://1b1a78582461.ngrok.io/tag/tag",
"type": "OrderedCollection",
"totalItems": 0,
"first": "https://1b1a78582461.ngrok.io/tag/tag?page=1",
"last": "https://1b1a78582461.ngrok.io/tag/tag?page=1",
"name": "cool tag",
"@context": "https://www.w3.org/ns/activitystreams"
}
)
activity = {
"id": "https://bookwyrm.social/listbook/6189#add",
"type": "Add",
"actor": "https://example.com/users/rat",
"object": {
"type": "Edition",
"title": "Test Title",
"work": work.remote_id,
"id": "https://bookwyrm.social/book/37292",
},
"target": "https://www.example.com/tag/cool-tag",
"@context": "https://www.w3.org/ns/activitystreams"
}
views.inbox.activity_task(activity)
tag = models.Tag.objects.get()
self.assertFalse(models.List.objects.exists())
self.assertEqual(tag.name, 'cool tag')
self.assertEqual(tag.books.first(), book)
def test_handle_update_user(self):
''' update an existing user '''
# we only do this with remote users
@ -564,11 +713,16 @@ class Incoming(TestCase):
self.local_user.save()
datafile = pathlib.Path(__file__).parent.joinpath(
'data/ap_user.json')
'../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})
views.inbox.activity_task({
'type': 'Update',
'to': [], 'cc': [], 'actor': 'hi',
'id': 'sdkjf',
'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')
@ -578,7 +732,7 @@ class Incoming(TestCase):
def test_handle_update_edition(self):
''' update an existing edition '''
datafile = pathlib.Path(__file__).parent.joinpath(
'data/bw_edition.json')
'../data/bw_edition.json')
bookdata = json.loads(datafile.read_bytes())
models.Work.objects.create(
@ -591,7 +745,12 @@ class Incoming(TestCase):
with patch(
'bookwyrm.activitypub.base_activity.set_related_field.delay'):
incoming.handle_update_edition({'object': bookdata})
views.inbox.activity_task({
'type': 'Update',
'to': [], 'cc': [], 'actor': 'hi',
'id': 'sdkjf',
'object': bookdata
})
book = models.Edition.objects.get(id=book.id)
self.assertEqual(book.title, 'Piranesi')
@ -599,7 +758,7 @@ class Incoming(TestCase):
def test_handle_update_work(self):
''' update an existing edition '''
datafile = pathlib.Path(__file__).parent.joinpath(
'data/bw_work.json')
'../data/bw_work.json')
bookdata = json.loads(datafile.read_bytes())
book = models.Work.objects.create(
@ -609,7 +768,12 @@ class Incoming(TestCase):
self.assertEqual(book.title, 'Test Book')
with patch(
'bookwyrm.activitypub.base_activity.set_related_field.delay'):
incoming.handle_update_work({'object': bookdata})
views.inbox.activity_task({
'type': 'Update',
'to': [], 'cc': [], 'actor': 'hi',
'id': 'sdkjf',
'object': bookdata
})
book = models.Work.objects.get(id=book.id)
self.assertEqual(book.title, 'Piranesi')
@ -632,7 +796,7 @@ class Incoming(TestCase):
"object": "https://example.com/user/mouse"
}
incoming.handle_block(activity)
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)
@ -653,12 +817,18 @@ class Incoming(TestCase):
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)
activity = {
'type': 'Undo',
'actor': 'hi',
'id': 'bleh',
"to": ["https://www.w3.org/ns/activitystreams#public"],
"cc": ["https://example.com/user/mouse/followers"],
'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

@ -138,7 +138,7 @@ class InteractionViews(TestCase):
''' undo a boost '''
view = views.Unboost.as_view()
request = self.factory.post('')
request.user = self.remote_user
request.user = self.local_user
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
status = models.Status.objects.create(
user=self.local_user, content='hi')
@ -146,7 +146,9 @@ class InteractionViews(TestCase):
self.assertEqual(models.Boost.objects.count(), 1)
self.assertEqual(models.Notification.objects.count(), 1)
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay') \
as mock:
view(request, status.id)
self.assertEqual(mock.call_count, 1)
self.assertEqual(models.Boost.objects.count(), 0)
self.assertEqual(models.Notification.objects.count(), 0)

View File

@ -9,7 +9,7 @@ from django.test.client import RequestFactory
from bookwyrm import models, views
from bookwyrm.activitypub import ActivitypubResponse
#pylint: disable=unused-argument
class ListViews(TestCase):
''' tag views'''
def setUp(self):
@ -45,7 +45,7 @@ class ListViews(TestCase):
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
models.List.objects.create(name='Public list', user=self.local_user)
models.List.objects.create(
name='Private list', privacy='private', user=self.local_user)
name='Private list', privacy='direct', user=self.local_user)
request = self.factory.get('')
request.user = self.local_user
@ -164,7 +164,7 @@ class ListViews(TestCase):
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
models.List.objects.create(name='Public list', user=self.local_user)
models.List.objects.create(
name='Private list', privacy='private', user=self.local_user)
name='Private list', privacy='direct', user=self.local_user)
request = self.factory.get('')
request.user = self.local_user

View File

@ -1,4 +1,5 @@
''' test for app action functionality '''
import json
from unittest.mock import patch
from django.test import TestCase
from django.test.client import RequestFactory
@ -236,7 +237,11 @@ class StatusViews(TestCase):
self.assertFalse(status.deleted)
request = self.factory.post('')
request.user = self.local_user
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay') \
as mock:
view(request, status.id)
activity = json.loads(mock.call_args_list[0][0][1])
self.assertEqual(activity['type'], 'Delete')
self.assertEqual(activity['object']['type'], 'Tombstone')
status.refresh_from_db()
self.assertTrue(status.deleted)

View File

@ -59,6 +59,21 @@ class TagViews(TestCase):
self.assertEqual(result.status_code, 200)
def test_tag_page_activitypub_page(self):
''' there are so many views, this just makes sure it LOADS '''
view = views.Tag.as_view()
with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'):
tag = models.Tag.objects.create(name='hi there')
models.UserTag.objects.create(
tag=tag, user=self.local_user, book=self.book)
request = self.factory.get('', {'page': 1})
with patch('bookwyrm.views.tag.is_api_request') as is_api:
is_api.return_value = True
result = view(request, tag.identifier)
self.assertIsInstance(result, ActivitypubResponse)
self.assertEqual(result.status_code, 200)
def test_tag(self):
''' add a tag to a book '''
view = views.AddTag.as_view()