# Path: federation/entities/base.py
# class Post(RawContentMixin, PublicMixin, CreatedAtMixin, ProviderDisplayNameMixin, BaseEntity):
#     """Reflects a post, status message, etc, which will be composed from the message or to the message."""
#     location = ""
#     url = ""
# 
#     _allowed_children = (Image,)
#     _default_activity = ActivityType.CREATE
# 
# class Profile(CreatedAtMixin, OptionalRawContentMixin, PublicMixin, BaseEntity):
#     """Represents a profile for a user."""
#     atom_url = ""
#     email = ""
#     gender = ""
#     image_urls = None
#     location = ""
#     name = ""
#     nsfw = False
#     public_key = ""
#     tag_list = None
#     url = ""
#     username = ""
#     inboxes: Dict = None
# 
#     _allowed_children = (Image,)
# 
#     def __init__(self, *args, **kwargs):
#         self.image_urls = {
#             "small": "", "medium": "", "large": ""
#         }
#         self.inboxes = {
#             "private": None,
#             "public": None,
#         }
#         self.tag_list = []
#         super().__init__(*args, **kwargs)
#         # As an exception, a Profile does not require to have an `actor_id`
#         self._required.remove('actor_id')
# 
#     def validate_email(self):
#         if self.email:
#             validator = Email()
#             if not validator.is_valid(self.email):
#                 raise ValueError("Email is not valid")
# 
# Path: federation/entities/diaspora/entities.py
# class DiasporaPost(DiasporaEntityMixin, Post):
#     """Diaspora post, ie status message."""
#     _tag_name = "status_message"
# 
#     def to_xml(self):
#         """Convert to XML message."""
#         element = etree.Element(self._tag_name)
#         properties = [
#             {"text": self.raw_content},
#             {"guid": self.guid},
#             {"author": self.handle},
#             {"public": "true" if self.public else "false"},
#             {"created_at": format_dt(self.created_at)},
#             {"provider_display_name": self.provider_display_name},
#         ]
#         if self.id and self.id.startswith("http"):
#             properties.append({
#                 "activitypub_id": self.id,
#             })
#         struct_to_xml(element, properties)
#         return element
# 
# Path: federation/entities/diaspora/utils.py
# def get_full_xml_representation(entity, private_key):
#     """Get full XML representation of an entity.
# 
#     This contains the <XML><post>..</post></XML> wrapper.
# 
#     Accepts either a Base entity or a Diaspora entity.
# 
#     Author `private_key` must be given so that certain entities can be signed.
#     """
#     from federation.entities.diaspora.mappers import get_outbound_entity
#     diaspora_entity = get_outbound_entity(entity, private_key)
#     xml = diaspora_entity.to_xml()
#     return "<XML><post>%s</post></XML>" % etree.tostring(xml).decode("utf-8")
# 
# def format_dt(dt):
#     """
#     Format a datetime in the way that D* nodes expect.
#     """
#     return ensure_timezone(dt).astimezone(tzutc()).strftime(
#         '%Y-%m-%dT%H:%M:%SZ'
#     )
# 
# def add_element_to_doc(doc, tag, value):
#     """Set text value of an etree.Element of tag, appending a new element with given tag if it doesn't exist."""
#     element = doc.find(".//%s" % tag)
#     if element is None:
#         element = etree.SubElement(doc, tag)
#     element.text = value
# 
# Path: federation/entities/utils.py
# def get_base_attributes(entity):
#     """Build a dict of attributes of an entity.
# 
#     Returns attributes and their values, ignoring any properties, functions and anything that starts
#     with an underscore.
#     """
#     attributes = {}
#     cls = entity.__class__
#     for attr, _ in inspect.getmembers(cls, lambda o: not isinstance(o, property) and not inspect.isroutine(o)):
#         if not attr.startswith("_"):
#             attributes[attr] = getattr(entity, attr)
#     return attributes


import datetime
import re
import arrow
from unittest.mock import patch, Mock
from lxml import etree
from federation.entities.base import Post, Profile
from federation.entities.diaspora.entities import DiasporaPost
from federation.entities.diaspora.utils import (
    get_full_xml_representation, format_dt, add_element_to_doc)
from federation.entities.utils import get_base_attributes
            "inboxes", "mxid",
        }


class TestGetFullXMLRepresentation:
    @patch.object(DiasporaPost, "validate", new=Mock())
    def test_returns_xml_document(self):
        entity = Post()
        document = get_full_xml_representation(entity, "")
        document = re.sub(r"<created_at>.*</created_at>", "", document)  # Dates are annoying to compare
        assert document == "<XML><post><status_message><text></text><guid></guid>" \
                           "<author></author><public>false</public>" \
                           "<provider_display_name></provider_display_name></status_message></post></XML>"


class TestFormatDt:
    def test_formatted_string_returned_from_tz_aware_datetime(self):
        dt = arrow.get(datetime.datetime(2017, 1, 28, 3, 2, 3), "Europe/Helsinki").datetime
        assert format_dt(dt) == "2017-01-28T01:02:03Z"


def test_add_element_to_doc():
    # Replacing value
    doc = etree.fromstring("<comment><text>foobar</text><parent_author_signature>barfoo</parent_author_signature>"
                           "</comment>")
    add_element_to_doc(doc, "parent_author_signature", "newsig")
    assert etree.tostring(doc) == b"<comment><text>foobar</text><parent_author_signature>newsig" \
                                  b"</parent_author_signature></comment>"
    # Adding value to an empty tag
    doc = etree.fromstring("<comment><text>foobar</text><parent_author_signature /></comment>")

