kopia lustrzana https://gitlab.com/jaywink/federation
rodzic
42fce2493f
commit
c7b741913e
|
@ -1,3 +1,8 @@
|
|||
## [unreleased]
|
||||
|
||||
### Added
|
||||
* Added `Retraction` entity with `DiasporaRetraction` counterpart.
|
||||
|
||||
## [0.7.0] - 2016-09-15
|
||||
|
||||
### Backwards incompatible changes
|
||||
|
|
|
@ -5,6 +5,11 @@ import warnings
|
|||
from dirty_validators.basic import Email
|
||||
|
||||
|
||||
__all__ = (
|
||||
"Post", "Image", "Comment", "Reaction", "Relationship", "Profile", "Retraction"
|
||||
)
|
||||
|
||||
|
||||
class BaseEntity(object):
|
||||
_required = []
|
||||
|
||||
|
@ -75,6 +80,18 @@ class GUIDMixin(BaseEntity):
|
|||
raise ValueError("GUID must be at least 16 characters")
|
||||
|
||||
|
||||
class TargetGUIDMixin(BaseEntity):
|
||||
target_guid = ""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._required += ["target_guid"]
|
||||
|
||||
def validate_target_guid(self):
|
||||
if len(self.target_guid) < 16:
|
||||
raise ValueError("Target GUID must be at least 16 characters")
|
||||
|
||||
|
||||
class HandleMixin(BaseEntity):
|
||||
handle = ""
|
||||
|
||||
|
@ -143,7 +160,7 @@ class Image(GUIDMixin, HandleMixin, PublicMixin, CreatedAtMixin, BaseEntity):
|
|||
self._required += ["remote_path", "remote_name"]
|
||||
|
||||
|
||||
class ParticipationMixin(BaseEntity):
|
||||
class ParticipationMixin(TargetGUIDMixin):
|
||||
"""Reflects a participation to something."""
|
||||
target_guid = ""
|
||||
participation = ""
|
||||
|
@ -152,7 +169,7 @@ class ParticipationMixin(BaseEntity):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._required += ["target_guid", "participation"]
|
||||
self._required += ["participation"]
|
||||
|
||||
def validate_participation(self):
|
||||
"""Ensure participation is of a certain type."""
|
||||
|
@ -231,3 +248,17 @@ class Profile(CreatedAtMixin, HandleMixin, RawContentMixin, PublicMixin, GUIDMix
|
|||
validator = Email()
|
||||
if not validator.is_valid(self.email):
|
||||
raise ValueError("Email is not valid")
|
||||
|
||||
|
||||
class Retraction(CreatedAtMixin, HandleMixin, TargetGUIDMixin):
|
||||
"""Represents a retraction of content by author."""
|
||||
entity_type = ""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._required += ["entity_type"]
|
||||
|
||||
def validate_entity_type(self):
|
||||
"""Ensure type is some entity we know of."""
|
||||
if self.entity_type not in __all__:
|
||||
raise ValueError("Entity type %s not recognized." % self.entity_type)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from lxml import etree
|
||||
|
||||
from federation.entities.base import Comment, Post, Reaction, Relationship, Profile
|
||||
from federation.entities.base import Comment, Post, Reaction, Relationship, Profile, Retraction
|
||||
from federation.entities.diaspora.utils import format_dt, struct_to_xml, get_base_attributes
|
||||
from federation.utils.diaspora import retrieve_and_parse_profile
|
||||
|
||||
|
@ -121,3 +121,37 @@ class DiasporaProfile(DiasporaEntityMixin, Profile):
|
|||
profile = retrieve_and_parse_profile(attributes.get("handle"))
|
||||
attributes["guid"] = profile.guid
|
||||
return attributes
|
||||
|
||||
|
||||
class DiasporaRetraction(DiasporaEntityMixin, Retraction):
|
||||
"""Diaspora Retraction."""
|
||||
mapped = {
|
||||
"Like": "Reaction",
|
||||
"Photo": "Image",
|
||||
}
|
||||
|
||||
def to_xml(self):
|
||||
"""Convert to XML message."""
|
||||
element = etree.Element("retraction")
|
||||
struct_to_xml(element, [
|
||||
{"author": self.handle},
|
||||
{"target_guid": self.target_guid},
|
||||
{"target_type": DiasporaRetraction.entity_type_to_remote(self.entity_type)},
|
||||
])
|
||||
return element
|
||||
|
||||
@staticmethod
|
||||
def entity_type_from_remote(value):
|
||||
"""Convert entity type between Diaspora names and our Entity names."""
|
||||
if value in DiasporaRetraction.mapped:
|
||||
return DiasporaRetraction.mapped[value]
|
||||
return value
|
||||
|
||||
@staticmethod
|
||||
def entity_type_to_remote(value):
|
||||
"""Convert entity type between our Entity names and Diaspora names."""
|
||||
if value in DiasporaRetraction.mapped.values():
|
||||
values = list(DiasporaRetraction.mapped.values())
|
||||
index = values.index(value)
|
||||
return list(DiasporaRetraction.mapped.keys())[index]
|
||||
return value
|
||||
|
|
|
@ -4,9 +4,9 @@ from datetime import datetime
|
|||
|
||||
from lxml import etree
|
||||
|
||||
from federation.entities.base import Image, Relationship, Post, Reaction, Comment, Profile
|
||||
from federation.entities.base import Image, Relationship, Post, Reaction, Comment, Profile, Retraction
|
||||
from federation.entities.diaspora.entities import (
|
||||
DiasporaPost, DiasporaComment, DiasporaLike, DiasporaRequest, DiasporaProfile)
|
||||
DiasporaPost, DiasporaComment, DiasporaLike, DiasporaRequest, DiasporaProfile, DiasporaRetraction)
|
||||
|
||||
|
||||
logger = logging.getLogger("social-federation")
|
||||
|
@ -18,6 +18,7 @@ MAPPINGS = {
|
|||
"like": DiasporaLike,
|
||||
"request": DiasporaRequest,
|
||||
"profile": DiasporaProfile,
|
||||
"retraction": DiasporaRetraction,
|
||||
}
|
||||
|
||||
BOOLEAN_KEYS = [
|
||||
|
@ -73,7 +74,7 @@ def transform_attributes(attrs):
|
|||
for key, value in attrs.items():
|
||||
if key in ["raw_message", "text"]:
|
||||
transformed["raw_content"] = value
|
||||
elif key in ["diaspora_handle", "sender_handle"]:
|
||||
elif key in ["diaspora_handle", "sender_handle", "author"]:
|
||||
transformed["handle"] = value
|
||||
elif key == "recipient_handle":
|
||||
transformed["target_handle"] = value
|
||||
|
@ -99,6 +100,8 @@ def transform_attributes(attrs):
|
|||
transformed["raw_content"] = value
|
||||
elif key == "searchable":
|
||||
transformed["public"] = True if value == "true" else False
|
||||
elif key == "target_type":
|
||||
transformed["entity_type"] = DiasporaRetraction.entity_type_from_remote(value)
|
||||
elif key in BOOLEAN_KEYS:
|
||||
transformed[key] = True if value == "true" else False
|
||||
elif key in DATETIME_KEYS:
|
||||
|
@ -137,4 +140,6 @@ def get_outbound_entity(entity):
|
|||
return DiasporaRequest.from_base(entity)
|
||||
elif cls == Profile:
|
||||
return DiasporaProfile.from_base(entity)
|
||||
elif cls == Retraction:
|
||||
return DiasporaRetraction.from_base(entity)
|
||||
raise ValueError("Don't know how to convert this base entity to Diaspora protocol entities.")
|
||||
|
|
|
@ -6,7 +6,7 @@ from lxml import etree
|
|||
|
||||
from federation.entities.base import Profile
|
||||
from federation.entities.diaspora.entities import DiasporaComment, DiasporaPost, DiasporaLike, DiasporaRequest, \
|
||||
DiasporaProfile
|
||||
DiasporaProfile, DiasporaRetraction
|
||||
|
||||
|
||||
class TestEntitiesConvertToXML(object):
|
||||
|
@ -66,6 +66,14 @@ class TestEntitiesConvertToXML(object):
|
|||
b"<nsfw>false</nsfw><tag_string>#socialfederation #federation</tag_string></profile>"
|
||||
assert etree.tostring(result) == converted
|
||||
|
||||
def test_retraction_to_xml(self):
|
||||
entity = DiasporaRetraction(handle="bob@example.com", target_guid="x" * 16, entity_type="Post")
|
||||
result = entity.to_xml()
|
||||
assert result.tag == "retraction"
|
||||
converted = b"<retraction><author>bob@example.com</author>" \
|
||||
b"<target_guid>xxxxxxxxxxxxxxxx</target_guid><target_type>Post</target_type></retraction>"
|
||||
assert etree.tostring(result) == converted
|
||||
|
||||
|
||||
class TestDiasporaProfileFillExtraAttributes(object):
|
||||
def test_raises_if_no_handle(self):
|
||||
|
@ -79,3 +87,17 @@ class TestDiasporaProfileFillExtraAttributes(object):
|
|||
attrs = {"handle": "foo"}
|
||||
attrs = DiasporaProfile.fill_extra_attributes(attrs)
|
||||
assert attrs == {"handle": "foo", "guid": "guidguidguidguid"}
|
||||
|
||||
|
||||
class TestDiasporaRetractionEntityConverters(object):
|
||||
def test_entity_type_from_remote(self):
|
||||
assert DiasporaRetraction.entity_type_from_remote("Post") == "Post"
|
||||
assert DiasporaRetraction.entity_type_from_remote("Like") == "Reaction"
|
||||
assert DiasporaRetraction.entity_type_from_remote("Photo") == "Image"
|
||||
assert DiasporaRetraction.entity_type_from_remote("Comment") == "Comment"
|
||||
|
||||
def test_entity_type_to_remote(self):
|
||||
assert DiasporaRetraction.entity_type_to_remote("Post") == "Post"
|
||||
assert DiasporaRetraction.entity_type_to_remote("Reaction") == "Like"
|
||||
assert DiasporaRetraction.entity_type_to_remote("Image") == "Photo"
|
||||
assert DiasporaRetraction.entity_type_to_remote("Comment") == "Comment"
|
||||
|
|
|
@ -4,12 +4,16 @@ from unittest.mock import patch
|
|||
|
||||
import pytest
|
||||
|
||||
from federation.entities.base import Comment, Post, Reaction, Relationship, Profile
|
||||
from federation.entities.diaspora.entities import DiasporaPost, DiasporaComment, DiasporaLike, DiasporaRequest, \
|
||||
DiasporaProfile
|
||||
from federation.entities.base import Comment, Post, Reaction, Relationship, Profile, Retraction
|
||||
from federation.entities.diaspora.entities import (
|
||||
DiasporaPost, DiasporaComment, DiasporaLike, DiasporaRequest,
|
||||
DiasporaProfile, DiasporaRetraction
|
||||
)
|
||||
from federation.entities.diaspora.mappers import message_to_objects, get_outbound_entity
|
||||
from federation.tests.fixtures.payloads import DIASPORA_POST_SIMPLE, DIASPORA_POST_COMMENT, DIASPORA_POST_LIKE, \
|
||||
DIASPORA_REQUEST, DIASPORA_PROFILE, DIASPORA_POST_INVALID
|
||||
from federation.tests.fixtures.payloads import (
|
||||
DIASPORA_POST_SIMPLE, DIASPORA_POST_COMMENT, DIASPORA_POST_LIKE,
|
||||
DIASPORA_REQUEST, DIASPORA_PROFILE, DIASPORA_POST_INVALID, DIASPORA_RETRACTION
|
||||
)
|
||||
|
||||
|
||||
def mock_fill(attributes):
|
||||
|
@ -90,6 +94,15 @@ class TestDiasporaEntityMappersReceive(object):
|
|||
assert profile.nsfw == False
|
||||
assert profile.tag_list == ["socialfederation", "federation"]
|
||||
|
||||
def test_message_to_objects_retraction(self):
|
||||
entities = message_to_objects(DIASPORA_RETRACTION)
|
||||
assert len(entities) == 1
|
||||
entity = entities[0]
|
||||
assert isinstance(entity, Retraction)
|
||||
assert entity.handle == "bob@example.com"
|
||||
assert entity.target_guid == "x" * 16
|
||||
assert entity.entity_type == "Post"
|
||||
|
||||
@patch("federation.entities.diaspora.mappers.logger.error")
|
||||
def test_invalid_entity_logs_an_error(self, mock_logger):
|
||||
entities = message_to_objects(DIASPORA_POST_INVALID)
|
||||
|
@ -118,7 +131,7 @@ class TestGetOutboundEntity(object):
|
|||
entity = Comment()
|
||||
assert isinstance(get_outbound_entity(entity), DiasporaComment)
|
||||
|
||||
def test_reaction_of_like_is_converted_to_diasporaplike(self):
|
||||
def test_reaction_of_like_is_converted_to_diasporalike(self):
|
||||
entity = Reaction(reaction="like")
|
||||
assert isinstance(get_outbound_entity(entity), DiasporaLike)
|
||||
|
||||
|
@ -141,3 +154,7 @@ class TestGetOutboundEntity(object):
|
|||
entity = Relationship(relationship="foo")
|
||||
with pytest.raises(ValueError):
|
||||
get_outbound_entity(entity)
|
||||
|
||||
def test_retraction_is_converted_to_diasporaretraction(self):
|
||||
entity = Retraction()
|
||||
assert isinstance(get_outbound_entity(entity), DiasporaRetraction)
|
||||
|
|
|
@ -4,7 +4,7 @@ from unittest.mock import Mock
|
|||
import pytest
|
||||
|
||||
from federation.entities.base import BaseEntity, Relationship, Profile, RawContentMixin, GUIDMixin, HandleMixin, \
|
||||
PublicMixin, Image
|
||||
PublicMixin, Image, Retraction
|
||||
from federation.tests.factories.entities import TaggedPostFactory, PostFactory
|
||||
|
||||
|
||||
|
@ -113,3 +113,28 @@ class TestImageEntity(object):
|
|||
)
|
||||
with pytest.raises(ValueError):
|
||||
entity.validate()
|
||||
|
||||
|
||||
class TestRetractionEntity(object):
|
||||
def test_instance_creation(self):
|
||||
entity = Retraction(
|
||||
handle="foo@example.com", target_guid="x"*16, entity_type="Post"
|
||||
)
|
||||
entity.validate()
|
||||
|
||||
def test_required_validates(self):
|
||||
entity = Retraction(
|
||||
handle="fooexample.com", target_guid="x" * 16, entity_type="Post"
|
||||
)
|
||||
with pytest.raises(ValueError):
|
||||
entity.validate()
|
||||
entity = Retraction(
|
||||
handle="foo@example.com", target_guid="x" * 15, entity_type="Post"
|
||||
)
|
||||
with pytest.raises(ValueError):
|
||||
entity.validate()
|
||||
entity = Retraction(
|
||||
handle="foo@example.com", target_guid="x" * 16, entity_type="Foo"
|
||||
)
|
||||
with pytest.raises(ValueError):
|
||||
entity.validate()
|
||||
|
|
|
@ -111,3 +111,14 @@ DIASPORA_PROFILE = """<XML>
|
|||
</post>
|
||||
</XML>
|
||||
"""
|
||||
|
||||
DIASPORA_RETRACTION = """<XML>
|
||||
<post>
|
||||
<retraction>
|
||||
<author>bob@example.com</author>
|
||||
<target_guid>xxxxxxxxxxxxxxxx</target_guid>
|
||||
<target_type>Post</target_type>
|
||||
</retraction>
|
||||
</post>
|
||||
</XML>
|
||||
"""
|
||||
|
|
Ładowanie…
Reference in New Issue