1
0
Fork 0

Use dataclasses to define activitypub (de)serialization (#177)

* Use dataclasses to define activitypub (de)serialization
This commit is contained in:
Mouse Reeve 2020-09-17 13:02:52 -07:00 committed by GitHub
parent 2c0a07a330
commit 8bbf1fe252
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 1449 additions and 1228 deletions

View file

@ -0,0 +1 @@
from . import *

View file

@ -0,0 +1,27 @@
import datetime
from django.test import TestCase
from fedireads import models
class Author(TestCase):
def setUp(self):
self.book = models.Edition.objects.create(
title='Example Edition',
remote_id='https://example.com/book/1',
)
self.author = models.Author.objects.create(
name='Author fullname',
first_name='Auth',
last_name='Or',
aliases=['One', 'Two'],
bio='bio bio bio',
)
def test_serialize_model(self):
activity = self.author.to_activity()
self.assertEqual(activity['id'], self.author.remote_id)
self.assertIsInstance(activity['aliases'], list)
self.assertEqual(activity['aliases'], ['One', 'Two'])
self.assertEqual(activity['name'], 'Author fullname')

View file

@ -0,0 +1,32 @@
import json
import pathlib
from django.test import TestCase
from fedireads import activitypub, models
class Person(TestCase):
def setUp(self):
self.user = models.User.objects.create_user(
'rat', 'rat@rat.com', 'ratword',
)
datafile = pathlib.Path(__file__).parent.joinpath(
'../data/ap_user.json'
)
self.user_data = json.loads(datafile.read_bytes())
def test_load_user_data(self):
activity = activitypub.Person(**self.user_data)
self.assertEqual(activity.id, 'https://example.com/user/mouse')
self.assertEqual(activity.preferredUsername, 'mouse')
self.assertEqual(activity.type, 'Person')
def test_serialize_model(self):
activity = self.user.to_activity()
self.assertEqual(activity['id'], self.user.remote_id)
self.assertEqual(
activity['endpoints'],
{'sharedInbox': self.user.shared_inbox}
)

View file

@ -0,0 +1,46 @@
import json
import pathlib
from django.test import TestCase
from fedireads import activitypub, models
class Quotation(TestCase):
''' we have hecka ways to create statuses '''
def setUp(self):
self.user = models.User.objects.create_user(
'mouse', 'mouse@mouse.mouse', 'mouseword',
local=False,
inbox='https://example.com/user/mouse/inbox',
outbox='https://example.com/user/mouse/outbox',
remote_id='https://example.com/user/mouse',
)
self.book = models.Edition.objects.create(
title='Example Edition',
remote_id='https://example.com/book/1',
)
datafile = pathlib.Path(__file__).parent.joinpath(
'../data/ap_quotation.json'
)
self.status_data = json.loads(datafile.read_bytes())
def test_quotation_activity(self):
quotation = activitypub.Quotation(**self.status_data)
self.assertEqual(quotation.type, 'Quotation')
self.assertEqual(
quotation.id, 'https://example.com/user/mouse/quotation/13')
self.assertEqual(quotation.content, 'commentary')
self.assertEqual(quotation.quote, 'quote body')
self.assertEqual(quotation.inReplyToBook, 'https://example.com/book/1')
self.assertEqual(
quotation.published, '2020-05-10T02:38:31.150343+00:00')
def test_activity_to_model(self):
activity = activitypub.Quotation(**self.status_data)
quotation = activity.to_model(models.Quotation)
self.assertEqual(quotation.book, self.book)
self.assertEqual(quotation.user, self.user)

View file

@ -0,0 +1,29 @@
{
"id": "https://example.com/user/mouse/comment/6",
"url": "https://example.com/user/mouse/comment/6",
"inReplyTo": null,
"published": "2020-05-08T23:45:44.768012+00:00",
"attributedTo": "https://example.com/user/mouse",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"https://example.com/user/mouse/followers"
],
"sensitive": null,
"content": "commentary",
"type": "Comment",
"attachment": [],
"replies": {
"id": "https://example.com/user/mouse/comment/6/replies",
"type": "Collection",
"first": {
"type": "CollectionPage",
"next": "https://example.com/user/mouse/comment/6/replies?only_other_accounts=true&page=true",
"partOf": "https://example.com/user/mouse/comment/6/replies",
"items": []
}
},
"inReplyToBook": "https://example.com/book/1"
}

View file

