2021-03-08 16:49:10 +00:00
|
|
|
""" tests the base functionality for activitypub dataclasses """
|
2020-12-07 20:19:15 +00:00
|
|
|
from io import BytesIO
|
2020-12-07 18:46:41 +00:00
|
|
|
import json
|
|
|
|
import pathlib
|
|
|
|
from unittest.mock import patch
|
|
|
|
|
2020-12-07 18:32:31 +00:00
|
|
|
from dataclasses import dataclass
|
|
|
|
from django.test import TestCase
|
2020-12-07 20:19:15 +00:00
|
|
|
from PIL import Image
|
2020-12-07 18:46:41 +00:00
|
|
|
import responses
|
2020-12-07 18:32:31 +00:00
|
|
|
|
2020-12-07 20:19:15 +00:00
|
|
|
from bookwyrm import activitypub
|
2021-03-08 16:49:10 +00:00
|
|
|
from bookwyrm.activitypub.base_activity import (
|
|
|
|
ActivityObject,
|
|
|
|
resolve_remote_id,
|
|
|
|
set_related_field,
|
|
|
|
)
|
2020-12-07 18:32:31 +00:00
|
|
|
from bookwyrm.activitypub import ActivitySerializerError
|
|
|
|
from bookwyrm import models
|
|
|
|
|
2021-03-08 16:49:10 +00:00
|
|
|
|
2021-09-06 20:53:49 +00:00
|
|
|
@patch("bookwyrm.activitystreams.add_status_task.delay")
|
2021-08-03 15:48:15 +00:00
|
|
|
@patch("bookwyrm.suggested_users.rerank_user_task.delay")
|
2021-08-03 19:21:11 +00:00
|
|
|
@patch("bookwyrm.suggested_users.remove_user_task.delay")
|
2021-08-03 19:02:47 +00:00
|
|
|
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
|
2021-09-06 21:50:33 +00:00
|
|
|
@patch("bookwyrm.activitystreams.populate_stream_task.delay")
|
2020-12-07 18:32:31 +00:00
|
|
|
class BaseActivity(TestCase):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""the super class for model-linked activitypub dataclasses"""
|
2021-03-08 16:49:10 +00:00
|
|
|
|
2020-12-07 20:19:15 +00:00
|
|
|
def setUp(self):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""we're probably going to re-use this so why copy/paste"""
|
2021-09-06 21:48:45 +00:00
|
|
|
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
|
|
|
|
"bookwyrm.activitystreams.populate_stream_task.delay"
|
|
|
|
):
|
2021-08-03 15:48:15 +00:00
|
|
|
self.user = models.User.objects.create_user(
|
|
|
|
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
|
|
|
|
)
|
2021-08-02 23:05:40 +00:00
|
|
|
self.user.remote_id = "http://example.com/a/b"
|
2021-08-03 15:48:15 +00:00
|
|
|
self.user.save(broadcast=False, update_fields=["remote_id"])
|
2020-12-07 20:19:15 +00:00
|
|
|
|
2021-08-02 23:05:40 +00:00
|
|
|
datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json")
|
|
|
|
self.userdata = json.loads(datafile.read_bytes())
|
|
|
|
# don't try to load the user icon
|
|
|
|
del self.userdata["icon"]
|
2020-12-07 20:19:15 +00:00
|
|
|
|
2021-08-02 23:05:40 +00:00
|
|
|
image_file = pathlib.Path(__file__).parent.joinpath(
|
|
|
|
"../../static/images/default_avi.jpg"
|
|
|
|
)
|
|
|
|
image = Image.open(image_file)
|
|
|
|
output = BytesIO()
|
|
|
|
image.save(output, format=image.format)
|
|
|
|
self.image_data = output.getvalue()
|
2020-12-08 02:28:42 +00:00
|
|
|
|
2021-08-03 15:48:15 +00:00
|
|
|
def test_init(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""simple successfuly init"""
|
2021-03-08 16:49:10 +00:00
|
|
|
instance = ActivityObject(id="a", type="b")
|
|
|
|
self.assertTrue(hasattr(instance, "id"))
|
|
|
|
self.assertTrue(hasattr(instance, "type"))
|
2020-12-07 18:32:31 +00:00
|
|
|
|
2021-08-03 15:48:15 +00:00
|
|
|
def test_init_missing(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""init with missing required params"""
|
2020-12-07 18:32:31 +00:00
|
|
|
with self.assertRaises(ActivitySerializerError):
|
|
|
|
ActivityObject()
|
|
|
|
|
2021-08-03 15:48:15 +00:00
|
|
|
def test_init_extra_fields(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""init ignoring additional fields"""
|
2021-03-08 16:49:10 +00:00
|
|
|
instance = ActivityObject(id="a", type="b", fish="c")
|
|
|
|
self.assertTrue(hasattr(instance, "id"))
|
|
|
|
self.assertTrue(hasattr(instance, "type"))
|
2020-12-07 18:32:31 +00:00
|
|
|
|
2021-08-03 15:48:15 +00:00
|
|
|
def test_init_default_field(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""replace an existing required field with a default field"""
|
2021-03-08 16:49:10 +00:00
|
|
|
|
2020-12-07 18:32:31 +00:00
|
|
|
@dataclass(init=False)
|
|
|
|
class TestClass(ActivityObject):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""test class with default field"""
|
2021-03-08 16:49:10 +00:00
|
|
|
|
|
|
|
type: str = "TestObject"
|
2020-12-07 18:32:31 +00:00
|
|
|
|
2021-03-08 16:49:10 +00:00
|
|
|
instance = TestClass(id="a")
|
|
|
|
self.assertEqual(instance.id, "a")
|
|
|
|
self.assertEqual(instance.type, "TestObject")
|
2020-12-07 18:32:31 +00:00
|
|
|
|
2021-08-03 15:48:15 +00:00
|
|
|
def test_serialize(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""simple function for converting dataclass to dict"""
|
2021-03-08 16:49:10 +00:00
|
|
|
instance = ActivityObject(id="a", type="b")
|
2020-12-07 18:32:31 +00:00
|
|
|
serialized = instance.serialize()
|
|
|
|
self.assertIsInstance(serialized, dict)
|
2021-03-08 16:49:10 +00:00
|
|
|
self.assertEqual(serialized["id"], "a")
|
|
|
|
self.assertEqual(serialized["type"], "b")
|
2020-12-07 18:32:31 +00:00
|
|
|
|
2020-12-07 18:46:41 +00:00
|
|
|
@responses.activate
|
2021-08-03 15:48:15 +00:00
|
|
|
def test_resolve_remote_id(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""look up or load remote data"""
|
2020-12-07 18:46:41 +00:00
|
|
|
# existing item
|
2021-03-08 16:49:10 +00:00
|
|
|
result = resolve_remote_id("http://example.com/a/b", model=models.User)
|
2020-12-07 20:19:15 +00:00
|
|
|
self.assertEqual(result, self.user)
|
2020-12-07 18:46:41 +00:00
|
|
|
|
|
|
|
# remote item
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
2021-03-08 16:49:10 +00:00
|
|
|
"https://example.com/user/mouse",
|
2020-12-07 20:19:15 +00:00
|
|
|
json=self.userdata,
|
2021-03-08 16:49:10 +00:00
|
|
|
status=200,
|
|
|
|
)
|
2020-12-07 18:46:41 +00:00
|
|
|
|
2021-08-02 23:05:40 +00:00
|
|
|
with patch("bookwyrm.models.user.set_remote_server.delay"):
|
|
|
|
result = resolve_remote_id(
|
|
|
|
"https://example.com/user/mouse", model=models.User
|
|
|
|
)
|
2020-12-07 18:46:41 +00:00
|
|
|
self.assertIsInstance(result, models.User)
|
2021-03-08 16:49:10 +00:00
|
|
|
self.assertEqual(result.remote_id, "https://example.com/user/mouse")
|
|
|
|
self.assertEqual(result.name, "MOUSE?? MOUSE!!")
|
2020-12-07 20:19:15 +00:00
|
|
|
|
2021-08-03 15:48:15 +00:00
|
|
|
def test_to_model_invalid_model(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""catch mismatch between activity type and model type"""
|
2021-03-08 16:49:10 +00:00
|
|
|
instance = ActivityObject(id="a", type="b")
|
2020-12-07 20:19:15 +00:00
|
|
|
with self.assertRaises(ActivitySerializerError):
|
2021-02-17 04:17:38 +00:00
|
|
|
instance.to_model(model=models.User)
|
2020-12-07 20:19:15 +00:00
|
|
|
|
|
|
|
@responses.activate
|
2021-08-03 15:48:15 +00:00
|
|
|
def test_to_model_image(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""update an image field"""
|
2020-12-13 22:35:56 +00:00
|
|
|
activity = activitypub.Person(
|
|
|
|
id=self.user.remote_id,
|
2021-03-08 16:49:10 +00:00
|
|
|
name="New Name",
|
|
|
|
preferredUsername="mouse",
|
|
|
|
inbox="http://www.com/",
|
|
|
|
outbox="http://www.com/",
|
|
|
|
followers="",
|
|
|
|
summary="",
|
|
|
|
publicKey={"id": "hi", "owner": self.user.remote_id, "publicKeyPem": "hi"},
|
2020-12-13 22:35:56 +00:00
|
|
|
endpoints={},
|
2021-03-15 22:57:00 +00:00
|
|
|
icon={"type": "Document", "url": "http://www.example.com/image.jpg"},
|
2020-12-13 22:35:56 +00:00
|
|
|
)
|
|
|
|
|
2020-12-07 20:19:15 +00:00
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
2021-03-08 16:49:10 +00:00
|
|
|
"http://www.example.com/image.jpg",
|
2020-12-08 02:28:42 +00:00
|
|
|
body=self.image_data,
|
2021-03-08 16:49:10 +00:00
|
|
|
status=200,
|
|
|
|
)
|
2020-12-07 20:19:15 +00:00
|
|
|
|
|
|
|
self.assertIsNone(self.user.avatar.name)
|
|
|
|
with self.assertRaises(ValueError):
|
2021-03-08 16:49:10 +00:00
|
|
|
self.user.avatar.file # pylint: disable=pointless-statement
|
2020-12-07 20:19:15 +00:00
|
|
|
|
2021-02-08 17:38:28 +00:00
|
|
|
# this would trigger a broadcast because it's a local user
|
2021-08-02 23:05:40 +00:00
|
|
|
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
|
|
|
activity.to_model(model=models.User, instance=self.user)
|
2020-12-07 20:19:15 +00:00
|
|
|
self.assertIsNotNone(self.user.avatar.file)
|
2021-03-08 16:49:10 +00:00
|
|
|
self.assertEqual(self.user.name, "New Name")
|
|
|
|
self.assertEqual(self.user.key_pair.public_key, "hi")
|
2020-12-07 21:16:42 +00:00
|
|
|
|
2021-08-03 15:48:15 +00:00
|
|
|
def test_to_model_many_to_many(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""annoying that these all need special handling"""
|
2021-03-08 16:49:10 +00:00
|
|
|
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
2021-02-08 17:38:28 +00:00
|
|
|
status = models.Status.objects.create(
|
2021-03-08 16:49:10 +00:00
|
|
|
content="test status",
|
2021-02-08 17:38:28 +00:00
|
|
|
user=self.user,
|
|
|
|
)
|
2021-08-02 23:05:40 +00:00
|
|
|
book = models.Edition.objects.create(
|
|
|
|
title="Test Edition", remote_id="http://book.com/book"
|
|
|
|
)
|
2020-12-13 22:35:56 +00:00
|
|
|
update_data = activitypub.Note(
|
|
|
|
id=status.remote_id,
|
|
|
|
content=status.content,
|
|
|
|
attributedTo=self.user.remote_id,
|
2021-03-08 16:49:10 +00:00
|
|
|
published="hi",
|
2020-12-13 22:35:56 +00:00
|
|
|
to=[],
|
|
|
|
cc=[],
|
|
|
|
tag=[
|
2021-03-08 16:49:10 +00:00
|
|
|
{"type": "Mention", "name": "gerald", "href": "http://example.com/a/b"},
|
2020-12-13 22:35:56 +00:00
|
|
|
{
|
2021-03-08 16:49:10 +00:00
|
|
|
"type": "Edition",
|
|
|
|
"name": "gerald j. books",
|
|
|
|
"href": "http://book.com/book",
|
2020-12-13 22:35:56 +00:00
|
|
|
},
|
2021-03-08 16:49:10 +00:00
|
|
|
],
|
2020-12-13 22:35:56 +00:00
|
|
|
)
|
2021-02-17 04:17:38 +00:00
|
|
|
update_data.to_model(model=models.Status, instance=status)
|
2020-12-07 21:16:42 +00:00
|
|
|
self.assertEqual(status.mention_users.first(), self.user)
|
2020-12-12 22:15:10 +00:00
|
|
|
self.assertEqual(status.mention_books.first(), book)
|
2020-12-08 02:28:42 +00:00
|
|
|
|
|
|
|
@responses.activate
|
2021-08-03 15:48:15 +00:00
|
|
|
def test_to_model_one_to_many(self, *_):
|
2021-03-08 16:49:10 +00:00
|
|
|
"""these are reversed relationships, where the secondary object
|
|
|
|
keys the primary object but not vice versa"""
|
|
|
|
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
2021-02-08 17:38:28 +00:00
|
|
|
status = models.Status.objects.create(
|
2021-03-08 16:49:10 +00:00
|
|
|
content="test status",
|
2021-02-08 17:38:28 +00:00
|
|
|
user=self.user,
|
|
|
|
)
|
2020-12-13 22:35:56 +00:00
|
|
|
update_data = activitypub.Note(
|
|
|
|
id=status.remote_id,
|
|
|
|
content=status.content,
|
|
|
|
attributedTo=self.user.remote_id,
|
2021-03-08 16:49:10 +00:00
|
|
|
published="hi",
|
2020-12-13 22:35:56 +00:00
|
|
|
to=[],
|
|
|
|
cc=[],
|
2021-03-08 16:49:10 +00:00
|
|
|
attachment=[
|
|
|
|
{
|
|
|
|
"url": "http://www.example.com/image.jpg",
|
|
|
|
"name": "alt text",
|
2021-03-15 22:57:00 +00:00
|
|
|
"type": "Document",
|
2021-03-08 16:49:10 +00:00
|
|
|
}
|
|
|
|
],
|
2020-12-13 22:35:56 +00:00
|
|
|
)
|
2020-12-08 02:28:42 +00:00
|
|
|
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
2021-03-08 16:49:10 +00:00
|
|
|
"http://www.example.com/image.jpg",
|
2020-12-08 02:28:42 +00:00
|
|
|
body=self.image_data,
|
2021-03-08 16:49:10 +00:00
|
|
|
status=200,
|
|
|
|
)
|
2020-12-08 02:28:42 +00:00
|
|
|
|
2020-12-09 21:11:42 +00:00
|
|
|
# sets the celery task call to the function call
|
2021-03-08 16:49:10 +00:00
|
|
|
with patch("bookwyrm.activitypub.base_activity.set_related_field.delay"):
|
|
|
|
with patch("bookwyrm.models.status.Status.ignore_activity") as discarder:
|
2021-03-07 21:13:16 +00:00
|
|
|
discarder.return_value = False
|
|
|
|
update_data.to_model(model=models.Status, instance=status)
|
2020-12-09 21:11:42 +00:00
|
|
|
self.assertIsNone(status.attachments.first())
|
|
|
|
|
|
|
|
@responses.activate
|
2021-08-03 15:48:15 +00:00
|
|
|
def test_set_related_field(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""celery task to add back-references to created objects"""
|
2021-03-08 16:49:10 +00:00
|
|
|
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
2021-02-08 17:38:28 +00:00
|
|
|
status = models.Status.objects.create(
|
2021-03-08 16:49:10 +00:00
|
|
|
content="test status",
|
2021-02-08 17:38:28 +00:00
|
|
|
user=self.user,
|
|
|
|
)
|
2020-12-09 21:11:42 +00:00
|
|
|
data = {
|
2021-03-08 16:49:10 +00:00
|
|
|
"url": "http://www.example.com/image.jpg",
|
|
|
|
"name": "alt text",
|
2021-03-15 22:57:00 +00:00
|
|
|
"type": "Document",
|
2020-12-09 21:11:42 +00:00
|
|
|
}
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
2021-03-08 16:49:10 +00:00
|
|
|
"http://www.example.com/image.jpg",
|
2020-12-09 21:11:42 +00:00
|
|
|
body=self.image_data,
|
2021-03-08 16:49:10 +00:00
|
|
|
status=200,
|
|
|
|
)
|
|
|
|
set_related_field("Image", "Status", "status", status.remote_id, data)
|
2020-12-09 21:11:42 +00:00
|
|
|
|
2020-12-08 02:28:42 +00:00
|
|
|
self.assertIsInstance(status.attachments.first(), models.Image)
|
2020-12-09 21:11:42 +00:00
|
|
|
self.assertIsNotNone(status.attachments.first().image)
|