kopia lustrzana https://gitlab.com/jaywink/federation
Merge pull request #10 from jaywink/participation-models
Support Comments and Likesmerge-requests/130/head
commit
561f2ab87a
|
@ -1,7 +1,12 @@
|
||||||
from datetime import datetime
|
# -*- coding: utf-8 -*-
|
||||||
|
import datetime
|
||||||
|
|
||||||
from dirty_validators.basic import Email
|
from dirty_validators.basic import Email
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ("Post", "Image", "Comment")
|
||||||
|
|
||||||
|
|
||||||
class BaseEntity(object):
|
class BaseEntity(object):
|
||||||
_required = []
|
_required = []
|
||||||
|
|
||||||
|
@ -16,15 +21,26 @@ class BaseEntity(object):
|
||||||
1) Loop through attributes and call their `validate_<attr>` methods, if any.
|
1) Loop through attributes and call their `validate_<attr>` methods, if any.
|
||||||
2) Check `_required` contents and make sure all attrs in there have a value.
|
2) Check `_required` contents and make sure all attrs in there have a value.
|
||||||
"""
|
"""
|
||||||
# TBD
|
attributes = []
|
||||||
pass
|
for attr in dir(self):
|
||||||
|
if not attr.startswith("_"):
|
||||||
|
attr_type = type(getattr(self, attr))
|
||||||
|
if attr_type != "method":
|
||||||
|
if getattr(self, "validate_{attr}".format(attr=attr), None):
|
||||||
|
getattr(self, "validate_{attr}".format(attr=attr))()
|
||||||
|
attributes.append(attr)
|
||||||
|
required_fulfilled = set(self._required).issubset(set(attributes))
|
||||||
|
if not required_fulfilled:
|
||||||
|
raise ValueError(
|
||||||
|
"Not all required attributes fulfilled. Required: {required}".format(required=self._required)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class GUIDMixin(BaseEntity):
|
class GUIDMixin(BaseEntity):
|
||||||
guid = ""
|
guid = ""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(GUIDMixin, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self._required += ["guid"]
|
self._required += ["guid"]
|
||||||
|
|
||||||
def validate_guid(self):
|
def validate_guid(self):
|
||||||
|
@ -36,7 +52,7 @@ class HandleMixin(BaseEntity):
|
||||||
handle = ""
|
handle = ""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(HandleMixin, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self._required += ["handle"]
|
self._required += ["handle"]
|
||||||
|
|
||||||
def validate_handle(self):
|
def validate_handle(self):
|
||||||
|
@ -50,22 +66,18 @@ class PublicMixin(BaseEntity):
|
||||||
|
|
||||||
|
|
||||||
class CreatedAtMixin(BaseEntity):
|
class CreatedAtMixin(BaseEntity):
|
||||||
created_at = datetime.today()
|
created_at = datetime.datetime.now()
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(CreatedAtMixin, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self._required += ["created_at"]
|
self._required += ["created_at"]
|
||||||
|
|
||||||
|
|
||||||
class Post(GUIDMixin, HandleMixin, PublicMixin, CreatedAtMixin, BaseEntity):
|
class RawContentMixin(BaseEntity):
|
||||||
"""Reflects a post, status message, etc, which will be composed from the message or to the message."""
|
|
||||||
raw_content = ""
|
raw_content = ""
|
||||||
provider_display_name = ""
|
|
||||||
location = ""
|
|
||||||
photos = []
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(Post, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self._required += ["raw_content"]
|
self._required += ["raw_content"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -74,6 +86,13 @@ class Post(GUIDMixin, HandleMixin, PublicMixin, CreatedAtMixin, BaseEntity):
|
||||||
return set({word.strip("#") for word in self.raw_content.split() if word.startswith("#")})
|
return set({word.strip("#") for word in self.raw_content.split() if word.startswith("#")})
|
||||||
|
|
||||||
|
|
||||||
|
class Post(RawContentMixin, GUIDMixin, HandleMixin, PublicMixin, CreatedAtMixin, BaseEntity):
|
||||||
|
"""Reflects a post, status message, etc, which will be composed from the message or to the message."""
|
||||||
|
provider_display_name = ""
|
||||||
|
location = ""
|
||||||
|
photos = []
|
||||||
|
|
||||||
|
|
||||||
class Image(GUIDMixin, HandleMixin, PublicMixin, CreatedAtMixin, BaseEntity):
|
class Image(GUIDMixin, HandleMixin, PublicMixin, CreatedAtMixin, BaseEntity):
|
||||||
"""Reflects a single image, possibly linked to another object."""
|
"""Reflects a single image, possibly linked to another object."""
|
||||||
remote_path = ""
|
remote_path = ""
|
||||||
|
@ -85,5 +104,51 @@ class Image(GUIDMixin, HandleMixin, PublicMixin, CreatedAtMixin, BaseEntity):
|
||||||
width = 0
|
width = 0
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(Image, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self._required += ["remote_path", "remote_name"]
|
self._required += ["remote_path", "remote_name"]
|
||||||
|
|
||||||
|
|
||||||
|
class ParticipationMixin(BaseEntity):
|
||||||
|
"""Reflects a participation to something."""
|
||||||
|
target_guid = ""
|
||||||
|
participation = ""
|
||||||
|
|
||||||
|
_participation_valid_values = ["reaction", "subscription", "comment"]
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self._required += ["target_guid", "participation"]
|
||||||
|
|
||||||
|
def validate_participation(self):
|
||||||
|
"""Ensure participation is of a certain type."""
|
||||||
|
if self.participation not in self._participation_valid_values:
|
||||||
|
raise ValueError("participation should be one of: {valid}".format(
|
||||||
|
valid=", ".join(self._participation_valid_values)
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
class Comment(RawContentMixin, GUIDMixin, ParticipationMixin, CreatedAtMixin, HandleMixin):
|
||||||
|
"""Represents a comment, linked to another object."""
|
||||||
|
participation = "comment"
|
||||||
|
|
||||||
|
|
||||||
|
class Reaction(GUIDMixin, ParticipationMixin, CreatedAtMixin, HandleMixin):
|
||||||
|
"""Represents a reaction to another object, for example a like."""
|
||||||
|
participation = "reaction"
|
||||||
|
reaction = ""
|
||||||
|
|
||||||
|
_reaction_valid_values = ["like"]
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self._required += ["reaction"]
|
||||||
|
|
||||||
|
def validate_reaction(self):
|
||||||
|
"""Ensure reaction is of a certain type.
|
||||||
|
|
||||||
|
Mainly for future expansion.
|
||||||
|
"""
|
||||||
|
if self.reaction not in self._reaction_valid_values:
|
||||||
|
raise ValueError("reaction should be one of: {valid}".format(
|
||||||
|
valid=", ".join(self._reaction_valid_values)
|
||||||
|
))
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
from federation.entities.base import Comment, Post, Reaction
|
||||||
|
from federation.entities.diaspora.utils import format_dt, struct_to_xml
|
||||||
|
|
||||||
|
|
||||||
|
class DiasporaComment(Comment):
|
||||||
|
"""Diaspora comment."""
|
||||||
|
author_signature = ""
|
||||||
|
|
||||||
|
def to_xml(self):
|
||||||
|
element = etree.Element("comment")
|
||||||
|
struct_to_xml(element, [
|
||||||
|
{'guid': self.guid},
|
||||||
|
{'parent_guid': self.target_guid},
|
||||||
|
{'author_signature': self.author_signature},
|
||||||
|
{'text': self.raw_content},
|
||||||
|
{'diaspora_handle': self.handle},
|
||||||
|
])
|
||||||
|
return element
|
||||||
|
|
||||||
|
|
||||||
|
class DiasporaPost(Post):
|
||||||
|
"""Diaspora post, ie status message."""
|
||||||
|
def to_xml(self):
|
||||||
|
"""Convert to XML message."""
|
||||||
|
element = etree.Element("status_message")
|
||||||
|
struct_to_xml(element, [
|
||||||
|
{'raw_message': self.raw_content},
|
||||||
|
{'guid': self.guid},
|
||||||
|
{'diaspora_handle': self.handle},
|
||||||
|
{'public': 'true' if self.public else 'false'},
|
||||||
|
{'created_at': format_dt(self.created_at)}
|
||||||
|
])
|
||||||
|
return element
|
||||||
|
|
||||||
|
|
||||||
|
class DiasporaLike(Reaction):
|
||||||
|
"""Diaspora like."""
|
||||||
|
author_signature = ""
|
||||||
|
reaction = "like"
|
||||||
|
|
||||||
|
def to_xml(self):
|
||||||
|
"""Convert to XML message."""
|
||||||
|
element = etree.Element("like")
|
||||||
|
struct_to_xml(element, [
|
||||||
|
{"target_type": "Post"},
|
||||||
|
{'guid': self.guid},
|
||||||
|
{'parent_guid': self.target_guid},
|
||||||
|
{'author_signature': self.author_signature},
|
||||||
|
{"positive": "true"},
|
||||||
|
{'diaspora_handle': self.handle},
|
||||||
|
])
|
||||||
|
return element
|
|
@ -1,54 +0,0 @@
|
||||||
from dateutil.tz import tzlocal, tzutc
|
|
||||||
from lxml import etree
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_timezone(dt, tz=None):
|
|
||||||
"""
|
|
||||||
Make sure the datetime <dt> has a timezone set, using timezone <tz> if it
|
|
||||||
doesn't. <tz> defaults to the local timezone.
|
|
||||||
"""
|
|
||||||
if dt.tzinfo is None:
|
|
||||||
return dt.replace(tzinfo=tz or tzlocal())
|
|
||||||
else:
|
|
||||||
return dt
|
|
||||||
|
|
||||||
|
|
||||||
class EntityConverter(object):
|
|
||||||
|
|
||||||
def __init__(self, entity):
|
|
||||||
self.entity = entity
|
|
||||||
self.entity_type = entity.__class__.__name__.lower()
|
|
||||||
|
|
||||||
def struct_to_xml(self, node, struct):
|
|
||||||
"""
|
|
||||||
Turn a list of dicts into XML nodes with tag names taken from the dict
|
|
||||||
keys and element text taken from dict values. This is a list of dicts
|
|
||||||
so that the XML nodes can be ordered in the XML output.
|
|
||||||
"""
|
|
||||||
for obj in struct:
|
|
||||||
for k, v in obj.items():
|
|
||||||
etree.SubElement(node, k).text = v
|
|
||||||
|
|
||||||
def convert_to_xml(self):
|
|
||||||
if hasattr(self, "%s_to_xml" % self.entity_type):
|
|
||||||
method_name = "%s_to_xml" % self.entity_type
|
|
||||||
return getattr(self, method_name)()
|
|
||||||
|
|
||||||
def format_dt(cls, dt):
|
|
||||||
"""
|
|
||||||
Format a datetime in the way that D* nodes expect.
|
|
||||||
"""
|
|
||||||
return ensure_timezone(dt).astimezone(tzutc()).strftime(
|
|
||||||
'%Y-%m-%d %H:%M:%S %Z'
|
|
||||||
)
|
|
||||||
|
|
||||||
def post_to_xml(self):
|
|
||||||
req = etree.Element("status_message")
|
|
||||||
self.struct_to_xml(req, [
|
|
||||||
{'raw_message': self.entity.raw_content},
|
|
||||||
{'guid': self.entity.guid},
|
|
||||||
{'diaspora_handle': self.entity.handle},
|
|
||||||
{'public': 'true' if self.entity.public else 'false'},
|
|
||||||
{'created_at': self.format_dt(self.entity.created_at)}
|
|
||||||
])
|
|
||||||
return req
|
|
|
@ -1,12 +1,16 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from federation.entities.base import Post, Image
|
from federation.entities.base import Image
|
||||||
|
from federation.entities.diaspora.entities import DiasporaPost, DiasporaComment, DiasporaLike
|
||||||
|
|
||||||
MAPPINGS = {
|
MAPPINGS = {
|
||||||
"status_message": Post,
|
"status_message": DiasporaPost,
|
||||||
"photo": Image,
|
"photo": Image,
|
||||||
|
"comment": DiasporaComment,
|
||||||
|
"like": DiasporaLike,
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOLEAN_KEYS = [
|
BOOLEAN_KEYS = [
|
||||||
|
@ -34,19 +38,22 @@ def message_to_objects(message):
|
||||||
cls = MAPPINGS.get(element.tag, None)
|
cls = MAPPINGS.get(element.tag, None)
|
||||||
if cls:
|
if cls:
|
||||||
attrs = xml_children_as_dict(element)
|
attrs = xml_children_as_dict(element)
|
||||||
transformed = transform_attributes(cls, attrs)
|
transformed = transform_attributes(attrs)
|
||||||
entities.append(cls(**transformed))
|
entity = cls(**transformed)
|
||||||
|
entities.append(entity)
|
||||||
return entities
|
return entities
|
||||||
|
|
||||||
|
|
||||||
def transform_attributes(cls, attrs):
|
def transform_attributes(attrs):
|
||||||
"""Transform some attribute keys."""
|
"""Transform some attribute keys."""
|
||||||
transformed = {}
|
transformed = {}
|
||||||
for key, value in attrs.items():
|
for key, value in attrs.items():
|
||||||
if key == "raw_message":
|
if key in ["raw_message", "text"]:
|
||||||
transformed["raw_content"] = value
|
transformed["raw_content"] = value
|
||||||
elif key == "diaspora_handle":
|
elif key == "diaspora_handle":
|
||||||
transformed["handle"] = value
|
transformed["handle"] = value
|
||||||
|
elif key == "parent_guid":
|
||||||
|
transformed["target_guid"] = value
|
||||||
elif key in BOOLEAN_KEYS:
|
elif key in BOOLEAN_KEYS:
|
||||||
transformed[key] = True if value == "true" else False
|
transformed[key] = True if value == "true" else False
|
||||||
elif key in DATETIME_KEYS:
|
elif key in DATETIME_KEYS:
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from dateutil.tz import tzlocal, tzutc
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_timezone(dt, tz=None):
|
||||||
|
"""
|
||||||
|
Make sure the datetime <dt> has a timezone set, using timezone <tz> if it
|
||||||
|
doesn't. <tz> defaults to the local timezone.
|
||||||
|
"""
|
||||||
|
if dt.tzinfo is None:
|
||||||
|
return dt.replace(tzinfo=tz or tzlocal())
|
||||||
|
else:
|
||||||
|
return dt
|
||||||
|
|
||||||
|
|
||||||
|
def format_dt(dt):
|
||||||
|
"""
|
||||||
|
Format a datetime in the way that D* nodes expect.
|
||||||
|
"""
|
||||||
|
return ensure_timezone(dt).astimezone(tzutc()).strftime(
|
||||||
|
'%Y-%m-%d %H:%M:%S %Z'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def struct_to_xml(node, struct):
|
||||||
|
"""
|
||||||
|
Turn a list of dicts into XML nodes with tag names taken from the dict
|
||||||
|
keys and element text taken from dict values. This is a list of dicts
|
||||||
|
so that the XML nodes can be ordered in the XML output.
|
||||||
|
"""
|
||||||
|
for obj in struct:
|
||||||
|
for k, v in obj.items():
|
||||||
|
etree.SubElement(node, k).text = v
|
|
@ -72,7 +72,7 @@ class DiasporaHostMeta(BaseHostMeta):
|
||||||
webfinger_host (str)
|
webfinger_host (str)
|
||||||
"""
|
"""
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(DiasporaHostMeta, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
link = Link(
|
link = Link(
|
||||||
rel='lrdd',
|
rel='lrdd',
|
||||||
type_='application/xrd+xml',
|
type_='application/xrd+xml',
|
||||||
|
@ -87,7 +87,7 @@ class BaseLegacyWebFinger(BaseHostMeta):
|
||||||
See: https://code.google.com/p/webfinger/wiki/WebFingerProtocol
|
See: https://code.google.com/p/webfinger/wiki/WebFingerProtocol
|
||||||
"""
|
"""
|
||||||
def __init__(self, address, *args, **kwargs):
|
def __init__(self, address, *args, **kwargs):
|
||||||
super(BaseLegacyWebFinger, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
subject = Element("Subject", "acct:%s" % address)
|
subject = Element("Subject", "acct:%s" % address)
|
||||||
self.xrd.elements.append(subject)
|
self.xrd.elements.append(subject)
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ class DiasporaWebFinger(BaseLegacyWebFinger):
|
||||||
public_key (str) - public key
|
public_key (str) - public key
|
||||||
"""
|
"""
|
||||||
def __init__(self, handle, host, guid, public_key, *args, **kwargs):
|
def __init__(self, handle, host, guid, public_key, *args, **kwargs):
|
||||||
super(DiasporaWebFinger, self).__init__(handle, *args, **kwargs)
|
super().__init__(handle, *args, **kwargs)
|
||||||
self.xrd.elements.append(Element("Alias", "%s/people/%s" % (
|
self.xrd.elements.append(Element("Alias", "%s/people/%s" % (
|
||||||
host, guid
|
host, guid
|
||||||
)))
|
)))
|
||||||
|
|
|
@ -10,7 +10,6 @@ from Crypto.Random import get_random_bytes
|
||||||
from Crypto.Signature import PKCS1_v1_5 as PKCSSign
|
from Crypto.Signature import PKCS1_v1_5 as PKCSSign
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from federation.entities.diaspora.generators import EntityConverter
|
|
||||||
from federation.exceptions import EncryptedMessageError, NoHeaderInMessageError, NoSenderKeyFoundError
|
from federation.exceptions import EncryptedMessageError, NoHeaderInMessageError, NoSenderKeyFoundError
|
||||||
from federation.protocols.base import BaseProtocol
|
from federation.protocols.base import BaseProtocol
|
||||||
|
|
||||||
|
@ -164,8 +163,7 @@ class Protocol(BaseProtocol):
|
||||||
|
|
||||||
def build_send(self, from_user, to_user, entity, *args, **kwargs):
|
def build_send(self, from_user, to_user, entity, *args, **kwargs):
|
||||||
"""Build POST data for sending out to remotes."""
|
"""Build POST data for sending out to remotes."""
|
||||||
converter = EntityConverter(entity)
|
xml = entity.to_xml()
|
||||||
xml = converter.convert_to_xml()
|
|
||||||
self.init_message(xml, from_user.handle, from_user.private_key)
|
self.init_message(xml, from_user.handle, from_user.private_key)
|
||||||
xml = quote_plus(
|
xml = quote_plus(
|
||||||
self.create_salmon_envelope(to_user.key))
|
self.create_salmon_envelope(to_user.key))
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
from federation.entities.diaspora.entities import DiasporaComment, DiasporaPost, DiasporaLike
|
||||||
|
|
||||||
|
|
||||||
|
class TestEntitiesConvertToXML(object):
|
||||||
|
def test_post_to_xml(self):
|
||||||
|
entity = DiasporaPost(raw_content="raw_content", guid="guid", handle="handle", public=True)
|
||||||
|
result = entity.to_xml()
|
||||||
|
assert result.tag == "status_message"
|
||||||
|
assert len(result.find("created_at").text) > 0
|
||||||
|
result.find("created_at").text = "" # timestamp makes testing painful
|
||||||
|
converted = b"<status_message><raw_message>raw_content</raw_message><guid>guid</guid>" \
|
||||||
|
b"<diaspora_handle>handle</diaspora_handle><public>true</public><created_at>" \
|
||||||
|
b"</created_at></status_message>"
|
||||||
|
assert etree.tostring(result) == converted
|
||||||
|
|
||||||
|
def test_comment_to_xml(self):
|
||||||
|
entity = DiasporaComment(raw_content="raw_content", guid="guid", target_guid="target_guid", handle="handle")
|
||||||
|
result = entity.to_xml()
|
||||||
|
assert result.tag == "comment"
|
||||||
|
converted = b"<comment><guid>guid</guid><parent_guid>target_guid</parent_guid>" \
|
||||||
|
b"<author_signature></author_signature><text>raw_content</text>" \
|
||||||
|
b"<diaspora_handle>handle</diaspora_handle></comment>"
|
||||||
|
assert etree.tostring(result) == converted
|
||||||
|
|
||||||
|
def test_like_to_xml(self):
|
||||||
|
entity = DiasporaLike(guid="guid", target_guid="target_guid", handle="handle")
|
||||||
|
result = entity.to_xml()
|
||||||
|
assert result.tag == "like"
|
||||||
|
converted = b"<like><target_type>Post</target_type><guid>guid</guid><parent_guid>target_guid</parent_guid>" \
|
||||||
|
b"<author_signature></author_signature><positive>true</positive>" \
|
||||||
|
b"<diaspora_handle>handle</diaspora_handle></like>"
|
||||||
|
assert etree.tostring(result) == converted
|
|
@ -1,31 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from datetime import datetime
|
|
||||||
from lxml import etree
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
from federation.entities.base import Post
|
|
||||||
from federation.entities.diaspora.generators import EntityConverter
|
|
||||||
|
|
||||||
|
|
||||||
class TestEntityConverterCallsToXML(object):
|
|
||||||
|
|
||||||
def test_entity_converter_call_to_xml(self):
|
|
||||||
entity = Post()
|
|
||||||
|
|
||||||
with patch.object(EntityConverter, "post_to_xml", return_value="foo") as mock_to_xml:
|
|
||||||
entity_converter = EntityConverter(entity=entity)
|
|
||||||
result = entity_converter.convert_to_xml()
|
|
||||||
assert result == "foo"
|
|
||||||
assert mock_to_xml.called
|
|
||||||
|
|
||||||
def test_entity_converter_converts_a_post(self):
|
|
||||||
entity = Post(raw_content="raw_content", guid="guid", handle="handle", public=True, created_at=datetime.today())
|
|
||||||
entity_converter = EntityConverter(entity)
|
|
||||||
result = entity_converter.convert_to_xml()
|
|
||||||
assert result.tag == "status_message"
|
|
||||||
assert len(result.find("created_at").text) > 0
|
|
||||||
result.find("created_at").text = "" # timestamp makes testing painful
|
|
||||||
post_converted = b"<status_message><raw_message>raw_content</raw_message><guid>guid</guid>" \
|
|
||||||
b"<diaspora_handle>handle</diaspora_handle><public>true</public><created_at>" \
|
|
||||||
b"</created_at></status_message>"
|
|
||||||
assert etree.tostring(result) == post_converted
|
|
|
@ -1,9 +1,10 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from federation.entities.base import Post
|
from federation.entities.base import Comment, Post, Reaction
|
||||||
|
from federation.entities.diaspora.entities import DiasporaPost, DiasporaComment, DiasporaLike
|
||||||
from federation.entities.diaspora.mappers import message_to_objects
|
from federation.entities.diaspora.mappers import message_to_objects
|
||||||
from federation.tests.fixtures.payloads import DIASPORA_POST_SIMPLE
|
from federation.tests.fixtures.payloads import DIASPORA_POST_SIMPLE, DIASPORA_POST_COMMENT, DIASPORA_POST_LIKE
|
||||||
|
|
||||||
|
|
||||||
class TestDiasporaEntityMappersReceive(object):
|
class TestDiasporaEntityMappersReceive(object):
|
||||||
|
@ -12,9 +13,34 @@ class TestDiasporaEntityMappersReceive(object):
|
||||||
entities = message_to_objects(DIASPORA_POST_SIMPLE)
|
entities = message_to_objects(DIASPORA_POST_SIMPLE)
|
||||||
assert len(entities) == 1
|
assert len(entities) == 1
|
||||||
post = entities[0]
|
post = entities[0]
|
||||||
|
assert isinstance(post, DiasporaPost)
|
||||||
assert isinstance(post, Post)
|
assert isinstance(post, Post)
|
||||||
assert post.raw_content == "((status message))"
|
assert post.raw_content == "((status message))"
|
||||||
assert post.guid == "((guid))"
|
assert post.guid == "((guid))"
|
||||||
assert post.handle == "alice@alice.diaspora.example.org"
|
assert post.handle == "alice@alice.diaspora.example.org"
|
||||||
assert post.public == False
|
assert post.public == False
|
||||||
assert post.created_at == datetime(2011, 7, 20, 1, 36, 7)
|
assert post.created_at == datetime(2011, 7, 20, 1, 36, 7)
|
||||||
|
|
||||||
|
def test_message_to_objects_comment(self):
|
||||||
|
entities = message_to_objects(DIASPORA_POST_COMMENT)
|
||||||
|
assert len(entities) == 1
|
||||||
|
comment = entities[0]
|
||||||
|
assert isinstance(comment, DiasporaComment)
|
||||||
|
assert isinstance(comment, Comment)
|
||||||
|
assert comment.target_guid == "((parent_guid))"
|
||||||
|
assert comment.guid == "((guid))"
|
||||||
|
assert comment.handle == "alice@alice.diaspora.example.org"
|
||||||
|
assert comment.participation == "comment"
|
||||||
|
assert comment.raw_content == "((text))"
|
||||||
|
|
||||||
|
def test_message_to_objects_like(self):
|
||||||
|
entities = message_to_objects(DIASPORA_POST_LIKE)
|
||||||
|
assert len(entities) == 1
|
||||||
|
like = entities[0]
|
||||||
|
assert isinstance(like, DiasporaLike)
|
||||||
|
assert isinstance(like, Reaction)
|
||||||
|
assert like.target_guid == "((parent_guid))"
|
||||||
|
assert like.guid == "((guid))"
|
||||||
|
assert like.handle == "alice@alice.diaspora.example.org"
|
||||||
|
assert like.participation == "reaction"
|
||||||
|
assert like.reaction == "like"
|
||||||
|
|
|
@ -1,9 +1,29 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from federation.tests.factories.entities import TaggedPostFactory
|
from unittest.mock import Mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from federation.entities.base import BaseEntity
|
||||||
|
from federation.tests.factories.entities import TaggedPostFactory, PostFactory
|
||||||
|
|
||||||
|
|
||||||
class TestPostEntityTags(object):
|
class TestPostEntityTags(object):
|
||||||
|
|
||||||
def test_post_entity_returns_list_of_tags(self):
|
def test_post_entity_returns_list_of_tags(self):
|
||||||
post = TaggedPostFactory()
|
post = TaggedPostFactory()
|
||||||
assert post.tags == {"tagone", "tagtwo", "tagthree"}
|
assert post.tags == {"tagone", "tagtwo", "tagthree"}
|
||||||
|
|
||||||
|
|
||||||
|
class TestBaseEntityCallsValidateMethods(object):
|
||||||
|
def test_entity_calls_attribute_validate_method(self):
|
||||||
|
post = PostFactory()
|
||||||
|
post.validate_location = Mock()
|
||||||
|
post.validate()
|
||||||
|
assert post.validate_location.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
|
class TestEntityRequiredAttributes(object):
|
||||||
|
def test_entity_checks_for_required_attributes(self):
|
||||||
|
entity = BaseEntity()
|
||||||
|
entity._required = ["foobar"]
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
entity.validate()
|
||||||
|
|
|
@ -35,3 +35,30 @@ DIASPORA_POST_SIMPLE = """<XML>
|
||||||
</post>
|
</post>
|
||||||
</XML>
|
</XML>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
DIASPORA_POST_COMMENT = """<XML>
|
||||||
|
<post>
|
||||||
|
<comment>
|
||||||
|
<guid>((guid))</guid>
|
||||||
|
<parent_guid>((parent_guid))</parent_guid>
|
||||||
|
<author_signature>((base64-encoded data))</author_signature>
|
||||||
|
<text>((text))</text>
|
||||||
|
<diaspora_handle>alice@alice.diaspora.example.org</diaspora_handle>
|
||||||
|
</comment>
|
||||||
|
</post>
|
||||||
|
</XML>
|
||||||
|
"""
|
||||||
|
|
||||||
|
DIASPORA_POST_LIKE = """<XML>
|
||||||
|
<post>
|
||||||
|
<like>
|
||||||
|
<target_type>Post</target_type>
|
||||||
|
<guid>((guid))</guid>
|
||||||
|
<parent_guid>((parent_guid))</parent_guid>
|
||||||
|
<author_signature>((base64-encoded data))</author_signature>
|
||||||
|
<positive>true</positive>
|
||||||
|
<diaspora_handle>alice@alice.diaspora.example.org</diaspora_handle>
|
||||||
|
</like>
|
||||||
|
</post>
|
||||||
|
</XML>
|
||||||
|
"""
|
||||||
|
|
|
@ -4,7 +4,7 @@ from Crypto.PublicKey import RSA
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from federation.controllers import handle_receive, handle_create_payload
|
from federation.controllers import handle_receive, handle_create_payload
|
||||||
from federation.entities.base import Post
|
from federation.entities.diaspora.entities import DiasporaPost
|
||||||
from federation.exceptions import NoSuitableProtocolFoundError
|
from federation.exceptions import NoSuitableProtocolFoundError
|
||||||
from federation.protocols.diaspora.protocol import Protocol
|
from federation.protocols.diaspora.protocol import Protocol
|
||||||
from federation.tests.fixtures.payloads import UNENCRYPTED_DIASPORA_PAYLOAD
|
from federation.tests.fixtures.payloads import UNENCRYPTED_DIASPORA_PAYLOAD
|
||||||
|
@ -35,7 +35,7 @@ class TestHandleCreatePayloadBuildsAPayload(object):
|
||||||
def test_handle_create_payload_builds_an_xml(self):
|
def test_handle_create_payload_builds_an_xml(self):
|
||||||
from_user = Mock(private_key=RSA.generate(2048), handle="foobar@domain.tld")
|
from_user = Mock(private_key=RSA.generate(2048), handle="foobar@domain.tld")
|
||||||
to_user = Mock(key=RSA.generate(2048).publickey())
|
to_user = Mock(key=RSA.generate(2048).publickey())
|
||||||
entity = Post()
|
entity = DiasporaPost()
|
||||||
data = handle_create_payload(from_user, to_user, entity)
|
data = handle_create_payload(from_user, to_user, entity)
|
||||||
assert len(data) > 0
|
assert len(data) > 0
|
||||||
parts = data.split("=")
|
parts = data.split("=")
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
[pytest]
|
||||||
|
testpaths = federation
|
Ładowanie…
Reference in New Issue