2021-03-08 16:49:10 +00:00
|
|
|
""" testing models """
|
2020-12-05 22:40:23 +00:00
|
|
|
from io import BytesIO
|
2020-12-05 01:42:01 +00:00
|
|
|
from collections import namedtuple
|
2020-12-13 23:43:39 +00:00
|
|
|
from dataclasses import dataclass
|
2020-12-07 17:14:30 +00:00
|
|
|
import json
|
2020-12-05 22:40:23 +00:00
|
|
|
import pathlib
|
|
|
|
import re
|
2020-12-13 23:43:39 +00:00
|
|
|
from typing import List
|
2020-12-07 21:39:05 +00:00
|
|
|
from unittest.mock import patch
|
2020-12-05 22:40:23 +00:00
|
|
|
|
|
|
|
from PIL import Image
|
|
|
|
import responses
|
2020-12-05 01:42:01 +00:00
|
|
|
|
2020-12-05 01:38:39 +00:00
|
|
|
from django.core.exceptions import ValidationError
|
2020-12-05 22:40:23 +00:00
|
|
|
from django.core.files.base import ContentFile
|
2020-12-05 01:38:39 +00:00
|
|
|
from django.db import models
|
|
|
|
from django.test import TestCase
|
2020-12-05 22:40:23 +00:00
|
|
|
from django.utils import timezone
|
2020-12-05 01:38:39 +00:00
|
|
|
|
2021-08-03 03:07:46 +00:00
|
|
|
from bookwyrm import activitypub
|
2020-12-13 23:43:39 +00:00
|
|
|
from bookwyrm.activitypub.base_activity import ActivityObject
|
2021-11-28 16:49:54 +00:00
|
|
|
from bookwyrm.models import fields, User, Status, Edition
|
2021-02-06 20:00:47 +00:00
|
|
|
from bookwyrm.models.base_model import BookWyrmModel
|
|
|
|
from bookwyrm.models.activitypub_mixin import ActivitypubMixin
|
2021-08-04 17:11:57 +00:00
|
|
|
from bookwyrm.settings import DOMAIN
|
2020-12-05 01:38:39 +00:00
|
|
|
|
2021-03-08 16:49:10 +00:00
|
|
|
# pylint: disable=too-many-public-methods
|
2021-08-03 23:21:29 +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")
|
2021-08-03 23:21:29 +00:00
|
|
|
class ModelFields(TestCase):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""overwrites standard model feilds to work with activitypub"""
|
2021-03-08 16:49:10 +00:00
|
|
|
|
2021-09-06 22:09:04 +00:00
|
|
|
def test_validate_remote_id(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""should look like a url"""
|
2021-03-08 16:49:10 +00:00
|
|
|
self.assertIsNone(fields.validate_remote_id("http://www.example.com"))
|
|
|
|
self.assertIsNone(fields.validate_remote_id("https://www.example.com"))
|
|
|
|
self.assertIsNone(fields.validate_remote_id("http://exle.com/dlg-23/x"))
|
2020-12-05 01:38:39 +00:00
|
|
|
self.assertRaises(
|
2021-03-08 16:49:10 +00:00
|
|
|
ValidationError, fields.validate_remote_id, "http:/example.com/dlfjg-23/x"
|
|
|
|
)
|
2020-12-05 01:38:39 +00:00
|
|
|
self.assertRaises(
|
2021-03-08 16:49:10 +00:00
|
|
|
ValidationError, fields.validate_remote_id, "www.example.com/dlfjg-23/x"
|
|
|
|
)
|
2020-12-05 01:38:39 +00:00
|
|
|
self.assertRaises(
|
2021-03-08 16:49:10 +00:00
|
|
|
ValidationError,
|
|
|
|
fields.validate_remote_id,
|
|
|
|
"http://www.example.com/dlfjg 23/x",
|
|
|
|
)
|
2020-12-05 01:38:39 +00:00
|
|
|
|
2021-09-06 22:09:04 +00:00
|
|
|
def test_activitypub_field_mixin(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""generic mixin with super basic to and from functionality"""
|
2020-12-05 01:38:39 +00:00
|
|
|
instance = fields.ActivitypubFieldMixin()
|
2021-03-08 16:49:10 +00:00
|
|
|
self.assertEqual(instance.field_to_activity("fish"), "fish")
|
|
|
|
self.assertEqual(instance.field_from_activity("fish"), "fish")
|
2020-12-12 21:39:55 +00:00
|
|
|
self.assertFalse(instance.deduplication_field)
|
2020-12-05 01:38:39 +00:00
|
|
|
|
|
|
|
instance = fields.ActivitypubFieldMixin(
|
2021-03-08 16:49:10 +00:00
|
|
|
activitypub_wrapper="endpoints", activitypub_field="outbox"
|
2020-12-05 01:38:39 +00:00
|
|
|
)
|
2021-03-08 16:49:10 +00:00
|
|
|
self.assertEqual(instance.field_to_activity("fish"), {"outbox": "fish"})
|
|
|
|
self.assertEqual(instance.field_from_activity({"outbox": "fish"}), "fish")
|
|
|
|
self.assertEqual(instance.get_activitypub_field(), "endpoints")
|
2020-12-05 01:38:39 +00:00
|
|
|
|
|
|
|
instance = fields.ActivitypubFieldMixin()
|
2021-03-08 16:49:10 +00:00
|
|
|
instance.name = "snake_case_name"
|
|
|
|
self.assertEqual(instance.get_activitypub_field(), "snakeCaseName")
|
2020-12-05 01:38:39 +00:00
|
|
|
|
2021-09-06 22:09:04 +00:00
|
|
|
def test_set_field_from_activity(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""setter from entire json blob"""
|
2021-03-08 16:49:10 +00:00
|
|
|
|
2020-12-17 02:39:18 +00:00
|
|
|
@dataclass
|
|
|
|
class TestModel:
|
2021-04-26 16:15:42 +00:00
|
|
|
"""real simple mock"""
|
2021-03-08 16:49:10 +00:00
|
|
|
|
2020-12-17 02:39:18 +00:00
|
|
|
field_name: str
|
|
|
|
|
2021-03-08 16:49:10 +00:00
|
|
|
mock_model = TestModel(field_name="bip")
|
|
|
|
TestActivity = namedtuple("test", ("fieldName", "unrelated"))
|
|
|
|
data = TestActivity(fieldName="hi", unrelated="bfkjh")
|
2020-12-17 02:39:18 +00:00
|
|
|
|
|
|
|
instance = fields.ActivitypubFieldMixin()
|
2021-03-08 16:49:10 +00:00
|
|
|
instance.name = "field_name"
|
2020-12-17 02:39:18 +00:00
|
|
|
|
|
|
|
instance.set_field_from_activity(mock_model, data)
|
2021-03-08 16:49:10 +00:00
|
|
|
self.assertEqual(mock_model.field_name, "hi")
|
2020-12-17 02:39:18 +00:00
|
|
|
|
2021-09-06 22:09:04 +00:00
|
|
|
def test_set_activity_from_field(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""set json field given entire model"""
|
2021-03-08 16:49:10 +00:00
|
|
|
|
2020-12-17 02:39:18 +00:00
|
|
|
@dataclass
|
|
|
|
class TestModel:
|
2021-04-26 16:15:42 +00:00
|
|
|
"""real simple mock"""
|
2021-03-08 16:49:10 +00:00
|
|
|
|
2020-12-17 02:39:18 +00:00
|
|
|
field_name: str
|
|
|
|
unrelated: str
|
2021-03-08 16:49:10 +00:00
|
|
|
|
|
|
|
mock_model = TestModel(field_name="bip", unrelated="field")
|
2020-12-17 02:39:18 +00:00
|
|
|
instance = fields.ActivitypubFieldMixin()
|
2021-03-08 16:49:10 +00:00
|
|
|
instance.name = "field_name"
|
2020-12-17 02:39:18 +00:00
|
|
|
|
|
|
|
data = {}
|
|
|
|
instance.set_activity_from_field(data, mock_model)
|
2021-03-08 16:49:10 +00:00
|
|
|
self.assertEqual(data["fieldName"], "bip")
|
2020-12-17 02:39:18 +00:00
|
|
|
|
2021-09-06 22:09:04 +00:00
|
|
|
def test_remote_id_field(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""just sets some defaults on charfield"""
|
2020-12-05 01:38:39 +00:00
|
|
|
instance = fields.RemoteIdField()
|
|
|
|
self.assertEqual(instance.max_length, 255)
|
2020-12-12 21:39:55 +00:00
|
|
|
self.assertTrue(instance.deduplication_field)
|
2020-12-05 01:38:39 +00:00
|
|
|
|
|
|
|
with self.assertRaises(ValidationError):
|
2021-03-08 16:49:10 +00:00
|
|
|
instance.run_validators("http://www.example.com/dlfjg 23/x")
|
2020-12-05 01:38:39 +00:00
|
|
|
|
2021-09-06 22:09:04 +00:00
|
|
|
def test_username_field(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""again, just setting defaults on username field"""
|
2020-12-05 01:38:39 +00:00
|
|
|
instance = fields.UsernameField()
|
2021-03-08 16:49:10 +00:00
|
|
|
self.assertEqual(instance.activitypub_field, "preferredUsername")
|
2020-12-05 01:38:39 +00:00
|
|
|
self.assertEqual(instance.max_length, 150)
|
|
|
|
self.assertEqual(instance.unique, True)
|
|
|
|
with self.assertRaises(ValidationError):
|
2021-03-08 16:49:10 +00:00
|
|
|
instance.run_validators("mouse")
|
|
|
|
instance.run_validators("mouseexample.com")
|
|
|
|
instance.run_validators("mouse@example.c")
|
|
|
|
instance.run_validators("@example.com")
|
|
|
|
instance.run_validators("mouse@examplecom")
|
|
|
|
instance.run_validators("one two@fish.aaaa")
|
|
|
|
instance.run_validators("a*&@exampke.com")
|
|
|
|
instance.run_validators("trailingwhite@example.com ")
|
|
|
|
self.assertIsNone(instance.run_validators("mouse@example.com"))
|
|
|
|
self.assertIsNone(instance.run_validators("mo-2use@ex3ample.com"))
|
|
|
|
self.assertIsNone(instance.run_validators("aksdhf@sdkjf-df.cm"))
|
|
|
|
|
|
|
|
self.assertEqual(instance.field_to_activity("test@example.com"), "test")
|
2020-12-13 23:43:39 +00:00
|
|
|
|
2021-09-06 22:09:04 +00:00
|
|
|
def test_privacy_field_defaults(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""post privacy field's many default values"""
|
2020-12-13 23:43:39 +00:00
|
|
|
instance = fields.PrivacyField()
|
|
|
|
self.assertEqual(instance.max_length, 255)
|
|
|
|
self.assertEqual(
|
|
|
|
[c[0] for c in instance.choices],
|
2021-03-08 16:49:10 +00:00
|
|
|
["public", "unlisted", "followers", "direct"],
|
|
|
|
)
|
|
|
|
self.assertEqual(instance.default, "public")
|
2020-12-13 23:43:39 +00:00
|
|
|
self.assertEqual(
|
2021-03-08 16:49:10 +00:00
|
|
|
instance.public, "https://www.w3.org/ns/activitystreams#Public"
|
|
|
|
)
|
2020-12-13 23:43:39 +00:00
|
|
|
|
2021-09-06 22:09:04 +00:00
|
|
|
def test_privacy_field_set_field_from_activity(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""translate between to/cc fields and privacy"""
|
2021-03-08 16:49:10 +00:00
|
|
|
|
2021-08-28 18:41:33 +00:00
|
|
|
with patch("bookwyrm.models.user.set_remote_server.delay"):
|
|
|
|
test_user = User.objects.create_user(
|
|
|
|
username="test_user@example.com",
|
|
|
|
local=False,
|
|
|
|
remote_id="https://example.com/test_user",
|
|
|
|
inbox="https://example.com/users/test_user/inbox",
|
|
|
|
followers_url="https://example.com/users/test_user/followers",
|
|
|
|
)
|
|
|
|
|
2020-12-13 23:43:39 +00:00
|
|
|
@dataclass(init=False)
|
|
|
|
class TestActivity(ActivityObject):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""real simple mock"""
|
2021-03-08 16:49:10 +00:00
|
|
|
|
2020-12-13 23:43:39 +00:00
|
|
|
to: List[str]
|
|
|
|
cc: List[str]
|
2021-03-08 16:49:10 +00:00
|
|
|
id: str = "http://hi.com"
|
|
|
|
type: str = "Test"
|
2021-08-28 18:41:33 +00:00
|
|
|
attributedTo: str = test_user.remote_id
|
2020-12-13 23:43:39 +00:00
|
|
|
|
2020-12-13 23:56:30 +00:00
|
|
|
class TestPrivacyModel(ActivitypubMixin, BookWyrmModel):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""real simple mock model because BookWyrmModel is abstract"""
|
2021-03-08 16:49:10 +00:00
|
|
|
|
2020-12-13 23:43:39 +00:00
|
|
|
privacy_field = fields.PrivacyField()
|
|
|
|
mention_users = fields.TagField(User)
|
|
|
|
user = fields.ForeignKey(User, on_delete=models.CASCADE)
|
|
|
|
|
2021-03-08 16:49:10 +00:00
|
|
|
public = "https://www.w3.org/ns/activitystreams#Public"
|
2020-12-13 23:43:39 +00:00
|
|
|
data = TestActivity(
|
|
|
|
to=[public],
|
2021-03-08 16:49:10 +00:00
|
|
|
cc=["bleh"],
|
2020-12-13 23:43:39 +00:00
|
|
|
)
|
2021-03-08 16:49:10 +00:00
|
|
|
model_instance = TestPrivacyModel(privacy_field="direct")
|
|
|
|
self.assertEqual(model_instance.privacy_field, "direct")
|
2020-12-13 23:43:39 +00:00
|
|
|
|
|
|
|
instance = fields.PrivacyField()
|
2021-03-08 16:49:10 +00:00
|
|
|
instance.name = "privacy_field"
|
2020-12-13 23:43:39 +00:00
|
|
|
instance.set_field_from_activity(model_instance, data)
|
2021-03-08 16:49:10 +00:00
|
|
|
self.assertEqual(model_instance.privacy_field, "public")
|
2020-12-13 23:43:39 +00:00
|
|
|
|
2021-03-08 16:49:10 +00:00
|
|
|
data.to = ["bleh"]
|
2020-12-13 23:43:39 +00:00
|
|
|
data.cc = []
|
|
|
|
instance.set_field_from_activity(model_instance, data)
|
2021-03-08 16:49:10 +00:00
|
|
|
self.assertEqual(model_instance.privacy_field, "direct")
|
2020-12-13 23:43:39 +00:00
|
|
|
|
2021-03-08 16:49:10 +00:00
|
|
|
data.to = ["bleh"]
|
|
|
|
data.cc = [public, "waah"]
|
2020-12-13 23:43:39 +00:00
|
|
|
instance.set_field_from_activity(model_instance, data)
|
2021-03-08 16:49:10 +00:00
|
|
|
self.assertEqual(model_instance.privacy_field, "unlisted")
|
2020-12-13 23:43:39 +00:00
|
|
|
|
2021-08-28 18:41:33 +00:00
|
|
|
data.to = [test_user.followers_url]
|
2021-08-28 17:08:41 +00:00
|
|
|
data.cc = []
|
|
|
|
instance.set_field_from_activity(model_instance, data)
|
|
|
|
self.assertEqual(model_instance.privacy_field, "followers")
|
|
|
|
|
|
|
|
data.to = ["http://user_remote/followers"]
|
|
|
|
data.cc = ["http://mentioned_user/remote_id"]
|
|
|
|
instance.set_field_from_activity(model_instance, data)
|
|
|
|
self.assertEqual(model_instance.privacy_field, "followers")
|
|
|
|
|
2021-03-08 16:49:10 +00:00
|
|
|
@patch("bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast")
|
2021-09-06 20:53:49 +00:00
|
|
|
@patch("bookwyrm.activitystreams.add_status_task.delay")
|
2021-03-23 17:41:18 +00:00
|
|
|
def test_privacy_field_set_activity_from_field(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""translate between to/cc fields and privacy"""
|
2021-08-02 23:05:40 +00:00
|
|
|
user = User.objects.create_user(
|
|
|
|
"rat", "rat@rat.rat", "ratword", local=True, localname="rat"
|
|
|
|
)
|
2021-03-08 16:49:10 +00:00
|
|
|
public = "https://www.w3.org/ns/activitystreams#Public"
|
2021-11-28 16:24:00 +00:00
|
|
|
followers = f"{user.remote_id}/followers"
|
2020-12-13 23:43:39 +00:00
|
|
|
|
|
|
|
instance = fields.PrivacyField()
|
2021-03-08 16:49:10 +00:00
|
|
|
instance.name = "privacy_field"
|
2020-12-13 23:43:39 +00:00
|
|
|
|
2021-03-08 16:49:10 +00:00
|
|
|
model_instance = Status.objects.create(user=user, content="hi")
|
2020-12-13 23:43:39 +00:00
|
|
|
activity = {}
|
|
|
|
instance.set_activity_from_field(activity, model_instance)
|
2021-03-08 16:49:10 +00:00
|
|
|
self.assertEqual(activity["to"], [public])
|
|
|
|
self.assertEqual(activity["cc"], [followers])
|
2020-12-13 23:43:39 +00:00
|
|
|
|
2021-02-07 00:13:59 +00:00
|
|
|
model_instance = Status.objects.create(
|
2021-03-08 16:49:10 +00:00
|
|
|
user=user, content="hi", privacy="unlisted"
|
|
|
|
)
|
2020-12-13 23:43:39 +00:00
|
|
|
activity = {}
|
|
|
|
instance.set_activity_from_field(activity, model_instance)
|
2021-03-08 16:49:10 +00:00
|
|
|
self.assertEqual(activity["to"], [followers])
|
|
|
|
self.assertEqual(activity["cc"], [public])
|
2020-12-13 23:43:39 +00:00
|
|
|
|
2021-02-07 00:13:59 +00:00
|
|
|
model_instance = Status.objects.create(
|
2021-03-08 16:49:10 +00:00
|
|
|
user=user, content="hi", privacy="followers"
|
|
|
|
)
|
2020-12-13 23:43:39 +00:00
|
|
|
activity = {}
|
|
|
|
instance.set_activity_from_field(activity, model_instance)
|
2021-03-08 16:49:10 +00:00
|
|
|
self.assertEqual(activity["to"], [followers])
|
|
|
|
self.assertEqual(activity["cc"], [])
|
2020-12-13 23:43:39 +00:00
|
|
|
|
|
|
|
model_instance = Status.objects.create(
|
|
|
|
user=user,
|
2021-03-08 16:49:10 +00:00
|
|
|
content="hi",
|
|
|
|
privacy="direct",
|
2020-12-13 23:43:39 +00:00
|
|
|
)
|
|
|
|
model_instance.mention_users.set([user])
|
|
|
|
activity = {}
|
|
|
|
instance.set_activity_from_field(activity, model_instance)
|
2021-03-08 16:49:10 +00:00
|
|
|
self.assertEqual(activity["to"], [user.remote_id])
|
|
|
|
self.assertEqual(activity["cc"], [])
|
2020-12-13 23:43:39 +00:00
|
|
|
|
2021-09-06 22:09:04 +00:00
|
|
|
def test_foreign_key(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""should be able to format a related model"""
|
2021-03-08 16:49:10 +00:00
|
|
|
instance = fields.ForeignKey("User", on_delete=models.CASCADE)
|
|
|
|
Serializable = namedtuple("Serializable", ("to_activity", "remote_id"))
|
|
|
|
item = Serializable(lambda: {"a": "b"}, "https://e.b/c")
|
2020-12-06 05:33:48 +00:00
|
|
|
# returns the remote_id field of the related object
|
2021-03-08 16:49:10 +00:00
|
|
|
self.assertEqual(instance.field_to_activity(item), "https://e.b/c")
|
2020-12-13 23:43:39 +00:00
|
|
|
|
2020-12-07 17:31:12 +00:00
|
|
|
@responses.activate
|
2021-09-06 22:09:04 +00:00
|
|
|
def test_foreign_key_from_activity_str(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""create a new object from a foreign key"""
|
2020-12-06 05:33:48 +00:00
|
|
|
instance = fields.ForeignKey(User, on_delete=models.CASCADE)
|
2021-03-08 16:49:10 +00:00
|
|
|
datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json")
|
2020-12-07 17:14:30 +00:00
|
|
|
userdata = json.loads(datafile.read_bytes())
|
2020-12-07 17:31:12 +00:00
|
|
|
# don't try to load the user icon
|
2021-03-08 16:49:10 +00:00
|
|
|
del userdata["icon"]
|
2020-12-07 17:31:12 +00:00
|
|
|
|
2020-12-12 21:47:51 +00:00
|
|
|
# it shouldn't match with this unrelated user:
|
2021-08-02 23:05:40 +00:00
|
|
|
unrelated_user = User.objects.create_user(
|
|
|
|
"rat", "rat@rat.rat", "ratword", local=True, localname="rat"
|
|
|
|
)
|
2020-12-12 21:47:51 +00:00
|
|
|
|
2021-08-02 23:05:40 +00:00
|
|
|
# test receiving an unknown remote id and loading data
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
|
|
|
"https://example.com/user/mouse",
|
|
|
|
json=userdata,
|
|
|
|
status=200,
|
|
|
|
)
|
|
|
|
with patch("bookwyrm.models.user.set_remote_server.delay"):
|
|
|
|
value = instance.field_from_activity("https://example.com/user/mouse")
|
2020-12-12 21:39:55 +00:00
|
|
|
self.assertIsInstance(value, User)
|
2020-12-12 21:47:51 +00:00
|
|
|
self.assertNotEqual(value, unrelated_user)
|
2021-03-08 16:49:10 +00:00
|
|
|
self.assertEqual(value.remote_id, "https://example.com/user/mouse")
|
|
|
|
self.assertEqual(value.name, "MOUSE?? MOUSE!!")
|
2020-12-12 21:39:55 +00:00
|
|
|
|
2021-08-03 23:21:29 +00:00
|
|
|
def test_foreign_key_from_activity_dict(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""test recieving activity json"""
|
2020-12-12 21:39:55 +00:00
|
|
|
instance = fields.ForeignKey(User, on_delete=models.CASCADE)
|
2021-03-08 16:49:10 +00:00
|
|
|
datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json")
|
2020-12-12 21:39:55 +00:00
|
|
|
userdata = json.loads(datafile.read_bytes())
|
|
|
|
# don't try to load the user icon
|
2021-03-08 16:49:10 +00:00
|
|
|
del userdata["icon"]
|
2020-12-12 21:47:51 +00:00
|
|
|
|
|
|
|
# it shouldn't match with this unrelated user:
|
2021-08-02 23:05:40 +00:00
|
|
|
unrelated_user = User.objects.create_user(
|
|
|
|
"rat", "rat@rat.rat", "ratword", local=True, localname="rat"
|
|
|
|
)
|
|
|
|
with patch("bookwyrm.models.user.set_remote_server.delay"):
|
|
|
|
value = instance.field_from_activity(activitypub.Person(**userdata))
|
2020-12-07 17:14:30 +00:00
|
|
|
self.assertIsInstance(value, User)
|
2020-12-12 21:47:51 +00:00
|
|
|
self.assertNotEqual(value, unrelated_user)
|
2021-03-08 16:49:10 +00:00
|
|
|
self.assertEqual(value.remote_id, "https://example.com/user/mouse")
|
|
|
|
self.assertEqual(value.name, "MOUSE?? MOUSE!!")
|
2020-12-07 17:14:30 +00:00
|
|
|
# et cetera but we're not testing serializing user json
|
2020-12-06 05:33:48 +00:00
|
|
|
|
2021-09-06 22:09:04 +00:00
|
|
|
def test_foreign_key_from_activity_dict_existing(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""test receiving a dict of an existing object in the db"""
|
2020-12-12 21:39:55 +00:00
|
|
|
instance = fields.ForeignKey(User, on_delete=models.CASCADE)
|
2021-03-08 16:49:10 +00:00
|
|
|
datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json")
|
2020-12-12 21:39:55 +00:00
|
|
|
userdata = json.loads(datafile.read_bytes())
|
2021-08-02 23:05:40 +00:00
|
|
|
user = User.objects.create_user(
|
|
|
|
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
|
|
|
|
)
|
|
|
|
user.remote_id = "https://example.com/user/mouse"
|
2021-08-03 23:21:29 +00:00
|
|
|
user.save(broadcast=False, update_fields=["remote_id"])
|
2021-05-26 21:57:29 +00:00
|
|
|
|
2021-08-02 23:05:40 +00:00
|
|
|
User.objects.create_user(
|
|
|
|
"rat", "rat@rat.rat", "ratword", local=True, localname="rat"
|
|
|
|
)
|
2020-12-12 21:47:51 +00:00
|
|
|
|
2021-08-02 23:05:40 +00:00
|
|
|
with patch("bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast"):
|
|
|
|
value = instance.field_from_activity(activitypub.Person(**userdata))
|
2020-12-12 21:39:55 +00:00
|
|
|
self.assertEqual(value, user)
|
|
|
|
|
2021-09-06 22:09:04 +00:00
|
|
|
def test_foreign_key_from_activity_str_existing(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""test receiving a remote id of an existing object in the db"""
|
2020-12-12 21:39:55 +00:00
|
|
|
instance = fields.ForeignKey(User, on_delete=models.CASCADE)
|
2021-08-02 23:05:40 +00:00
|
|
|
user = User.objects.create_user(
|
|
|
|
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
|
|
|
|
)
|
|
|
|
User.objects.create_user(
|
|
|
|
"rat", "rat@rat.rat", "ratword", local=True, localname="rat"
|
|
|
|
)
|
2020-12-12 21:47:51 +00:00
|
|
|
|
2020-12-06 05:33:48 +00:00
|
|
|
value = instance.field_from_activity(user.remote_id)
|
|
|
|
self.assertEqual(value, user)
|
|
|
|
|
2021-09-06 22:09:04 +00:00
|
|
|
def test_one_to_one_field(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""a gussied up foreign key"""
|
2021-03-08 16:49:10 +00:00
|
|
|
instance = fields.OneToOneField("User", on_delete=models.CASCADE)
|
|
|
|
Serializable = namedtuple("Serializable", ("to_activity", "remote_id"))
|
|
|
|
item = Serializable(lambda: {"a": "b"}, "https://e.b/c")
|
|
|
|
self.assertEqual(instance.field_to_activity(item), {"a": "b"})
|
2020-12-05 01:57:14 +00:00
|
|
|
|
2021-09-06 22:09:04 +00:00
|
|
|
def test_many_to_many_field(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""lists!"""
|
2021-03-08 16:49:10 +00:00
|
|
|
instance = fields.ManyToManyField("User")
|
2020-12-05 01:57:14 +00:00
|
|
|
|
2021-03-08 16:49:10 +00:00
|
|
|
Serializable = namedtuple("Serializable", ("to_activity", "remote_id"))
|
|
|
|
Queryset = namedtuple("Queryset", ("all", "instance"))
|
|
|
|
item = Serializable(lambda: {"a": "b"}, "https://e.b/c")
|
|
|
|
another_item = Serializable(lambda: {}, "example.com")
|
2020-12-05 01:57:14 +00:00
|
|
|
|
|
|
|
items = Queryset(lambda: [item], another_item)
|
|
|
|
|
2021-03-08 16:49:10 +00:00
|
|
|
self.assertEqual(instance.field_to_activity(items), ["https://e.b/c"])
|
2020-12-05 01:57:14 +00:00
|
|
|
|
2021-03-08 16:49:10 +00:00
|
|
|
instance = fields.ManyToManyField("User", link_only=True)
|
|
|
|
instance.name = "snake_case"
|
|
|
|
self.assertEqual(instance.field_to_activity(items), "example.com/snake_case")
|
2020-12-05 22:40:23 +00:00
|
|
|
|
2020-12-07 17:31:12 +00:00
|
|
|
@responses.activate
|
2021-09-06 22:09:04 +00:00
|
|
|
def test_many_to_many_field_from_activity(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""resolve related fields for a list, takes a list of remote ids"""
|
2020-12-07 17:31:12 +00:00
|
|
|
instance = fields.ManyToManyField(User)
|
2021-03-08 16:49:10 +00:00
|
|
|
datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json")
|
2020-12-07 17:31:12 +00:00
|
|
|
userdata = json.loads(datafile.read_bytes())
|
|
|
|
# don't try to load the user icon
|
2021-03-08 16:49:10 +00:00
|
|
|
del userdata["icon"]
|
2020-12-07 17:31:12 +00:00
|
|
|
|
|
|
|
# test receiving an unknown remote id and loading data
|
|
|
|
responses.add(
|
2021-03-08 16:49:10 +00:00
|
|
|
responses.GET, "https://example.com/user/mouse", json=userdata, status=200
|
|
|
|
)
|
2021-08-02 23:05:40 +00:00
|
|
|
with patch("bookwyrm.models.user.set_remote_server.delay"):
|
|
|
|
value = instance.field_from_activity(
|
|
|
|
["https://example.com/user/mouse", "bleh"]
|
|
|
|
)
|
2020-12-07 17:31:12 +00:00
|
|
|
self.assertIsInstance(value, list)
|
|
|
|
self.assertEqual(len(value), 1)
|
|
|
|
self.assertIsInstance(value[0], User)
|
2020-12-07 17:14:30 +00:00
|
|
|
|
2021-09-06 22:09:04 +00:00
|
|
|
def test_tag_field(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""a special type of many to many field"""
|
2021-03-08 16:49:10 +00:00
|
|
|
instance = fields.TagField("User")
|
2020-12-05 22:40:23 +00:00
|
|
|
|
|
|
|
Serializable = namedtuple(
|
2021-03-08 16:49:10 +00:00
|
|
|
"Serializable", ("to_activity", "remote_id", "name_field", "name")
|
2020-12-05 22:40:23 +00:00
|
|
|
)
|
2021-03-08 16:49:10 +00:00
|
|
|
Queryset = namedtuple("Queryset", ("all", "instance"))
|
|
|
|
item = Serializable(lambda: {"a": "b"}, "https://e.b/c", "name", "Name")
|
|
|
|
another_item = Serializable(lambda: {}, "example.com", "", "")
|
2020-12-05 22:40:23 +00:00
|
|
|
items = Queryset(lambda: [item], another_item)
|
|
|
|
|
|
|
|
result = instance.field_to_activity(items)
|
|
|
|
self.assertIsInstance(result, list)
|
|
|
|
self.assertEqual(len(result), 1)
|
2021-03-08 16:49:10 +00:00
|
|
|
self.assertEqual(result[0].href, "https://e.b/c")
|
|
|
|
self.assertEqual(result[0].name, "Name")
|
|
|
|
self.assertEqual(result[0].type, "Serializable")
|
2020-12-05 22:40:23 +00:00
|
|
|
|
2021-09-06 22:09:04 +00:00
|
|
|
def test_tag_field_from_activity(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""loadin' a list of items from Links"""
|
2020-12-07 17:14:30 +00:00
|
|
|
# TODO
|
|
|
|
|
2021-03-08 16:49:10 +00:00
|
|
|
@patch("bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast")
|
2021-08-03 23:21:29 +00:00
|
|
|
@patch("bookwyrm.suggested_users.remove_user_task.delay")
|
2021-11-28 16:24:00 +00:00
|
|
|
def test_image_field_to_activity(self, *_):
|
|
|
|
"""serialize an image field to activitypub"""
|
2021-08-02 23:05:40 +00:00
|
|
|
user = User.objects.create_user(
|
|
|
|
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
|
|
|
|
)
|
|
|
|
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)
|
|
|
|
user.avatar.save("test.jpg", ContentFile(output.getvalue()))
|
2020-12-05 22:40:23 +00:00
|
|
|
|
2021-08-04 17:11:57 +00:00
|
|
|
instance = fields.ImageField()
|
|
|
|
|
|
|
|
output = instance.field_to_activity(user.avatar)
|
2020-12-05 22:40:23 +00:00
|
|
|
self.assertIsNotNone(
|
|
|
|
re.match(
|
2021-08-04 17:11:57 +00:00
|
|
|
fr"https:\/\/{DOMAIN}\/.*\.jpg",
|
2020-12-05 22:40:23 +00:00
|
|
|
output.url,
|
|
|
|
)
|
|
|
|
)
|
2021-08-04 17:11:57 +00:00
|
|
|
self.assertEqual(output.name, "")
|
2021-03-15 22:45:18 +00:00
|
|
|
self.assertEqual(output.type, "Document")
|
2020-12-05 22:40:23 +00:00
|
|
|
|
2021-11-28 16:24:00 +00:00
|
|
|
@responses.activate
|
|
|
|
def test_image_field_from_activity(self, *_):
|
|
|
|
"""load an image from activitypub"""
|
|
|
|
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)
|
|
|
|
|
|
|
|
instance = fields.ImageField()
|
|
|
|
|
2020-12-05 22:40:23 +00:00
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
2021-03-08 16:49:10 +00:00
|
|
|
"http://www.example.com/image.jpg",
|
2021-11-28 16:24:00 +00:00
|
|
|
body=image.tobytes(),
|
2021-03-08 16:49:10 +00:00
|
|
|
status=200,
|
|
|
|
)
|
|
|
|
loaded_image = instance.field_from_activity("http://www.example.com/image.jpg")
|
2020-12-05 22:40:23 +00:00
|
|
|
self.assertIsInstance(loaded_image, list)
|
|
|
|
self.assertIsInstance(loaded_image[1], ContentFile)
|
|
|
|
|
2021-11-28 16:49:54 +00:00
|
|
|
@responses.activate
|
|
|
|
def test_image_field_set_field_from_activity(self, *_):
|
|
|
|
"""update a model instance from an activitypub object"""
|
|
|
|
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)
|
|
|
|
|
|
|
|
instance = fields.ImageField(activitypub_field="cover", name="cover")
|
|
|
|
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
|
|
|
"http://www.example.com/image.jpg",
|
|
|
|
body=image.tobytes(),
|
|
|
|
status=200,
|
|
|
|
)
|
|
|
|
book = Edition.objects.create(title="hello")
|
|
|
|
|
|
|
|
MockActivity = namedtuple("MockActivity", ("cover"))
|
|
|
|
mock_activity = MockActivity("http://www.example.com/image.jpg")
|
|
|
|
|
|
|
|
instance.set_field_from_activity(book, mock_activity)
|
|
|
|
self.assertIsNotNone(book.cover.name)
|
|
|
|
|
2021-11-28 17:23:13 +00:00
|
|
|
@responses.activate
|
|
|
|
def test_image_field_set_field_from_activity_no_overwrite_no_cover(self, *_):
|
|
|
|
"""update a model instance from an activitypub object"""
|
|
|
|
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)
|
|
|
|
|
|
|
|
instance = fields.ImageField(activitypub_field="cover", name="cover")
|
|
|
|
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
|
|
|
"http://www.example.com/image.jpg",
|
|
|
|
body=image.tobytes(),
|
|
|
|
status=200,
|
|
|
|
)
|
|
|
|
book = Edition.objects.create(title="hello")
|
|
|
|
|
|
|
|
MockActivity = namedtuple("MockActivity", ("cover"))
|
|
|
|
mock_activity = MockActivity("http://www.example.com/image.jpg")
|
|
|
|
|
|
|
|
instance.set_field_from_activity(book, mock_activity, overwrite=False)
|
|
|
|
self.assertIsNotNone(book.cover.name)
|
|
|
|
|
|
|
|
@responses.activate
|
|
|
|
def test_image_field_set_field_from_activity_no_overwrite_with_cover(self, *_):
|
|
|
|
"""update a model instance from an activitypub object"""
|
|
|
|
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)
|
|
|
|
|
|
|
|
another_image_file = pathlib.Path(__file__).parent.joinpath(
|
|
|
|
"../../static/images/logo.png"
|
|
|
|
)
|
|
|
|
another_image = Image.open(another_image_file)
|
|
|
|
another_output = BytesIO()
|
|
|
|
another_image.save(another_output, format=another_image.format)
|
|
|
|
|
|
|
|
instance = fields.ImageField(activitypub_field="cover", name="cover")
|
|
|
|
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
|
|
|
"http://www.example.com/image.jpg",
|
|
|
|
body=another_image.tobytes(),
|
|
|
|
status=200,
|
|
|
|
)
|
|
|
|
book = Edition.objects.create(title="hello")
|
|
|
|
book.cover.save("test.jpg", ContentFile(output.getvalue()))
|
2022-01-04 19:35:12 +00:00
|
|
|
cover_size = book.cover.size
|
|
|
|
self.assertIsNotNone(cover_size)
|
2021-11-28 17:23:13 +00:00
|
|
|
|
|
|
|
MockActivity = namedtuple("MockActivity", ("cover"))
|
|
|
|
mock_activity = MockActivity("http://www.example.com/image.jpg")
|
|
|
|
|
|
|
|
instance.set_field_from_activity(book, mock_activity, overwrite=False)
|
|
|
|
# same cover as before
|
2022-01-04 19:35:12 +00:00
|
|
|
self.assertEqual(book.cover.size, cover_size)
|
2021-11-28 17:23:13 +00:00
|
|
|
|
|
|
|
@responses.activate
|
|
|
|
def test_image_field_set_field_from_activity_with_overwrite_with_cover(self, *_):
|
|
|
|
"""update a model instance from an activitypub object"""
|
|
|
|
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)
|
|
|
|
book = Edition.objects.create(title="hello")
|
|
|
|
book.cover.save("test.jpg", ContentFile(output.getvalue()))
|
2022-01-04 19:35:12 +00:00
|
|
|
cover_size = book.cover.size
|
|
|
|
self.assertIsNotNone(cover_size)
|
2021-11-28 17:23:13 +00:00
|
|
|
|
|
|
|
another_image_file = pathlib.Path(__file__).parent.joinpath(
|
|
|
|
"../../static/images/logo.png"
|
|
|
|
)
|
|
|
|
another_image = Image.open(another_image_file)
|
|
|
|
another_output = BytesIO()
|
|
|
|
another_image.save(another_output, format=another_image.format)
|
|
|
|
|
|
|
|
instance = fields.ImageField(activitypub_field="cover", name="cover")
|
|
|
|
|
|
|
|
responses.add(
|
|
|
|
responses.GET,
|
|
|
|
"http://www.example.com/image.jpg",
|
|
|
|
body=another_image.tobytes(),
|
|
|
|
status=200,
|
|
|
|
)
|
|
|
|
|
|
|
|
MockActivity = namedtuple("MockActivity", ("cover"))
|
|
|
|
mock_activity = MockActivity("http://www.example.com/image.jpg")
|
|
|
|
|
|
|
|
instance.set_field_from_activity(book, mock_activity, overwrite=True)
|
|
|
|
# new cover
|
|
|
|
self.assertIsNotNone(book.cover.name)
|
2022-01-04 19:35:12 +00:00
|
|
|
self.assertFalse(book.cover.size, cover_size)
|
2021-11-28 17:23:13 +00:00
|
|
|
|
2021-09-06 22:09:04 +00:00
|
|
|
def test_datetime_field(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""this one is pretty simple, it just has to use isoformat"""
|
2020-12-05 22:40:23 +00:00
|
|
|
instance = fields.DateTimeField()
|
|
|
|
now = timezone.now()
|
|
|
|
self.assertEqual(instance.field_to_activity(now), now.isoformat())
|
2021-03-08 16:49:10 +00:00
|
|
|
self.assertEqual(instance.field_from_activity(now.isoformat()), now)
|
|
|
|
self.assertEqual(instance.field_from_activity("bip"), None)
|
2020-12-05 22:40:23 +00:00
|
|
|
|
2021-09-06 22:09:04 +00:00
|
|
|
def test_array_field(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""idk why it makes them strings but probably for a good reason"""
|
2020-12-05 22:40:23 +00:00
|
|
|
instance = fields.ArrayField(fields.IntegerField)
|
2021-03-08 16:49:10 +00:00
|
|
|
self.assertEqual(instance.field_to_activity([0, 1]), ["0", "1"])
|
2020-12-17 02:39:18 +00:00
|
|
|
|
2021-09-06 22:09:04 +00:00
|
|
|
def test_html_field(self, *_):
|
2021-04-26 16:15:42 +00:00
|
|
|
"""sanitizes html, the sanitizer has its own tests"""
|
2020-12-17 02:39:18 +00:00
|
|
|
instance = fields.HtmlField()
|
|
|
|
self.assertEqual(
|
2021-03-08 16:49:10 +00:00
|
|
|
instance.field_from_activity("<marquee><p>hi</p></marquee>"), "<p>hi</p>"
|
2020-12-17 02:39:18 +00:00
|
|
|
)
|