@ -0,0 +1,36 @@
{
"id": "https://example.com/user/mouse/quotation/13",
"url": "https://example.com/user/mouse/quotation/13",
"inReplyTo": null,
"published": "2020-05-10T02:38:31.150343+00:00",
"attributedTo": "https://example.com/user/mouse",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"https://example.com/user/mouse/followers"
],
"sensitive": false,
"content": "commentary",
"type": "Quotation",
"attachment": [
{
"type": "Document",
"mediaType": "image//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\""
}
],
"replies": {
"id": "https://example.com/user/mouse/quotation/13/replies",
"type": "Collection",
"first": {
"type": "CollectionPage",
"next": "https://example.com/user/mouse/quotation/13/replies?only_other_accounts=true&page=true",
"partOf": "https://example.com/user/mouse/quotation/13/replies",
"items": []
}
},
"inReplyToBook": "https://example.com/book/1",
"quote": "quote body"
}

View file

@ -19,32 +19,24 @@ class Status(TestCase):
def test_status(self):
status = models.Status.objects.first()
self.assertEqual(status.status_type, 'Note')
self.assertEqual(status.activity_type, 'Note')
expected_id = 'https://%s/user/mouse/status/%d' % \
(settings.DOMAIN, status.id)
self.assertEqual(status.remote_id, expected_id)
def test_comment(self):
comment = models.Comment.objects.first()
self.assertEqual(comment.status_type, 'Comment')
self.assertEqual(comment.activity_type, 'Note')
expected_id = 'https://%s/user/mouse/comment/%d' % \
(settings.DOMAIN, comment.id)
self.assertEqual(comment.remote_id, expected_id)
def test_quotation(self):
quotation = models.Quotation.objects.first()
self.assertEqual(quotation.status_type, 'Quotation')
self.assertEqual(quotation.activity_type, 'Note')
expected_id = 'https://%s/user/mouse/quotation/%d' % \
(settings.DOMAIN, quotation.id)
self.assertEqual(quotation.remote_id, expected_id)
def test_review(self):
review = models.Review.objects.first()
self.assertEqual(review.status_type, 'Review')
self.assertEqual(review.activity_type, 'Article')
expected_id = 'https://%s/user/mouse/review/%d' % \
(settings.DOMAIN, review.id)
self.assertEqual(review.remote_id, expected_id)

View file

@ -16,41 +16,3 @@ class Comment(TestCase):
comment = status_builder.create_comment(
self.user, self.book, 'commentary')
self.assertEqual(comment.content, 'commentary')
def test_comment_from_activity(self):
activity = {
"id": "https://example.com/user/mouse/comment/6",
"url": "https://example.com/user/mouse/comment/6",
"inReplyTo": None,
"published": "2020-05-08T23:45:44.768012+00:00",
"attributedTo": "https://example.com/user/mouse",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"https://example.com/user/mouse/followers"
],
"sensitive": False,
"content": "commentary",
"type": "Note",
"attachment": [],
"replies": {
"id": "https://example.com/user/mouse/comment/6/replies",
"type": "Collection",
"first": {
"type": "CollectionPage",
"next": "https://example.com/user/mouse/comment/6/replies?only_other_accounts=true&page=true",
"partOf": "https://example.com/user/mouse/comment/6/replies",
"items": []
}
},
"inReplyToBook": self.book.remote_id,
"fedireadsType": "Comment"
}
comment = status_builder.create_comment_from_activity(
self.user, activity)
self.assertEqual(comment.content, 'commentary')
self.assertEqual(comment.book, self.book)
self.assertEqual(
comment.published_date, '2020-05-08T23:45:44.768012+00:00')

View file

