Merge pull request #366 from mouse-reeve/tests

Fixes default field values in ActivityPub dataclasses
This commit is contained in:
Mouse Reeve 2020-11-25 11:19:31 -08:00 committed by GitHub
commit 121e685f8a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 30 additions and 60 deletions

View file

@ -74,7 +74,8 @@ class ActivityObject:
try: try:
value = kwargs[field.name] value = kwargs[field.name]
except KeyError: except KeyError:
if field.default == MISSING: if field.default == MISSING and \
field.default_factory == MISSING:
raise ActivitySerializerError(\ raise ActivitySerializerError(\
'Missing required field: %s' % field.name) 'Missing required field: %s' % field.name)
value = field.default value = field.default
@ -143,6 +144,8 @@ class ActivityObject:
# add images # add images
for (model_key, value) in image_fields.items(): for (model_key, value) in image_fields.items():
if not value:
continue
getattr(instance, model_key).save(*value, save=True) getattr(instance, model_key).save(*value, save=True)
# add one to many fields # add one to many fields
@ -188,6 +191,8 @@ def resolve_foreign_key(model, remote_id):
def tag_formatter(tags, tag_type): def tag_formatter(tags, tag_type):
''' helper function to extract foreign keys from tag activity json ''' ''' helper function to extract foreign keys from tag activity json '''
if not isinstance(tags, list):
return []
items = [] items = []
types = { types = {
'Book': models.Book, 'Book': models.Book,
@ -207,9 +212,9 @@ def tag_formatter(tags, tag_type):
def image_formatter(image_json): def image_formatter(image_json):
''' helper function to load images and format them for a model ''' ''' helper function to load images and format them for a model '''
url = image_json.get('url') if not image_json or not hasattr(image_json, 'url'):
if not url:
return None return None
url = image_json.get('url')
try: try:
response = requests.get(url) response = requests.get(url)
@ -230,6 +235,8 @@ def image_attachments_formatter(images_json):
caption = image.get('name') caption = image.get('name')
attachment = models.Attachment(caption=caption) attachment = models.Attachment(caption=caption)
image_field = image_formatter(image) image_field = image_formatter(image)
if not image_field:
continue
attachment.image.save(*image_field, save=False) attachment.image.save(*image_field, save=False)
attachments.append(attachment) attachments.append(attachment)
return attachments return attachments

View file

@ -25,7 +25,7 @@ class Book(ActivityObject):
librarything_key: str librarything_key: str
goodreads_key: str goodreads_key: str
attachment: List[Image] = field(default=lambda: []) attachment: List[Image] = field(default_factory=lambda: [])
type: str = 'Book' type: str = 'Book'

View file

@ -24,8 +24,8 @@ class Note(ActivityObject):
cc: List[str] cc: List[str]
content: str content: str
replies: Dict replies: Dict
tag: List[Link] = field(default=lambda: []) tag: List[Link] = field(default_factory=lambda: [])
attachment: List[Image] = field(default=lambda: []) attachment: List[Image] = field(default_factory=lambda: [])
sensitive: bool = False sensitive: bool = False
type: str = 'Note' type: str = 'Note'

View file

@ -15,7 +15,7 @@ class Person(ActivityObject):
summary: str summary: str
publicKey: PublicKey publicKey: PublicKey
endpoints: Dict endpoints: Dict
icon: Image = field(default=lambda: {}) icon: Image = field(default_factory=lambda: {})
bookwyrmUser: bool = False bookwyrmUser: bool = False
manuallyApprovesFollowers: str = False manuallyApprovesFollowers: str = False
discoverable: str = True discoverable: str = True

View file

@ -59,24 +59,31 @@ class ActivitypubMixin:
def to_activity(self, pure=False): def to_activity(self, pure=False):
''' convert from a model to an activity ''' ''' convert from a model to an activity '''
if pure: if pure:
# works around bookwyrm-specific fields for vanilla AP services
mappings = self.pure_activity_mappings mappings = self.pure_activity_mappings
else: else:
# may include custom fields that bookwyrm instances will understand
mappings = self.activity_mappings mappings = self.activity_mappings
fields = {} fields = {}
for mapping in mappings: for mapping in mappings:
if not hasattr(self, mapping.model_key) or not mapping.activity_key: if not hasattr(self, mapping.model_key) or not mapping.activity_key:
# this field on the model isn't serialized
continue continue
value = getattr(self, mapping.model_key) value = getattr(self, mapping.model_key)
if hasattr(value, 'remote_id'): if hasattr(value, 'remote_id'):
# this is probably a foreign key field, which we want to
# serialize as just the remote_id url reference
value = value.remote_id value = value.remote_id
if isinstance(value, datetime): elif isinstance(value, datetime):
value = value.isoformat() value = value.isoformat()
# run the custom formatter function set in the model
result = mapping.activity_formatter(value) result = mapping.activity_formatter(value)
if mapping.activity_key in fields and \ if mapping.activity_key in fields and \
isinstance(fields[mapping.activity_key], list): isinstance(fields[mapping.activity_key], list):
# there are two database fields that map to the same AP list # there can be two database fields that map to the same AP list
# this happens in status, which combines user and book tags # this happens in status tags, which combines user and book tags
fields[mapping.activity_key] += result fields[mapping.activity_key] += result
else: else:
fields[mapping.activity_key] = result fields[mapping.activity_key] = result
@ -265,7 +272,7 @@ def tag_formatter(items, name_field, activity_type):
def image_formatter(image, default_path=None): def image_formatter(image, default_path=None):
''' convert images into activitypub json ''' ''' convert images into activitypub json '''
if image: if image and hasattr(image, 'url'):
url = image.url url = image.url
elif default_path: elif default_path:
url = default_path url = default_path

View file

@ -57,10 +57,9 @@ class SelfConnector(TestCase):
def test_search_rank(self): def test_search_rank(self):
results = self.connector.search('Anonymous') results = self.connector.search('Anonymous')
self.assertEqual(len(results), 3) self.assertEqual(len(results), 2)
self.assertEqual(results[0].title, 'Edition of Example Work') self.assertEqual(results[0].title, 'More Editions')
self.assertEqual(results[1].title, 'More Editions') self.assertEqual(results[1].title, 'Edition of Example Work')
self.assertEqual(results[2].title, 'Another Edition')
def test_search_default_filter(self): def test_search_default_filter(self):

View file

@ -19,8 +19,8 @@
"mediaType": "image//images/covers/2b4e4712-5a4d-4ac1-9df4-634cc9c7aff3jpg", "mediaType": "image//images/covers/2b4e4712-5a4d-4ac1-9df4-634cc9c7aff3jpg",
"url": "https://example.com/images/covers/2b4e4712-5a4d-4ac1-9df4-634cc9c7aff3jpg", "url": "https://example.com/images/covers/2b4e4712-5a4d-4ac1-9df4-634cc9c7aff3jpg",
"name": "Cover of \"This Is How You Lose the Time War\"" "name": "Cover of \"This Is How You Lose the Time War\""
} }
], ],
"replies": { "replies": {
"id": "https://example.com/user/mouse/quotation/13/replies", "id": "https://example.com/user/mouse/quotation/13/replies",
"type": "Collection", "type": "Collection",

View file

@ -21,50 +21,7 @@ class RemoteUser(TestCase):
self.user_data = json.loads(datafile.read_bytes()) self.user_data = json.loads(datafile.read_bytes())
def test_get_remote_user(self): def test_get_remote_user(self):
actor = 'https://example.com/users/rat' actor = 'https://example.com/users/rat'
user = remote_user.get_or_create_remote_user(actor) user = remote_user.get_or_create_remote_user(actor)
self.assertEqual(user, self.remote_user) self.assertEqual(user, self.remote_user)
def test_create_remote_user(self):
user = remote_user.create_remote_user(self.user_data)
self.assertFalse(user.local)
self.assertEqual(user.remote_id, 'https://example.com/user/mouse')
self.assertEqual(user.username, 'mouse@example.com')
self.assertEqual(user.name, 'MOUSE?? MOUSE!!')
self.assertEqual(user.inbox, 'https://example.com/user/mouse/inbox')
self.assertEqual(user.outbox, 'https://example.com/user/mouse/outbox')
self.assertEqual(user.shared_inbox, 'https://example.com/inbox')
self.assertEqual(
user.public_key,
self.user_data['publicKey']['publicKeyPem']
)
self.assertEqual(user.local, False)
self.assertEqual(user.bookwyrm_user, True)
self.assertEqual(user.manually_approves_followers, False)
def test_create_remote_user_missing_inbox(self):
del self.user_data['inbox']
self.assertRaises(
TypeError,
remote_user.create_remote_user,
self.user_data
)
def test_create_remote_user_missing_outbox(self):
del self.user_data['outbox']
self.assertRaises(
TypeError,
remote_user.create_remote_user,
self.user_data
)
def test_create_remote_user_default_fields(self):
del self.user_data['manuallyApprovesFollowers']
user = remote_user.create_remote_user(self.user_data)
self.assertEqual(user.manually_approves_followers, False)