kopia lustrzana https://gitlab.com/jaywink/federation
Fix tests and other issues tests revealed
rodzic
ce00a6045d
commit
63b591db46
|
@ -43,6 +43,7 @@ def get_outbound_entity(entity: BaseEntity, private_key):
|
|||
elif entity.entity_type == 'Share':
|
||||
outbound = models.Announce.from_base(entity)
|
||||
outbound.activity = models.Undo
|
||||
outbound._required.remove('id')
|
||||
elif entity.entity_type == 'Profile':
|
||||
outbound = models.Delete.from_base(entity)
|
||||
elif cls == Share:
|
||||
|
|
|
@ -5,6 +5,7 @@ import logging
|
|||
from typing import List, Callable, Dict, Union, Optional
|
||||
import uuid
|
||||
|
||||
import bleach
|
||||
from calamus import fields
|
||||
from calamus.schema import JsonLDAnnotation, JsonLDSchema, JsonLDSchemaOpts
|
||||
from calamus.utils import normalize_value
|
||||
|
@ -15,8 +16,9 @@ from pyld import jsonld
|
|||
import requests_cache as rc
|
||||
|
||||
from federation.entities.activitypub.constants import CONTEXT, NAMESPACE_PUBLIC
|
||||
from federation.entities.mixins import BaseEntity
|
||||
from federation.entities.mixins import BaseEntity, RawContentMixin
|
||||
from federation.entities.utils import get_base_attributes
|
||||
from federation.outbound import handle_send
|
||||
from federation.types import UserType, ReceiverVariant
|
||||
from federation.utils.activitypub import retrieve_and_parse_document, retrieve_and_parse_profile
|
||||
from federation.utils.text import with_slash, validate_handle
|
||||
|
@ -199,15 +201,20 @@ class MixedField(fields.Nested):
|
|||
isinstance(value, list) and len(value) > 0 and isinstance(value[0], str)):
|
||||
return self.iri._serialize(value, attr, obj, **kwargs)
|
||||
else:
|
||||
value = value[0] if isinstance(value, list) and len(value) > 0 else value
|
||||
value = value[0] if isinstance(value, list) and len(value) == 1 else value
|
||||
if isinstance(value, list) and len(value) == 0: value = None
|
||||
return super()._serialize(value, attr, obj, **kwargs)
|
||||
|
||||
def _deserialize(self, value, attr, data, **kwargs):
|
||||
print(attr, value, type(value))
|
||||
# this is just so the ACTIVITYPUB_POST_OBJECT_IMAGES test payload passes
|
||||
if len(value) == 0: return value
|
||||
|
||||
if isinstance(value, list) and value[0] == {}: return {}
|
||||
if isinstance(value, list):
|
||||
if value[0] == {}: return {}
|
||||
else:
|
||||
value = [value]
|
||||
|
||||
|
||||
ret = []
|
||||
for item in value:
|
||||
|
@ -258,13 +265,13 @@ class Object(BaseEntity, metaclass=JsonLDAnnotation):
|
|||
also_known_as = IRI(as2.alsoKnownAs)
|
||||
icon = MixedField(as2.icon, nested='ImageSchema')
|
||||
image = MixedField(as2.image, nested='ImageSchema')
|
||||
tag_list = MixedField(as2.tag, nested=['HashtagSchema','MentionSchema','PropertyValueSchema','EmojiSchema'])
|
||||
tag_objects = MixedField(as2.tag, nested=['HashtagSchema','MentionSchema','PropertyValueSchema','EmojiSchema'], many=True)
|
||||
attachment = fields.Nested(as2.attachment, nested=['ImageSchema', 'AudioSchema', 'DocumentSchema','PropertyValueSchema','IdentityProofSchema'], many=True)
|
||||
content_map = LanguageMap(as2.content) # language maps are not implemented in calamus
|
||||
context = IRI(as2.context)
|
||||
guid = fields.String(diaspora.guid)
|
||||
name = fields.String(as2.name)
|
||||
generator = MixedField(as2.generator, nested='ServiceSchema')
|
||||
generator = MixedField(as2.generator, nested=['ApplicationSchema','ServiceSchema'])
|
||||
created_at = fields.DateTime(as2.published, add_value_types=True)
|
||||
replies = MixedField(as2.replies, nested=['CollectionSchema','OrderedCollectionSchema'])
|
||||
signature = MixedField(sec.signature, nested = 'SignatureSchema')
|
||||
|
@ -273,7 +280,6 @@ class Object(BaseEntity, metaclass=JsonLDAnnotation):
|
|||
to = IRI(as2.to)
|
||||
cc = IRI(as2.cc)
|
||||
media_type = fields.String(as2.mediaType)
|
||||
sensitive = fields.Boolean(as2.sensitive)
|
||||
source = CompactedDict(as2.source)
|
||||
|
||||
# The following properties are defined by some platforms, but are not implemented yet
|
||||
|
@ -287,6 +293,7 @@ class Object(BaseEntity, metaclass=JsonLDAnnotation):
|
|||
|
||||
def to_as2(self):
|
||||
obj = self.activity if isinstance(self.activity, Activity) else self
|
||||
print('to_as2', obj, getattr(obj, 'tag_objects', None))
|
||||
return jsonld.compact(obj.dump(), CONTEXT)
|
||||
|
||||
@classmethod
|
||||
|
@ -379,7 +386,7 @@ class Object(BaseEntity, metaclass=JsonLDAnnotation):
|
|||
|
||||
@post_dump
|
||||
def sanitize(self, data, **kwargs):
|
||||
return {k: v for k,v in data.items() if v}
|
||||
return {k: v for k,v in data.items() if v or isinstance(v, bool)}
|
||||
|
||||
class Home(metaclass=JsonLDAnnotation):
|
||||
country_name = fields.String(fields.IRIReference("http://www.w3.org/2006/vcard/ns#","country-name"))
|
||||
|
@ -392,8 +399,11 @@ class Home(metaclass=JsonLDAnnotation):
|
|||
|
||||
class NormalizedList(fields.List):
|
||||
def _deserialize(self,value, attr, data, **kwargs):
|
||||
print('List', attr, value)
|
||||
value = normalize_value(value)
|
||||
return super()._deserialize(value,attr,data,**kwargs)
|
||||
ret = super()._deserialize(value,attr,data,**kwargs)
|
||||
print('List after', ret)
|
||||
return ret
|
||||
|
||||
|
||||
class Collection(Object):
|
||||
|
@ -435,7 +445,7 @@ class OrderedCollectionPage(OrderedCollection, CollectionPage):
|
|||
# AP defines [Ii]mage and [Aa]udio objects/properties, but only a Video object
|
||||
# seen with Peertube payloads only so far
|
||||
class Document(Object):
|
||||
inline = fields.Boolean(pyfed.inlineImage)
|
||||
inline = fields.Boolean(pyfed.inlineImage, default=False)
|
||||
height = Integer(as2.height, flavor=xsd.nonNegativeInteger, add_value_types=True)
|
||||
width = Integer(as2.width, flavor=xsd.nonNegativeInteger, add_value_types=True)
|
||||
blurhash = fields.String(toot.blurhash)
|
||||
|
@ -490,9 +500,18 @@ class Link(metaclass=JsonLDAnnotation):
|
|||
# Not implemented yet
|
||||
#preview : variable type?
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
for key, value in kwargs.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
class Meta:
|
||||
rdf_type = as2.Link
|
||||
|
||||
@post_dump
|
||||
def noid(self, data, **kwargs):
|
||||
if data['@id'].startswith('_:'): data.pop('@id')
|
||||
return data
|
||||
|
||||
@post_load
|
||||
def make_instance(self, data, **kwargs):
|
||||
data.pop('@id', None)
|
||||
|
@ -536,9 +555,9 @@ class Emoji(Object):
|
|||
class Person(Object, base.Profile):
|
||||
id = fields.Id()
|
||||
inbox = IRI(ldp.inbox)
|
||||
outbox = IRI(as2.outbox, dump_derived={'fmt': '{id}outbox/', 'fields': ['id']})
|
||||
following = IRI(as2.following, dump_derived={'fmt': '{id}following/', 'fields': ['id']})
|
||||
followers = IRI(as2.followers, dump_derived={'fmt': '{id}followers/', 'fields': ['id']})
|
||||
outbox = IRI(as2.outbox, dump_derived={'fmt': '{id}/outbox/', 'fields': ['id']})
|
||||
following = IRI(as2.following, dump_derived={'fmt': '{id}/following/', 'fields': ['id']})
|
||||
followers = IRI(as2.followers, dump_derived={'fmt': '{id}/followers/', 'fields': ['id']})
|
||||
username = fields.String(as2.preferredUsername)
|
||||
endpoints = CompactedDict(as2.endpoints)
|
||||
shared_inbox = IRI(as2.sharedInbox) # misskey adds this
|
||||
|
@ -578,6 +597,10 @@ class Person(Object, base.Profile):
|
|||
super().__init__(*args, **kwargs)
|
||||
self._allowed_children += (PropertyValue, IdentityProof)
|
||||
|
||||
def to_as2(self):
|
||||
self.id = self.id.rstrip('/') # TODO: sort out the trailing / business
|
||||
return super().to_as2()
|
||||
|
||||
@property
|
||||
def inboxes(self):
|
||||
if self._inboxes: return self._inboxes
|
||||
|
@ -611,7 +634,8 @@ class Person(Object, base.Profile):
|
|||
@public_key.setter
|
||||
def public_key(self, value):
|
||||
self._public_key = value
|
||||
self.public_key_dict = {'id': self.id+'#main-key', 'owner': self.id, 'publicKeyPem': value}
|
||||
id_ = self.id.rstrip('/')
|
||||
self.public_key_dict = {'id': id_+'#main-key', 'owner': id_, 'publicKeyPem': value}
|
||||
|
||||
@property
|
||||
def image_urls(self):
|
||||
|
@ -667,28 +691,38 @@ class Service(Person):
|
|||
rdf_type = as2.Service
|
||||
|
||||
|
||||
class Application(Person):
|
||||
class Meta:
|
||||
rdf_type = as2.Application
|
||||
|
||||
|
||||
# The to_base method is used to handle cases where an AP object type matches multiple
|
||||
# classes depending on the existence/value of specific propertie(s) or
|
||||
# when the same class is used both as an object or an activity or
|
||||
# when a property can't be directly deserialized from the payload.
|
||||
# calamus Nested field can't handle using the same model
|
||||
# or the same type in multiple schemas
|
||||
class Note(Object):
|
||||
class Note(Object, RawContentMixin):
|
||||
id = fields.Id()
|
||||
actor_id = IRI(as2.attributedTo)
|
||||
target_id = IRI(as2.inReplyTo)
|
||||
conversation = fields.RawJsonLD(ostatus.conversation)
|
||||
in_reply_to_atom_uri = IRI(ostatus.inReplyToAtomUri)
|
||||
sensitive = fields.Boolean(as2.sensitive, default=False)
|
||||
summary = fields.String(as2.summary)
|
||||
url = IRI(as2.url)
|
||||
_raw_content = None
|
||||
__children = []
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.tag_objects = [] # mutable objects...
|
||||
super().__init__(*args, **kwargs)
|
||||
self.extract_mentions()
|
||||
self._allowed_children += (base.Image, base.Audio, base.Video)
|
||||
|
||||
def to_as2(self):
|
||||
self.sensitive = 'nsfw' in self.tags
|
||||
|
||||
if self.activity_id:
|
||||
activity = Create
|
||||
if hasattr(self, 'times'):
|
||||
|
@ -727,6 +761,9 @@ class Note(Object):
|
|||
) for image in self.embedded_images
|
||||
]
|
||||
self.extract_mentions()
|
||||
self.content_map = {'orig': self.rendered_content}
|
||||
self.add_object_mentions()
|
||||
self.add_object_tags()
|
||||
|
||||
def post_receive(self) -> None:
|
||||
"""
|
||||
|
@ -752,11 +789,10 @@ class Note(Object):
|
|||
skip_tags=["code", "pre"],
|
||||
)
|
||||
|
||||
def add_object_tags(self) -> List[Dict]:
|
||||
def add_object_tags(self) -> None:
|
||||
"""
|
||||
Populate tags to the object.tag list.
|
||||
"""
|
||||
tags = []
|
||||
try:
|
||||
config = get_configuration()
|
||||
except ImportError:
|
||||
|
@ -767,14 +803,24 @@ class Note(Object):
|
|||
else:
|
||||
tags_path = None
|
||||
for tag in self.tags:
|
||||
_tag = {
|
||||
'type': 'Hashtag',
|
||||
'name': f'#{tag}',
|
||||
}
|
||||
_tag = Hashtag(name=f'#{tag}')
|
||||
if tags_path:
|
||||
_tag["href"] = tags_path.replace(":tag:", tag)
|
||||
tags.append(_tag)
|
||||
return tags
|
||||
_tag.href = tags_path.replace(":tag:", tag)
|
||||
self.tag_objects.append(_tag)
|
||||
|
||||
def add_object_mentions(self) -> None:
|
||||
"""
|
||||
Populate mentions to the object.tag list.
|
||||
"""
|
||||
if len(self._mentions):
|
||||
mentions = list(self._mentions)
|
||||
mentions.sort()
|
||||
for mention in mentions:
|
||||
if mention.startswith("http"):
|
||||
self.tag_objects.append(Mention(href=mention, name=mention))
|
||||
elif validate_handle(mention):
|
||||
# Look up via WebFinger
|
||||
self.tag_objects.append(Mention(href=mention, name=mention)) # TODO need to implement fetch via webfinger for AP handles first
|
||||
|
||||
def extract_mentions(self):
|
||||
"""
|
||||
|
@ -782,10 +828,9 @@ class Note(Object):
|
|||
"""
|
||||
super().extract_mentions()
|
||||
|
||||
if getattr(self, 'tag_list', None):
|
||||
from federation.entities.activitypub.models import Mention # Circulars
|
||||
tag_list = self.tag_list if isinstance(self.tag_list, list) else [self.tag_list]
|
||||
for tag in tag_list:
|
||||
if getattr(self, 'tag_objects', None):
|
||||
tag_objects = self.tag_objects if isinstance(self.tag_objects, list) else [self.tag_objects]
|
||||
for tag in tag_objects:
|
||||
if isinstance(tag, Mention):
|
||||
self._mentions.add(tag.href)
|
||||
|
||||
|
@ -817,7 +862,6 @@ class Note(Object):
|
|||
self._raw_content = value
|
||||
if self._media_type == 'text/markdown':
|
||||
self.source = {'content': value, 'mediaType': self._media_type}
|
||||
self.content_map = {'orig': self.rendered_content}
|
||||
|
||||
@property
|
||||
def _children(self):
|
||||
|
@ -1011,20 +1055,22 @@ class Follow(Activity, base.Follow):
|
|||
|
||||
class Meta:
|
||||
rdf_type = as2.Follow
|
||||
exclude = ('created_at',)
|
||||
|
||||
|
||||
class Announce(Activity, base.Share):
|
||||
id = fields.Id(default="unused") # needed for validation with undo
|
||||
id = fields.Id()
|
||||
target_id = IRI(as2.object)
|
||||
|
||||
def to_as2(self):
|
||||
if isinstance(self.activity, type):
|
||||
self.activity = self.activity(
|
||||
activity_id = f"{self.actor_id}#share-{uuid.uuid4()}",
|
||||
activity_id = self.activity_id if self.activity_id else f"{self.actor_id}#share-{uuid.uuid4()}",
|
||||
actor_id = self.actor_id,
|
||||
created_at = self.created_at,
|
||||
object_ = self.target_id
|
||||
object_ = self
|
||||
)
|
||||
self.id = f"{self.target_id}"
|
||||
|
||||
return super().to_as2()
|
||||
|
||||
|
@ -1051,7 +1097,7 @@ class Tombstone(Object, base.Retraction):
|
|||
def to_as2(self):
|
||||
if not isinstance(self.activity, type): return None
|
||||
self.activity = self.activity(
|
||||
activity_id = f"{self.actor_id}#delete-{uuid.uuid4()}",
|
||||
activity_id = self.activity_id,
|
||||
actor_id = self.actor_id,
|
||||
created_at = self.created_at,
|
||||
object_ = self,
|
||||
|
@ -1096,6 +1142,7 @@ class Accept(Create, base.Accept):
|
|||
|
||||
class Meta:
|
||||
rdf_type = as2.Accept
|
||||
exclude = ('created_at',)
|
||||
|
||||
|
||||
class Delete(Create, base.Retraction):
|
||||
|
@ -1117,6 +1164,7 @@ class Update(Create):
|
|||
class Undo(Create):
|
||||
class Meta:
|
||||
rdf_type = as2.Undo
|
||||
exclude = ('created_at',)
|
||||
|
||||
|
||||
class View(Create):
|
||||
|
@ -1241,7 +1289,7 @@ def model_to_objects(payload):
|
|||
try:
|
||||
entity = model.schema().load(payload)
|
||||
except (KeyError, jsonld.JsonLdError, exceptions.ValidationError) as exc : # Just give up for now. This must be made robust
|
||||
logger.error(f"Error parsing jsonld payload ({exc})")
|
||||
logger.error(f"Error parsing jsonld payload ({exc})")
|
||||
return None
|
||||
|
||||
if isinstance(getattr(entity, 'object_', None), Object):
|
||||
|
|
|
@ -2,6 +2,7 @@ import copy
|
|||
import importlib
|
||||
import json
|
||||
import logging
|
||||
from pprint import pprint
|
||||
import traceback
|
||||
from typing import List, Dict, Union
|
||||
|
||||
|
@ -357,8 +358,11 @@ def handle_send(
|
|||
# Do actual sending
|
||||
for payload in payloads:
|
||||
for url in payload["urls"]:
|
||||
print(url, payload["payload"])
|
||||
continue
|
||||
#try:
|
||||
# pprint(json.loads(payload["payload"]))
|
||||
#except:
|
||||
# pass
|
||||
#continue
|
||||
try:
|
||||
# TODO send_document and fetch_document need to handle rate limits
|
||||
send_document(
|
||||
|
|
|
@ -4,9 +4,8 @@ from pprint import pprint
|
|||
# noinspection PyPackageRequirements
|
||||
from Crypto.PublicKey.RSA import RsaKey
|
||||
|
||||
from federation.entities.activitypub.constants import (
|
||||
CONTEXTS_DEFAULT, CONTEXT_MANUALLY_APPROVES_FOLLOWERS, CONTEXT_LD_SIGNATURES, CONTEXT_DIASPORA)
|
||||
from federation.entities.activitypub.entities import ActivitypubAccept
|
||||
from federation.entities.activitypub.constants import CONTEXT
|
||||
from federation.entities.activitypub.models import Accept
|
||||
from federation.tests.fixtures.keys import PUBKEY
|
||||
from federation.types import UserType
|
||||
|
||||
|
@ -15,12 +14,11 @@ class TestEntitiesConvertToAS2:
|
|||
def test_accept_to_as2(self, activitypubaccept):
|
||||
result = activitypubaccept.to_as2()
|
||||
assert result == {
|
||||
"@context": CONTEXTS_DEFAULT,
|
||||
"@context": CONTEXT,
|
||||
"id": "https://localhost/accept",
|
||||
"type": "Accept",
|
||||
"actor": "https://localhost/profile",
|
||||
"object": {
|
||||
"@context": CONTEXTS_DEFAULT,
|
||||
"id": "https://localhost/follow",
|
||||
"type": "Follow",
|
||||
"actor": "https://localhost/profile",
|
||||
|
@ -28,10 +26,10 @@ class TestEntitiesConvertToAS2:
|
|||
},
|
||||
}
|
||||
|
||||
def test_accounce_to_as2(self, activitypubannounce):
|
||||
def test_announce_to_as2(self, activitypubannounce):
|
||||
result = activitypubannounce.to_as2()
|
||||
assert result == {
|
||||
"@context": CONTEXTS_DEFAULT,
|
||||
"@context": CONTEXT,
|
||||
"id": "http://127.0.0.1:8000/post/123456/#create",
|
||||
"type": "Announce",
|
||||
"actor": "http://127.0.0.1:8000/profile/123456/",
|
||||
|
@ -40,15 +38,10 @@ class TestEntitiesConvertToAS2:
|
|||
}
|
||||
|
||||
def test_comment_to_as2(self, activitypubcomment):
|
||||
activitypubcomment.pre_send()
|
||||
result = activitypubcomment.to_as2()
|
||||
assert result == {
|
||||
'@context': [
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
{"pyfed": "https://docs.jasonrobinson.me/ns/python-federation#"},
|
||||
{'Hashtag': 'as:Hashtag'},
|
||||
'https://w3id.org/security/v1',
|
||||
{'sensitive': 'as:sensitive'},
|
||||
],
|
||||
'@context': CONTEXT,
|
||||
'type': 'Create',
|
||||
'id': 'http://127.0.0.1:8000/post/123456/#create',
|
||||
'actor': 'http://127.0.0.1:8000/profile/123456/',
|
||||
|
@ -60,9 +53,6 @@ class TestEntitiesConvertToAS2:
|
|||
'published': '2019-04-27T00:00:00',
|
||||
'inReplyTo': 'http://127.0.0.1:8000/post/012345/',
|
||||
'sensitive': False,
|
||||
'summary': None,
|
||||
'tag': [],
|
||||
'url': '',
|
||||
'source': {
|
||||
'content': 'raw_content',
|
||||
'mediaType': 'text/markdown',
|
||||
|
@ -73,15 +63,10 @@ class TestEntitiesConvertToAS2:
|
|||
|
||||
def test_comment_to_as2__url_in_raw_content(self, activitypubcomment):
|
||||
activitypubcomment.raw_content = 'raw_content http://example.com'
|
||||
activitypubcomment.pre_send()
|
||||
result = activitypubcomment.to_as2()
|
||||
assert result == {
|
||||
'@context': [
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
{"pyfed": "https://docs.jasonrobinson.me/ns/python-federation#"},
|
||||
{'Hashtag': 'as:Hashtag'},
|
||||
'https://w3id.org/security/v1',
|
||||
{'sensitive': 'as:sensitive'},
|
||||
],
|
||||
'@context': CONTEXT,
|
||||
'type': 'Create',
|
||||
'id': 'http://127.0.0.1:8000/post/123456/#create',
|
||||
'actor': 'http://127.0.0.1:8000/profile/123456/',
|
||||
|
@ -94,9 +79,6 @@ class TestEntitiesConvertToAS2:
|
|||
'published': '2019-04-27T00:00:00',
|
||||
'inReplyTo': 'http://127.0.0.1:8000/post/012345/',
|
||||
'sensitive': False,
|
||||
'summary': None,
|
||||
'tag': [],
|
||||
'url': '',
|
||||
'source': {
|
||||
'content': 'raw_content http://example.com',
|
||||
'mediaType': 'text/markdown',
|
||||
|
@ -108,7 +90,7 @@ class TestEntitiesConvertToAS2:
|
|||
def test_follow_to_as2(self, activitypubfollow):
|
||||
result = activitypubfollow.to_as2()
|
||||
assert result == {
|
||||
"@context": CONTEXTS_DEFAULT,
|
||||
"@context": CONTEXT,
|
||||
"id": "https://localhost/follow",
|
||||
"type": "Follow",
|
||||
"actor": "https://localhost/profile",
|
||||
|
@ -119,7 +101,7 @@ class TestEntitiesConvertToAS2:
|
|||
result = activitypubundofollow.to_as2()
|
||||
result["object"]["id"] = "https://localhost/follow" # Real object will have a random UUID postfix here
|
||||
assert result == {
|
||||
"@context": CONTEXTS_DEFAULT,
|
||||
"@context": CONTEXT,
|
||||
"id": "https://localhost/undo",
|
||||
"type": "Undo",
|
||||
"actor": "https://localhost/profile",
|
||||
|
@ -132,15 +114,10 @@ class TestEntitiesConvertToAS2:
|
|||
}
|
||||
|
||||
def test_post_to_as2(self, activitypubpost):
|
||||
activitypubpost.pre_send()
|
||||
result = activitypubpost.to_as2()
|
||||
assert result == {
|
||||
'@context': [
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
{"pyfed": "https://docs.jasonrobinson.me/ns/python-federation#"},
|
||||
{'Hashtag': 'as:Hashtag'},
|
||||
'https://w3id.org/security/v1',
|
||||
{'sensitive': 'as:sensitive'},
|
||||
],
|
||||
'@context': CONTEXT,
|
||||
'type': 'Create',
|
||||
'id': 'http://127.0.0.1:8000/post/123456/#create',
|
||||
'actor': 'http://127.0.0.1:8000/profile/123456/',
|
||||
|
@ -150,11 +127,7 @@ class TestEntitiesConvertToAS2:
|
|||
'attributedTo': 'http://127.0.0.1:8000/profile/123456/',
|
||||
'content': '<h1>raw_content</h1>',
|
||||
'published': '2019-04-27T00:00:00',
|
||||
'inReplyTo': None,
|
||||
'sensitive': False,
|
||||
'summary': None,
|
||||
'tag': [],
|
||||
'url': '',
|
||||
'source': {
|
||||
'content': '# raw_content',
|
||||
'mediaType': 'text/markdown',
|
||||
|
@ -167,13 +140,7 @@ class TestEntitiesConvertToAS2:
|
|||
activitypubpost_mentions.pre_send()
|
||||
result = activitypubpost_mentions.to_as2()
|
||||
assert result == {
|
||||
'@context': [
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
{"pyfed": "https://docs.jasonrobinson.me/ns/python-federation#"},
|
||||
{'Hashtag': 'as:Hashtag'},
|
||||
'https://w3id.org/security/v1',
|
||||
{'sensitive': 'as:sensitive'},
|
||||
],
|
||||
'@context': CONTEXT,
|
||||
'type': 'Create',
|
||||
'id': 'http://127.0.0.1:8000/post/123456/#create',
|
||||
'actor': 'http://127.0.0.1:8000/profile/123456/',
|
||||
|
@ -185,9 +152,7 @@ class TestEntitiesConvertToAS2:
|
|||
'href="http://localhost.local/someone" rel="nofollow" target="_blank">'
|
||||
'<span>Bob Bobértson</span></a></p>',
|
||||
'published': '2019-04-27T00:00:00',
|
||||
'inReplyTo': None,
|
||||
'sensitive': False,
|
||||
'summary': None,
|
||||
'tag': [
|
||||
{
|
||||
"type": "Mention",
|
||||
|
@ -210,7 +175,6 @@ class TestEntitiesConvertToAS2:
|
|||
"name": "someone@localhost.local",
|
||||
},
|
||||
],
|
||||
'url': '',
|
||||
'source': {
|
||||
'content': '# raw_content\n\n@{someone@localhost.local} @{http://localhost.local/someone}',
|
||||
'mediaType': 'text/markdown',
|
||||
|
@ -220,15 +184,10 @@ class TestEntitiesConvertToAS2:
|
|||
}
|
||||
|
||||
def test_post_to_as2__with_tags(self, activitypubpost_tags):
|
||||
activitypubpost_tags.pre_send()
|
||||
result = activitypubpost_tags.to_as2()
|
||||
assert result == {
|
||||
'@context': [
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
{"pyfed": "https://docs.jasonrobinson.me/ns/python-federation#"},
|
||||
{'Hashtag': 'as:Hashtag'},
|
||||
'https://w3id.org/security/v1',
|
||||
{'sensitive': 'as:sensitive'},
|
||||
],
|
||||
'@context': CONTEXT,
|
||||
'type': 'Create',
|
||||
'id': 'http://127.0.0.1:8000/post/123456/#create',
|
||||
'actor': 'http://127.0.0.1:8000/profile/123456/',
|
||||
|
@ -246,9 +205,7 @@ class TestEntitiesConvertToAS2:
|
|||
'noreferrer nofollow" '
|
||||
'target="_blank">#<span>barfoo</span></a></p>',
|
||||
'published': '2019-04-27T00:00:00',
|
||||
'inReplyTo': None,
|
||||
'sensitive': False,
|
||||
'summary': None,
|
||||
'tag': [
|
||||
{
|
||||
"type": "Hashtag",
|
||||
|
@ -261,7 +218,6 @@ class TestEntitiesConvertToAS2:
|
|||
"name": "#foobar",
|
||||
},
|
||||
],
|
||||
'url': '',
|
||||
'source': {
|
||||
'content': '# raw_content\n#foobar\n#barfoo',
|
||||
'mediaType': 'text/markdown',
|
||||
|
@ -271,15 +227,10 @@ class TestEntitiesConvertToAS2:
|
|||
}
|
||||
|
||||
def test_post_to_as2__with_images(self, activitypubpost_images):
|
||||
activitypubpost_images.pre_send()
|
||||
result = activitypubpost_images.to_as2()
|
||||
assert result == {
|
||||
'@context': [
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
{"pyfed": "https://docs.jasonrobinson.me/ns/python-federation#"},
|
||||
{'Hashtag': 'as:Hashtag'},
|
||||
'https://w3id.org/security/v1',
|
||||
{'sensitive': 'as:sensitive'},
|
||||
],
|
||||
'@context': CONTEXT,
|
||||
'type': 'Create',
|
||||
'id': 'http://127.0.0.1:8000/post/123456/#create',
|
||||
'actor': 'http://127.0.0.1:8000/profile/123456/',
|
||||
|
@ -289,16 +240,11 @@ class TestEntitiesConvertToAS2:
|
|||
'attributedTo': 'http://127.0.0.1:8000/profile/123456/',
|
||||
'content': '<p>raw_content</p>',
|
||||
'published': '2019-04-27T00:00:00',
|
||||
'inReplyTo': None,
|
||||
'sensitive': False,
|
||||
'summary': None,
|
||||
'tag': [],
|
||||
'url': '',
|
||||
'attachment': [
|
||||
{
|
||||
'type': 'Image',
|
||||
'mediaType': 'image/jpeg',
|
||||
'name': '',
|
||||
'url': 'foobar',
|
||||
'pyfed:inlineImage': False,
|
||||
},
|
||||
|
@ -319,16 +265,10 @@ class TestEntitiesConvertToAS2:
|
|||
}
|
||||
|
||||
def test_post_to_as2__with_diaspora_guid(self, activitypubpost_diaspora_guid):
|
||||
activitypubpost_diaspora_guid.pre_send()
|
||||
result = activitypubpost_diaspora_guid.to_as2()
|
||||
assert result == {
|
||||
'@context': [
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
{"pyfed": "https://docs.jasonrobinson.me/ns/python-federation#"},
|
||||
{'Hashtag': 'as:Hashtag'},
|
||||
'https://w3id.org/security/v1',
|
||||
{'sensitive': 'as:sensitive'},
|
||||
{'diaspora': 'https://diasporafoundation.org/ns/'},
|
||||
],
|
||||
'@context': CONTEXT,
|
||||
'type': 'Create',
|
||||
'id': 'http://127.0.0.1:8000/post/123456/#create',
|
||||
'actor': 'http://127.0.0.1:8000/profile/123456/',
|
||||
|
@ -339,11 +279,7 @@ class TestEntitiesConvertToAS2:
|
|||
'attributedTo': 'http://127.0.0.1:8000/profile/123456/',
|
||||
'content': '<p>raw_content</p>',
|
||||
'published': '2019-04-27T00:00:00',
|
||||
'inReplyTo': None,
|
||||
'sensitive': False,
|
||||
'summary': None,
|
||||
'tag': [],
|
||||
'url': '',
|
||||
'source': {
|
||||
'content': 'raw_content',
|
||||
'mediaType': 'text/markdown',
|
||||
|
@ -353,14 +289,10 @@ class TestEntitiesConvertToAS2:
|
|||
}
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@patch("federation.entities.base.fetch_content_type", return_value="image/jpeg")
|
||||
def test_profile_to_as2(self, mock_fetch, activitypubprofile):
|
||||
def test_profile_to_as2(self, activitypubprofile):
|
||||
result = activitypubprofile.to_as2()
|
||||
assert result == {
|
||||
"@context": CONTEXTS_DEFAULT + [
|
||||
CONTEXT_LD_SIGNATURES,
|
||||
CONTEXT_MANUALLY_APPROVES_FOLLOWERS,
|
||||
],
|
||||
"@context": CONTEXT,
|
||||
"endpoints": {
|
||||
"sharedInbox": "https://example.com/public",
|
||||
},
|
||||
|
@ -376,6 +308,7 @@ class TestEntitiesConvertToAS2:
|
|||
"owner": "https://example.com/bob",
|
||||
"publicKeyPem": PUBKEY,
|
||||
},
|
||||
'published': '2022-09-06T00:00:00',
|
||||
"type": "Person",
|
||||
"url": "https://example.com/bob-bobertson",
|
||||
"summary": "foobar",
|
||||
|
@ -383,21 +316,15 @@ class TestEntitiesConvertToAS2:
|
|||
"type": "Image",
|
||||
"url": "urllarge",
|
||||
"mediaType": "image/jpeg",
|
||||
"name": "",
|
||||
"pyfed:inlineImage": False,
|
||||
}
|
||||
}
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@patch("federation.entities.base.fetch_content_type", return_value="image/jpeg")
|
||||
def test_profile_to_as2__with_diaspora_guid(self, mock_fetch, activitypubprofile_diaspora_guid):
|
||||
def test_profile_to_as2__with_diaspora_guid(self, activitypubprofile_diaspora_guid):
|
||||
result = activitypubprofile_diaspora_guid.to_as2()
|
||||
assert result == {
|
||||
"@context": CONTEXTS_DEFAULT + [
|
||||
CONTEXT_LD_SIGNATURES,
|
||||
CONTEXT_MANUALLY_APPROVES_FOLLOWERS,
|
||||
CONTEXT_DIASPORA,
|
||||
],
|
||||
"@context": CONTEXT,
|
||||
"endpoints": {
|
||||
"sharedInbox": "https://example.com/public",
|
||||
},
|
||||
|
@ -415,6 +342,7 @@ class TestEntitiesConvertToAS2:
|
|||
"owner": "https://example.com/bob",
|
||||
"publicKeyPem": PUBKEY,
|
||||
},
|
||||
'published': '2022-09-06T00:00:00',
|
||||
"type": "Person",
|
||||
"url": "https://example.com/bob-bobertson",
|
||||
"summary": "foobar",
|
||||
|
@ -422,7 +350,6 @@ class TestEntitiesConvertToAS2:
|
|||
"type": "Image",
|
||||
"url": "urllarge",
|
||||
"mediaType": "image/jpeg",
|
||||
"name": "",
|
||||
"pyfed:inlineImage": False,
|
||||
}
|
||||
}
|
||||
|
@ -430,10 +357,7 @@ class TestEntitiesConvertToAS2:
|
|||
def test_retraction_to_as2(self, activitypubretraction):
|
||||
result = activitypubretraction.to_as2()
|
||||
assert result == {
|
||||
'@context': [
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
{"pyfed": "https://docs.jasonrobinson.me/ns/python-federation#"},
|
||||
],
|
||||
'@context': CONTEXT,
|
||||
'type': 'Delete',
|
||||
'id': 'http://127.0.0.1:8000/post/123456/#delete',
|
||||
'actor': 'http://127.0.0.1:8000/profile/123456/',
|
||||
|
@ -447,10 +371,7 @@ class TestEntitiesConvertToAS2:
|
|||
def test_retraction_to_as2__announce(self, activitypubretraction_announce):
|
||||
result = activitypubretraction_announce.to_as2()
|
||||
assert result == {
|
||||
'@context': [
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
{"pyfed": "https://docs.jasonrobinson.me/ns/python-federation#"},
|
||||
],
|
||||
'@context': CONTEXT,
|
||||
'type': 'Undo',
|
||||
'id': 'http://127.0.0.1:8000/post/123456/#delete',
|
||||
'actor': 'http://127.0.0.1:8000/profile/123456/',
|
||||
|
@ -463,15 +384,15 @@ class TestEntitiesConvertToAS2:
|
|||
|
||||
|
||||
class TestEntitiesPostReceive:
|
||||
@patch("federation.utils.activitypub.retrieve_and_parse_profile", autospec=True)
|
||||
@patch("federation.entities.activitypub.entities.handle_send", autospec=True)
|
||||
@patch("federation.entities.activitypub.models.retrieve_and_parse_profile", autospec=True)
|
||||
@patch("federation.entities.activitypub.models.handle_send", autospec=True)
|
||||
def test_follow_post_receive__sends_correct_accept_back(
|
||||
self, mock_send, mock_retrieve, activitypubfollow, profile
|
||||
):
|
||||
mock_retrieve.return_value = profile
|
||||
activitypubfollow.post_receive()
|
||||
args, kwargs = mock_send.call_args_list[0]
|
||||
assert isinstance(args[0], ActivitypubAccept)
|
||||
assert isinstance(args[0], Accept)
|
||||
assert args[0].activity_id.startswith("https://example.com/profile#accept-")
|
||||
assert args[0].actor_id == "https://example.com/profile"
|
||||
assert args[0].target_id == "https://localhost/follow"
|
||||
|
@ -485,13 +406,13 @@ class TestEntitiesPostReceive:
|
|||
"public": False,
|
||||
}]
|
||||
|
||||
@patch("federation.entities.activitypub.entities.bleach.linkify", autospec=True)
|
||||
@patch("federation.entities.activitypub.models.bleach.linkify", autospec=True)
|
||||
def test_post_post_receive__linkifies_if_not_markdown(self, mock_linkify, activitypubpost):
|
||||
activitypubpost._media_type = 'text/html'
|
||||
activitypubpost.post_receive()
|
||||
mock_linkify.assert_called_once()
|
||||
|
||||
@patch("federation.entities.activitypub.entities.bleach.linkify", autospec=True)
|
||||
@patch("federation.entities.activitypub.models.bleach.linkify", autospec=True)
|
||||
def test_post_post_receive__skips_linkify_if_markdown(self, mock_linkify, activitypubpost):
|
||||
activitypubpost.post_receive()
|
||||
mock_linkify.assert_not_called()
|
||||
|
|
|
@ -3,9 +3,10 @@ from unittest.mock import patch, Mock
|
|||
|
||||
import pytest
|
||||
|
||||
from federation.entities.activitypub.entities import (
|
||||
ActivitypubFollow, ActivitypubAccept, ActivitypubProfile, ActivitypubPost, ActivitypubComment,
|
||||
ActivitypubRetraction, ActivitypubShare)
|
||||
#from federation.entities.activitypub.entities import (
|
||||
# models.Follow, models.Accept, models.Person, models.Note, models.Note,
|
||||
# models.Delete, models.Announce)
|
||||
import federation.entities.activitypub.models as models
|
||||
from federation.entities.activitypub.mappers import message_to_objects, get_outbound_entity
|
||||
from federation.entities.base import Accept, Follow, Profile, Post, Comment, Image, Share
|
||||
from federation.tests.fixtures.payloads import (
|
||||
|
@ -17,7 +18,7 @@ from federation.types import UserType, ReceiverVariant
|
|||
|
||||
|
||||
class TestActivitypubEntityMappersReceive:
|
||||
@patch.object(ActivitypubFollow, "post_receive", autospec=True)
|
||||
@patch.object(models.Follow, "post_receive", autospec=True)
|
||||
def test_message_to_objects__calls_post_receive_hook(self, mock_post_receive):
|
||||
message_to_objects(ACTIVITYPUB_FOLLOW, "https://example.com/actor")
|
||||
assert mock_post_receive.called
|
||||
|
@ -26,7 +27,7 @@ class TestActivitypubEntityMappersReceive:
|
|||
entities = message_to_objects(ACTIVITYPUB_SHARE, "https://mastodon.social/users/jaywink")
|
||||
assert len(entities) == 1
|
||||
entity = entities[0]
|
||||
assert isinstance(entity, ActivitypubShare)
|
||||
assert isinstance(entity, models.Announce)
|
||||
assert entity.actor_id == "https://mastodon.social/users/jaywink"
|
||||
assert entity.target_id == "https://mastodon.social/users/Gargron/statuses/102559779793316012"
|
||||
assert entity.id == "https://mastodon.social/users/jaywink/statuses/102560701449465612/activity"
|
||||
|
@ -38,7 +39,7 @@ class TestActivitypubEntityMappersReceive:
|
|||
entities = message_to_objects(ACTIVITYPUB_FOLLOW, "https://example.com/actor")
|
||||
assert len(entities) == 1
|
||||
entity = entities[0]
|
||||
assert isinstance(entity, ActivitypubFollow)
|
||||
assert isinstance(entity, models.Follow)
|
||||
assert entity.actor_id == "https://example.com/actor"
|
||||
assert entity.target_id == "https://example.org/actor"
|
||||
assert entity.following is True
|
||||
|
@ -47,7 +48,7 @@ class TestActivitypubEntityMappersReceive:
|
|||
entities = message_to_objects(ACTIVITYPUB_UNDO_FOLLOW, "https://example.com/actor")
|
||||
assert len(entities) == 1
|
||||
entity = entities[0]
|
||||
assert isinstance(entity, ActivitypubFollow)
|
||||
assert isinstance(entity, models.Follow)
|
||||
assert entity.actor_id == "https://example.com/actor"
|
||||
assert entity.target_id == "https://example.org/actor"
|
||||
assert entity.following is False
|
||||
|
@ -65,7 +66,7 @@ class TestActivitypubEntityMappersReceive:
|
|||
entities = message_to_objects(ACTIVITYPUB_POST, "https://diaspodon.fr/users/jaywink")
|
||||
assert len(entities) == 1
|
||||
post = entities[0]
|
||||
assert isinstance(post, ActivitypubPost)
|
||||
assert isinstance(post, models.Note)
|
||||
assert isinstance(post, Post)
|
||||
assert post.raw_content == '<p><span class="h-card"><a class="u-url mention" ' \
|
||||
'href="https://dev.jasonrobinson.me/u/jaywink/">' \
|
||||
|
@ -82,7 +83,7 @@ class TestActivitypubEntityMappersReceive:
|
|||
entities = message_to_objects(ACTIVITYPUB_POST_WITH_TAGS, "https://diaspodon.fr/users/jaywink")
|
||||
assert len(entities) == 1
|
||||
post = entities[0]
|
||||
assert isinstance(post, ActivitypubPost)
|
||||
assert isinstance(post, models.Note)
|
||||
assert isinstance(post, Post)
|
||||
assert post.raw_content == '<p>boom #test</p>'
|
||||
|
||||
|
@ -90,7 +91,7 @@ class TestActivitypubEntityMappersReceive:
|
|||
entities = message_to_objects(ACTIVITYPUB_POST_WITH_MENTIONS, "https://mastodon.social/users/jaywink")
|
||||
assert len(entities) == 1
|
||||
post = entities[0]
|
||||
assert isinstance(post, ActivitypubPost)
|
||||
assert isinstance(post, models.Note)
|
||||
assert isinstance(post, Post)
|
||||
assert len(post._mentions) == 1
|
||||
assert list(post._mentions)[0] == "https://dev3.jasonrobinson.me/u/jaywink/"
|
||||
|
@ -99,7 +100,7 @@ class TestActivitypubEntityMappersReceive:
|
|||
entities = message_to_objects(ACTIVITYPUB_POST_WITH_SOURCE_BBCODE, "https://diaspodon.fr/users/jaywink")
|
||||
assert len(entities) == 1
|
||||
post = entities[0]
|
||||
assert isinstance(post, ActivitypubPost)
|
||||
assert isinstance(post, models.Note)
|
||||
assert isinstance(post, Post)
|
||||
assert post.rendered_content == '<p><span class="h-card"><a class="u-url mention" href="https://dev.jasonrobinson.me/u/jaywink/">' \
|
||||
'@<span>jaywink</span></a></span> boom</p>'
|
||||
|
@ -111,7 +112,7 @@ class TestActivitypubEntityMappersReceive:
|
|||
entities = message_to_objects(ACTIVITYPUB_POST_WITH_SOURCE_MARKDOWN, "https://diaspodon.fr/users/jaywink")
|
||||
assert len(entities) == 1
|
||||
post = entities[0]
|
||||
assert isinstance(post, ActivitypubPost)
|
||||
assert isinstance(post, models.Note)
|
||||
assert isinstance(post, Post)
|
||||
assert post.rendered_content == '<p><span class="h-card"><a href="https://dev.jasonrobinson.me/u/jaywink/" ' \
|
||||
'class="u-url mention">@<span>jaywink</span></a></span> boom</p>'
|
||||
|
@ -126,7 +127,7 @@ class TestActivitypubEntityMappersReceive:
|
|||
entities = message_to_objects(ACTIVITYPUB_POST_IMAGES, "https://mastodon.social/users/jaywink")
|
||||
assert len(entities) == 1
|
||||
post = entities[0]
|
||||
assert isinstance(post, ActivitypubPost)
|
||||
assert isinstance(post, models.Note)
|
||||
# TODO: test video and audio attachment
|
||||
assert len(post._children) == 2
|
||||
photo = post._children[0]
|
||||
|
@ -144,7 +145,7 @@ class TestActivitypubEntityMappersReceive:
|
|||
entities = message_to_objects(ACTIVITYPUB_COMMENT, "https://diaspodon.fr/users/jaywink")
|
||||
assert len(entities) == 1
|
||||
comment = entities[0]
|
||||
assert isinstance(comment, ActivitypubComment)
|
||||
assert isinstance(comment, models.Note)
|
||||
assert isinstance(comment, Comment)
|
||||
assert comment.raw_content == '<p><span class="h-card"><a class="u-url mention" ' \
|
||||
'href="https://dev.jasonrobinson.me/u/jaywink/">' \
|
||||
|
@ -238,7 +239,7 @@ class TestActivitypubEntityMappersReceive:
|
|||
entities = message_to_objects(ACTIVITYPUB_RETRACTION, "https://friendica.feneas.org/profile/jaywink")
|
||||
assert len(entities) == 1
|
||||
entity = entities[0]
|
||||
assert isinstance(entity, ActivitypubRetraction)
|
||||
assert isinstance(entity, models.Delete)
|
||||
assert entity.actor_id == "https://friendica.feneas.org/profile/jaywink"
|
||||
assert entity.target_id == "https://friendica.feneas.org/objects/76158462-165d-3386-aa23-ba2090614385"
|
||||
assert entity.entity_type == "Object"
|
||||
|
@ -247,7 +248,7 @@ class TestActivitypubEntityMappersReceive:
|
|||
entities = message_to_objects(ACTIVITYPUB_RETRACTION_SHARE, "https://mastodon.social/users/jaywink")
|
||||
assert len(entities) == 1
|
||||
entity = entities[0]
|
||||
assert isinstance(entity, ActivitypubRetraction)
|
||||
assert isinstance(entity, models.Announce)
|
||||
assert entity.actor_id == "https://mastodon.social/users/jaywink"
|
||||
assert entity.target_id == "https://mastodon.social/users/jaywink/statuses/102571932479036987/activity"
|
||||
assert entity.entity_type == "Object"
|
||||
|
@ -296,30 +297,30 @@ class TestActivitypubEntityMappersReceive:
|
|||
|
||||
class TestGetOutboundEntity:
|
||||
def test_already_fine_entities_are_returned_as_is(self, private_key):
|
||||
entity = ActivitypubAccept()
|
||||
entity = models.Accept()
|
||||
entity.validate = Mock()
|
||||
assert get_outbound_entity(entity, private_key) == entity
|
||||
entity = ActivitypubFollow()
|
||||
entity = models.Follow()
|
||||
entity.validate = Mock()
|
||||
assert get_outbound_entity(entity, private_key) == entity
|
||||
entity = ActivitypubProfile()
|
||||
entity = models.Person()
|
||||
entity.validate = Mock()
|
||||
assert get_outbound_entity(entity, private_key) == entity
|
||||
|
||||
@patch.object(ActivitypubAccept, "validate", new=Mock())
|
||||
@patch.object(models.Accept, "validate", new=Mock())
|
||||
def test_accept_is_converted_to_activitypubaccept(self, private_key):
|
||||
entity = Accept()
|
||||
assert isinstance(get_outbound_entity(entity, private_key), ActivitypubAccept)
|
||||
assert isinstance(get_outbound_entity(entity, private_key), models.Accept)
|
||||
|
||||
@patch.object(ActivitypubFollow, "validate", new=Mock())
|
||||
@patch.object(models.Follow, "validate", new=Mock())
|
||||
def test_follow_is_converted_to_activitypubfollow(self, private_key):
|
||||
entity = Follow()
|
||||
assert isinstance(get_outbound_entity(entity, private_key), ActivitypubFollow)
|
||||
assert isinstance(get_outbound_entity(entity, private_key), models.Follow)
|
||||
|
||||
@patch.object(ActivitypubProfile, "validate", new=Mock())
|
||||
@patch.object(models.Person, "validate", new=Mock())
|
||||
def test_profile_is_converted_to_activitypubprofile(self, private_key):
|
||||
entity = Profile()
|
||||
assert isinstance(get_outbound_entity(entity, private_key), ActivitypubProfile)
|
||||
assert isinstance(get_outbound_entity(entity, private_key), models.Person)
|
||||
|
||||
def test_entity_is_validated__fail(self, private_key):
|
||||
entity = Share(
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import pytest
|
||||
# noinspection PyPackageRequirements
|
||||
from freezegun import freeze_time
|
||||
from unittest.mock import patch
|
||||
|
||||
from federation.entities.activitypub.entities import (
|
||||
ActivitypubPost, ActivitypubAccept, ActivitypubFollow, ActivitypubProfile, ActivitypubComment,
|
||||
ActivitypubRetraction, ActivitypubShare, ActivitypubImage)
|
||||
from federation.entities.base import Profile, Post
|
||||
from federation.entities.activitypub.mappers import get_outbound_entity
|
||||
import federation.entities.activitypub.models as models
|
||||
from federation.entities.activitypub.models import make_content_class
|
||||
from federation.entities.base import Profile, Post, Comment, Retraction
|
||||
from federation.entities.diaspora.entities import (
|
||||
DiasporaPost, DiasporaComment, DiasporaLike, DiasporaProfile, DiasporaRetraction,
|
||||
DiasporaContact, DiasporaReshare,
|
||||
|
@ -18,8 +19,8 @@ from federation.tests.fixtures.payloads import DIASPORA_PUBLIC_PAYLOAD
|
|||
@pytest.fixture
|
||||
def activitypubannounce():
|
||||
with freeze_time("2019-08-05"):
|
||||
return ActivitypubShare(
|
||||
activity_id="http://127.0.0.1:8000/post/123456/#create",
|
||||
return models.Announce(
|
||||
id="http://127.0.0.1:8000/post/123456/#create",
|
||||
actor_id="http://127.0.0.1:8000/profile/123456/",
|
||||
target_id="http://127.0.0.1:8000/post/012345/",
|
||||
)
|
||||
|
@ -28,7 +29,7 @@ def activitypubannounce():
|
|||
@pytest.fixture
|
||||
def activitypubcomment():
|
||||
with freeze_time("2019-04-27"):
|
||||
return ActivitypubComment(
|
||||
obj = make_content_class(Comment)(
|
||||
raw_content="raw_content",
|
||||
public=True,
|
||||
provider_display_name="Socialhome",
|
||||
|
@ -37,11 +38,13 @@ def activitypubcomment():
|
|||
actor_id=f"http://127.0.0.1:8000/profile/123456/",
|
||||
target_id="http://127.0.0.1:8000/post/012345/",
|
||||
)
|
||||
obj.times={'edited':False, 'created':obj.created_at}
|
||||
return obj
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def activitypubfollow():
|
||||
return ActivitypubFollow(
|
||||
return models.Follow(
|
||||
activity_id="https://localhost/follow",
|
||||
actor_id="https://localhost/profile",
|
||||
target_id="https://example.com/profile",
|
||||
|
@ -50,18 +53,18 @@ def activitypubfollow():
|
|||
|
||||
@pytest.fixture
|
||||
def activitypubaccept(activitypubfollow):
|
||||
return ActivitypubAccept(
|
||||
return models.Accept(
|
||||
activity_id="https://localhost/accept",
|
||||
actor_id="https://localhost/profile",
|
||||
target_id="https://example.com/follow/1234",
|
||||
object=activitypubfollow.to_as2(),
|
||||
object_=activitypubfollow,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def activitypubpost():
|
||||
with freeze_time("2019-04-27"):
|
||||
return ActivitypubPost(
|
||||
obj = make_content_class(Post)(
|
||||
raw_content="# raw_content",
|
||||
public=True,
|
||||
provider_display_name="Socialhome",
|
||||
|
@ -70,12 +73,14 @@ def activitypubpost():
|
|||
actor_id=f"http://127.0.0.1:8000/profile/123456/",
|
||||
_media_type="text/markdown",
|
||||
)
|
||||
obj.times={'edited':False, 'created':obj.created_at}
|
||||
return obj
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def activitypubpost_diaspora_guid():
|
||||
with freeze_time("2019-04-27"):
|
||||
return ActivitypubPost(
|
||||
obj = make_content_class(Post)(
|
||||
raw_content="raw_content",
|
||||
public=True,
|
||||
provider_display_name="Socialhome",
|
||||
|
@ -84,12 +89,14 @@ def activitypubpost_diaspora_guid():
|
|||
actor_id=f"http://127.0.0.1:8000/profile/123456/",
|
||||
guid="totallyrandomguid",
|
||||
)
|
||||
obj.times={'edited':False, 'created':obj.created_at}
|
||||
return obj
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def activitypubpost_images():
|
||||
with freeze_time("2019-04-27"):
|
||||
return ActivitypubPost(
|
||||
obj = make_content_class(Post)(
|
||||
raw_content="raw_content",
|
||||
public=True,
|
||||
provider_display_name="Socialhome",
|
||||
|
@ -97,16 +104,18 @@ def activitypubpost_images():
|
|||
activity_id=f"http://127.0.0.1:8000/post/123456/#create",
|
||||
actor_id=f"http://127.0.0.1:8000/profile/123456/",
|
||||
_children=[
|
||||
ActivitypubImage(url="foobar", media_type="image/jpeg"),
|
||||
ActivitypubImage(url="barfoo", name="spam and eggs", media_type="image/jpeg"),
|
||||
models.Image(url="foobar", media_type="image/jpeg"),
|
||||
models.Image(url="barfoo", name="spam and eggs", media_type="image/jpeg"),
|
||||
],
|
||||
)
|
||||
obj.times={'edited':False, 'created':obj.created_at}
|
||||
return obj
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def activitypubpost_mentions():
|
||||
with freeze_time("2019-04-27"):
|
||||
return ActivitypubPost(
|
||||
obj = make_content_class(Post)(
|
||||
raw_content="""# raw_content\n\n@{someone@localhost.local} @{http://localhost.local/someone}""",
|
||||
public=True,
|
||||
provider_display_name="Socialhome",
|
||||
|
@ -119,12 +128,14 @@ def activitypubpost_mentions():
|
|||
"http://localhost.local/someone",
|
||||
}
|
||||
)
|
||||
obj.times={'edited':False, 'created':obj.created_at}
|
||||
return obj
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def activitypubpost_tags():
|
||||
with freeze_time("2019-04-27"):
|
||||
return ActivitypubPost(
|
||||
obj = make_content_class(Post)(
|
||||
raw_content="# raw_content\n#foobar\n#barfoo",
|
||||
public=True,
|
||||
provider_display_name="Socialhome",
|
||||
|
@ -132,12 +143,14 @@ def activitypubpost_tags():
|
|||
activity_id=f"http://127.0.0.1:8000/post/123456/#create",
|
||||
actor_id=f"http://127.0.0.1:8000/profile/123456/",
|
||||
)
|
||||
obj.times={'edited':False, 'created':obj.created_at}
|
||||
return obj
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def activitypubpost_embedded_images():
|
||||
with freeze_time("2019-04-27"):
|
||||
return ActivitypubPost(
|
||||
obj = make_content_class(Post)(
|
||||
raw_content="""
|
||||
#Cycling #lauttasaari #sea #sun
|
||||
|
||||
|
@ -158,60 +171,68 @@ https://jasonrobinson.me/media/uploads/2019/07/16/daa24d89-cedf-4fc7-bad8-74a902
|
|||
activity_id=f"http://127.0.0.1:8000/post/123456/#create",
|
||||
actor_id=f"https://jasonrobinson.me/u/jaywink/",
|
||||
)
|
||||
obj.times={'edited':False, 'created':obj.created_at}
|
||||
return obj
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def activitypubprofile():
|
||||
return ActivitypubProfile(
|
||||
id="https://example.com/bob", raw_content="foobar", name="Bob Bobertson", public=True,
|
||||
tag_list=["socialfederation", "federation"], image_urls={
|
||||
"large": "urllarge", "medium": "urlmedium", "small": "urlsmall"
|
||||
}, inboxes={
|
||||
"private": "https://example.com/bob/private",
|
||||
"public": "https://example.com/public",
|
||||
}, public_key=PUBKEY, url="https://example.com/bob-bobertson"
|
||||
)
|
||||
@patch.object(models.base.Image, 'get_media_type', return_value="image/jpeg")
|
||||
def activitypubprofile(mock_fetch):
|
||||
with freeze_time("2022-09-06"):
|
||||
return models.Person(
|
||||
id="https://example.com/bob/", raw_content="foobar", name="Bob Bobertson", public=True,
|
||||
tag_list=["socialfederation", "federation"], image_urls={
|
||||
"large": "urllarge", "medium": "urlmedium", "small": "urlsmall"
|
||||
}, inboxes={
|
||||
"private": "https://example.com/bob/private",
|
||||
"public": "https://example.com/public",
|
||||
}, public_key=PUBKEY, url="https://example.com/bob-bobertson"
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def activitypubprofile_diaspora_guid():
|
||||
return ActivitypubProfile(
|
||||
id="https://example.com/bob", raw_content="foobar", name="Bob Bobertson", public=True,
|
||||
tag_list=["socialfederation", "federation"], image_urls={
|
||||
"large": "urllarge", "medium": "urlmedium", "small": "urlsmall"
|
||||
}, inboxes={
|
||||
"private": "https://example.com/bob/private",
|
||||
"public": "https://example.com/public",
|
||||
}, public_key=PUBKEY, url="https://example.com/bob-bobertson",
|
||||
guid="totallyrandomguid", handle="bob@example.com",
|
||||
)
|
||||
@patch.object(models.base.Image, 'get_media_type', return_value="image/jpeg")
|
||||
def activitypubprofile_diaspora_guid(mock_fetch):
|
||||
with freeze_time("2022-09-06"):
|
||||
return models.Person(
|
||||
id="https://example.com/bob/", raw_content="foobar", name="Bob Bobertson", public=True,
|
||||
tag_list=["socialfederation", "federation"], image_urls={
|
||||
"large": "urllarge", "medium": "urlmedium", "small": "urlsmall"
|
||||
}, inboxes={
|
||||
"private": "https://example.com/bob/private",
|
||||
"public": "https://example.com/public",
|
||||
}, public_key=PUBKEY, url="https://example.com/bob-bobertson",
|
||||
guid="totallyrandomguid", handle="bob@example.com",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def activitypubretraction():
|
||||
with freeze_time("2019-04-27"):
|
||||
return ActivitypubRetraction(
|
||||
obj = Retraction(
|
||||
target_id="http://127.0.0.1:8000/post/123456/",
|
||||
activity_id="http://127.0.0.1:8000/post/123456/#delete",
|
||||
actor_id="http://127.0.0.1:8000/profile/123456/",
|
||||
entity_type="Post",
|
||||
)
|
||||
return get_outbound_entity(obj, None)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def activitypubretraction_announce():
|
||||
with freeze_time("2019-04-27"):
|
||||
return ActivitypubRetraction(
|
||||
obj = Retraction(
|
||||
target_id="http://127.0.0.1:8000/post/123456/activity",
|
||||
activity_id="http://127.0.0.1:8000/post/123456/#delete",
|
||||
actor_id="http://127.0.0.1:8000/profile/123456/",
|
||||
entity_type="Share",
|
||||
)
|
||||
return get_outbound_entity(obj, None)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def activitypubundofollow():
|
||||
return ActivitypubFollow(
|
||||
return models.Follow(
|
||||
activity_id="https://localhost/undo",
|
||||
actor_id="https://localhost/profile",
|
||||
target_id="https://example.com/profile",
|
||||
|
|
|
@ -3,7 +3,7 @@ from unittest.mock import patch, Mock
|
|||
|
||||
import pytest
|
||||
|
||||
from federation.entities.activitypub.entities import ActivitypubFollow, ActivitypubPost
|
||||
from federation.entities.activitypub.models import Follow, Note
|
||||
from federation.tests.fixtures.payloads import (
|
||||
ACTIVITYPUB_FOLLOW, ACTIVITYPUB_POST, ACTIVITYPUB_POST_OBJECT, ACTIVITYPUB_POST_OBJECT_IMAGES)
|
||||
from federation.utils.activitypub import (
|
||||
|
@ -53,24 +53,24 @@ class TestRetrieveAndParseDocument:
|
|||
@patch("federation.utils.activitypub.fetch_document", autospec=True, return_value=(
|
||||
json.dumps(ACTIVITYPUB_FOLLOW), None, None),
|
||||
)
|
||||
@patch.object(ActivitypubFollow, "post_receive")
|
||||
@patch.object(Follow, "post_receive")
|
||||
def test_returns_entity_for_valid_document__follow(self, mock_post_receive, mock_fetch):
|
||||
entity = retrieve_and_parse_document("https://example.com/foobar")
|
||||
assert isinstance(entity, ActivitypubFollow)
|
||||
assert isinstance(entity, Follow)
|
||||
|
||||
@patch("federation.utils.activitypub.fetch_document", autospec=True, return_value=(
|
||||
json.dumps(ACTIVITYPUB_POST_OBJECT), None, None),
|
||||
)
|
||||
def test_returns_entity_for_valid_document__post__without_activity(self, mock_fetch):
|
||||
entity = retrieve_and_parse_document("https://example.com/foobar")
|
||||
assert isinstance(entity, ActivitypubPost)
|
||||
assert isinstance(entity, Note)
|
||||
|
||||
@patch("federation.utils.activitypub.fetch_document", autospec=True, return_value=(
|
||||
json.dumps(ACTIVITYPUB_POST_OBJECT_IMAGES), None, None),
|
||||
)
|
||||
def test_returns_entity_for_valid_document__post__without_activity__with_images(self, mock_fetch):
|
||||
entity = retrieve_and_parse_document("https://example.com/foobar")
|
||||
assert isinstance(entity, ActivitypubPost)
|
||||
assert isinstance(entity, Note)
|
||||
assert len(entity._children) == 1
|
||||
assert entity._children[0].url == "https://files.mastodon.social/media_attachments/files/017/792/237/original" \
|
||||
"/foobar.jpg"
|
||||
|
@ -80,7 +80,7 @@ class TestRetrieveAndParseDocument:
|
|||
)
|
||||
def test_returns_entity_for_valid_document__post__wrapped_in_activity(self, mock_fetch):
|
||||
entity = retrieve_and_parse_document("https://example.com/foobar")
|
||||
assert isinstance(entity, ActivitypubPost)
|
||||
assert isinstance(entity, Note)
|
||||
|
||||
@patch("federation.utils.activitypub.fetch_document", autospec=True, return_value=('{"foo": "bar"}', None, None))
|
||||
def test_returns_none_for_invalid_document(self, mock_fetch):
|
||||
|
|
Ładowanie…
Reference in New Issue