@ -1,6 +1,8 @@
from django.test import TestCase
import json
import pathlib
from fedireads import models
from fedireads import activitypub, models
from fedireads import status as status_builder
@ -8,8 +10,13 @@ class Quotation(TestCase):
''' we have hecka ways to create statuses '''
def setUp(self):
self.user = models.User.objects.create_user(
'mouse', 'mouse@mouse.mouse', 'mouseword')
self.book = models.Edition.objects.create(title='Example Edition')
'mouse', 'mouse@mouse.mouse', 'mouseword',
remote_id='https://example.com/user/mouse'
)
self.book = models.Edition.objects.create(
title='Example Edition',
remote_id='https://example.com/book/1',
)
def test_create_quotation(self):
@ -17,50 +24,3 @@ class Quotation(TestCase):
self.user, self.book, 'commentary', 'a quote')
self.assertEqual(quotation.quote, 'a quote')
self.assertEqual(quotation.content, 'commentary')
def test_quotation_from_activity(self):
activity = {
'id': 'https://example.com/user/mouse/quotation/13',
'url': 'https://example.com/user/mouse/quotation/13',
'inReplyTo': None,
'published': '2020-05-10T02:38:31.150343+00:00',
'attributedTo': 'https://example.com/user/mouse',
'to': [
'https://www.w3.org/ns/activitystreams#Public'
],
'cc': [
'https://example.com/user/mouse/followers'
],
'sensitive': False,
'content': 'commentary',
'type': 'Note',
'attachment': [
{
'type': 'Document',
'mediaType': 'image//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\''
}
],
'replies': {
'id': 'https://example.com/user/mouse/quotation/13/replies',
'type': 'Collection',
'first': {
'type': 'CollectionPage',
'next': 'https://example.com/user/mouse/quotation/13/replies?only_other_accounts=true&page=true',
'partOf': 'https://example.com/user/mouse/quotation/13/replies',
'items': []
}
},
'inReplyToBook': self.book.remote_id,
'fedireadsType': 'Quotation',
'quote': 'quote body'
}
quotation = status_builder.create_quotation_from_activity(
self.user, activity)
self.assertEqual(quotation.content, 'commentary')
self.assertEqual(quotation.quote, 'quote body')
self.assertEqual(quotation.book, self.book)
self.assertEqual(
quotation.published_date, '2020-05-10T02:38:31.150343+00:00')

View file

@ -37,45 +37,3 @@ class Review(TestCase):
self.assertEqual(review.name, 'review name')
self.assertEqual(review.content, 'content')
self.assertEqual(review.rating, None)
def test_review_from_activity(self):
activity = {
'id': 'https://example.com/user/mouse/review/9',
'url': 'https://example.com/user/mouse/review/9',
'inReplyTo': None,
'published': '2020-05-04T00:00:00.000000+00:00',
'attributedTo': 'https://example.com/user/mouse',
'to': [
'https://www.w3.org/ns/activitystreams#Public'
],
'cc': [
'https://example.com/user/mouse/followers'
],
'sensitive': False,
'content': 'review content',
'type': 'Article',
'attachment': [],
'replies': {
'id': 'https://example.com/user/mouse/review/9/replies',
'type': 'Collection',
'first': {
'type': 'CollectionPage',
'next': 'https://example.com/user/mouse/review/9/replies?only_other_accounts=true&page=true',
'partOf': 'https://example.com/user/mouse/review/9/replies',
'items': []
}
},
'inReplyToBook': self.book.remote_id,
'fedireadsType': 'Review',
'name': 'review title',
'rating': 3
}
review = status_builder.create_review_from_activity(
self.user, activity)
self.assertEqual(review.content, 'review content')
self.assertEqual(review.name, 'review title')
self.assertEqual(review.rating, 3)
self.assertEqual(review.book, self.book)
self.assertEqual(
review.published_date, '2020-05-04T00:00:00.000000+00:00')

View file

@ -8,7 +8,12 @@ class Status(TestCase):
''' we have hecka ways to create statuses '''
def setUp(self):
self.user = models.User.objects.create_user(
'mouse', 'mouse@mouse.mouse', 'mouseword')
'mouse', 'mouse@mouse.mouse', 'mouseword',
local=False,
inbox='https://example.com/user/mouse/inbox',
outbox='https://example.com/user/mouse/outbox',
remote_id='https://example.com/user/mouse'
)
def test_create_status(self):
@ -21,45 +26,3 @@ class Status(TestCase):
self.user, content, reply_parent=status)
self.assertEqual(reply.content, content)
self.assertEqual(reply.reply_parent, status)
def test_create_status_from_activity(self):
book = models.Edition.objects.create(title='Example Edition')
review = status_builder.create_review(
self.user, book, 'review name', 'content', 5)
activity = {
'id': 'https://example.com/user/mouse/status/12',
'url': 'https://example.com/user/mouse/status/12',
'inReplyTo': review.remote_id,
'published': '2020-05-10T02:15:59.635557+00:00',
'attributedTo': 'https://example.com/user/mouse',
'to': [
'https://www.w3.org/ns/activitystreams#Public'
],
'cc': [
'https://example.com/user/mouse/followers'
],
'sensitive': False,
'content': 'reply to status',
'type': 'Note',
'attachment': [],
'replies': {
'id': 'https://example.com/user/mouse/status/12/replies',
'type': 'Collection',
'first': {
'type': 'CollectionPage',
'next': 'https://example.com/user/mouse/status/12/replies?only_other_accounts=true&page=true',
'partOf': 'https://example.com/user/mouse/status/12/replies',
'items': []
}
}
}
status = status_builder.create_status_from_activity(
self.user, activity)
self.assertEqual(status.reply_parent, review)
self.assertEqual(status.content, 'reply to status')
self.assertEqual(
status.published_date,
'2020-05-10T02:15:59.635557+00:00'
)