# Path: federation/entities/base.py
# class Post(RawContentMixin, PublicMixin, CreatedAtMixin, ProviderDisplayNameMixin, BaseEntity):
#     """Reflects a post, status message, etc, which will be composed from the message or to the message."""
#     location = ""
#     url = ""
# 
#     _allowed_children = (Image,)
#     _default_activity = ActivityType.CREATE
# 
# class Profile(CreatedAtMixin, OptionalRawContentMixin, PublicMixin, BaseEntity):
#     """Represents a profile for a user."""
#     atom_url = ""
#     email = ""
#     gender = ""
#     image_urls = None
#     location = ""
#     name = ""
#     nsfw = False
#     public_key = ""
#     tag_list = None
#     url = ""
#     username = ""
#     inboxes: Dict = None
# 
#     _allowed_children = (Image,)
# 
#     def __init__(self, *args, **kwargs):
#         self.image_urls = {
#             "small": "", "medium": "", "large": ""
#         }
#         self.inboxes = {
#             "private": None,
#             "public": None,
#         }
#         self.tag_list = []
#         super().__init__(*args, **kwargs)
#         # As an exception, a Profile does not require to have an `actor_id`
#         self._required.remove('actor_id')
# 
#     def validate_email(self):
#         if self.email:
#             validator = Email()
#             if not validator.is_valid(self.email):
#                 raise ValueError("Email is not valid")
# 
# Path: federation/entities/diaspora/entities.py
# class DiasporaPost(DiasporaEntityMixin, Post):
#     """Diaspora post, ie status message."""
#     _tag_name = "status_message"
# 
#     def to_xml(self):
#         """Convert to XML message."""
#         element = etree.Element(self._tag_name)
#         properties = [
#             {"text": self.raw_content},
#             {"guid": self.guid},
#             {"author": self.handle},
#             {"public": "true" if self.public else "false"},
#             {"created_at": format_dt(self.created_at)},
#             {"provider_display_name": self.provider_display_name},
#         ]
#         if self.id and self.id.startswith("http"):
#             properties.append({
#                 "activitypub_id": self.id,
#             })
#         struct_to_xml(element, properties)
#         return element
# 
# Path: federation/entities/diaspora/utils.py
# def get_full_xml_representation(entity, private_key):
#     """Get full XML representation of an entity.
# 
#     This contains the <XML><post>..</post></XML> wrapper.
# 
#     Accepts either a Base entity or a Diaspora entity.
# 
#     Author `private_key` must be given so that certain entities can be signed.
#     """
#     from federation.entities.diaspora.mappers import get_outbound_entity
#     diaspora_entity = get_outbound_entity(entity, private_key)
#     xml = diaspora_entity.to_xml()
#     return "<XML><post>%s</post></XML>" % etree.tostring(xml).decode("utf-8")
# 
# def format_dt(dt):
#     """
#     Format a datetime in the way that D* nodes expect.
#     """
#     return ensure_timezone(dt).astimezone(tzutc()).strftime(
#         '%Y-%m-%dT%H:%M:%SZ'
#     )
# 
# def add_element_to_doc(doc, tag, value):
#     """Set text value of an etree.Element of tag, appending a new element with given tag if it doesn't exist."""
#     element = doc.find(".//%s" % tag)
#     if element is None:
#         element = etree.SubElement(doc, tag)
#     element.text = value
# 
# Path: federation/entities/utils.py
# def get_base_attributes(entity):
#     """Build a dict of attributes of an entity.
# 
#     Returns attributes and their values, ignoring any properties, functions and anything that starts
#     with an underscore.
#     """
#     attributes = {}
#     cls = entity.__class__
#     for attr, _ in inspect.getmembers(cls, lambda o: not isinstance(o, property) and not inspect.isroutine(o)):
#         if not attr.startswith("_"):
#             attributes[attr] = getattr(entity, attr)
#     return attributes


# Path: federation/tests/entities/diaspora/test_utils.py
import datetime
import re
import arrow
from unittest.mock import patch, Mock
from lxml import etree
from federation.entities.base import Post, Profile
from federation.entities.diaspora.entities import DiasporaPost
from federation.entities.diaspora.utils import (
    get_full_xml_representation, format_dt, add_element_to_doc)
from federation.entities.utils import get_base_attributes

            "inboxes", "mxid",
        }


class TestGetFullXMLRepresentation:
    @patch.object(DiasporaPost, "validate", new=Mock())
    def test_returns_xml_document(self):
        entity = Post()
        document = get_full_xml_representation(entity, "")
        document = re.sub(r"<created_at>.*</created_at>", "", document)  # Dates are annoying to compare
        assert document == "<XML><post><status_message><text></text><guid></guid>" \
                           "<author></author><public>false</public>" \
                           "<provider_display_name></provider_display_name></status_message></post></XML>"


class TestFormatDt:
    def test_formatted_string_returned_from_tz_aware_datetime(self):
        dt = arrow.get(datetime.datetime(2017, 1, 28, 3, 2, 3), "Europe/Helsinki").datetime
        assert format_dt(dt) == "2017-01-28T01:02:03Z"


def test_add_element_to_doc():
    # Replacing value
    doc = etree.fromstring("<comment><text>foobar</text><parent_author_signature>barfoo</parent_author_signature>"
                           "</comment>")
    add_element_to_doc(doc, "parent_author_signature", "newsig")
    assert etree.tostring(doc) == b"<comment><text>foobar</text><parent_author_signature>newsig" \
                                  b"</parent_author_signature></comment>"
    # Adding value to an empty tag
    doc = etree.fromstring("<comment><text>foobar</text><parent_author_signature /></comment>")

    add_element_to_doc(doc, "parent_author_signature", "newsig")