View file

@ -0,0 +1,59 @@
import json
import pathlib
from django.test import TestCase
from fedireads import models, incoming
class Favorite(TestCase):
''' not too much going on in the books model but here we are '''
def setUp(self):
self.remote_user = models.User.objects.create_user(
'rat', 'rat@rat.com', 'ratword',
local=False,
remote_id='https://example.com/users/rat',
inbox='https://example.com/users/rat/inbox',
outbox='https://example.com/users/rat/outbox',
)
self.local_user = models.User.objects.create_user(
'mouse', 'mouse@mouse.com', 'mouseword',
remote_id='http://local.com/user/mouse')
self.status = models.Status.objects.create(
user=self.local_user,
content='Test status',
remote_id='http://local.com/status/1',
)
datafile = pathlib.Path(__file__).parent.joinpath(
'data/ap_user.json'
)
self.user_data = json.loads(datafile.read_bytes())
def test_handle_favorite(self):
activity = {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': 'http://example.com/activity/1',
'type': 'Create',
'actor': 'https://example.com/users/rat',
'published': 'Mon, 25 May 2020 19:31:20 GMT',
'to': ['https://example.com/user/rat/followers'],
'cc': ['https://www.w3.org/ns/activitystreams#Public'],
'object': {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': 'http://example.com/fav/1',
'type': 'Like',
'actor': 'https://example.com/users/rat',
'object': 'http://local.com/status/1',
},
'signature': {}
}
result = incoming.handle_favorite(activity)
fav = models.Favorite.objects.get(remote_id='http://example.com/fav/1')
self.assertEqual(fav.status, self.status)
self.assertEqual(fav.remote_id, 'http://example.com/fav/1')
self.assertEqual(fav.user, self.remote_user)

View file

@ -1,6 +1,6 @@
from django.test import TestCase
import json
import pathlib
from django.test import TestCase
from fedireads import models, remote_user
@ -9,29 +9,62 @@ class RemoteUser(TestCase):
''' not too much going on in the books model but here we are '''
def setUp(self):
self.remote_user = models.User.objects.create_user(
'mouse', 'mouse@mouse.com', 'mouseword',
'rat', 'rat@rat.com', 'ratword',
local=False,
remote_id='https://example.com/users/mouse',
inbox='https://example.com/users/mouse/inbox',
outbox='https://example.com/users/mouse/outbox',
remote_id='https://example.com/users/rat',
inbox='https://example.com/users/rat/inbox',
outbox='https://example.com/users/rat/outbox',
)
datafile = pathlib.Path(__file__).parent.joinpath(
'data/ap_user.json'
)
self.user_data = json.loads(datafile.read_bytes())
def test_get_remote_user(self):
actor = 'https://example.com/users/mouse'
actor = 'https://example.com/users/rat'
user = remote_user.get_or_create_remote_user(actor)
self.assertEqual(user, self.remote_user)
def test_create_remote_user(self):
datafile = pathlib.Path(__file__).parent.joinpath('data/ap_user.json')
data = json.loads(datafile.read_bytes())
user = remote_user.create_remote_user(data)
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, data['publicKey']['publicKeyPem'])
self.assertEqual(
user.public_key,
self.user_data['publicKey']['publicKeyPem']
)
self.assertEqual(user.local, False)
self.assertEqual(user.fedireads_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)

View file

@ -10,12 +10,17 @@ from django.test import TestCase, Client
from django.utils.http import http_date
from fedireads.models import User
from fedireads.activitypub import get_follow_request
from fedireads.activitypub import Follow
from fedireads.settings import DOMAIN
from fedireads.signatures import create_key_pair, make_signature, make_digest
def get_follow_data(follower, followee):
return json.dumps(get_follow_request(follower, followee)).encode('utf-8')
follow_activity = Follow(
id='https://test.com/user/follow/id',
actor=follower.remote_id,
object=followee.remote_id,
).serialize()
return json.dumps(follow_activity)
Sender = namedtuple('Sender', ('remote_id', 'private_key', 'public_key'))