diff --git a/tests/__init__.py b/tests/__init__.py
index e69de29b..3aa487dd 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -0,0 +1,97 @@
+import re
+from xml.dom.minidom import Node, parseString
+
+
+def strip_quotes(want, got):
+ """
+ Strip quotes of doctests output values:
+ >>> strip_quotes("'foo'")
+ "foo"
+ >>> strip_quotes('"foo"')
+ "foo"
+ """
+ def is_quoted_string(s):
+ s = s.strip()
+ return (len(s) >= 2
+ and s[0] == s[-1]
+ and s[0] in ('"', "'"))
+
+ def is_quoted_unicode(s):
+ s = s.strip()
+ return (len(s) >= 3
+ and s[0] == 'u'
+ and s[1] == s[-1]
+ and s[1] in ('"', "'"))
+
+ if is_quoted_string(want) and is_quoted_string(got):
+ want = want.strip()[1:-1]
+ got = got.strip()[1:-1]
+ elif is_quoted_unicode(want) and is_quoted_unicode(got):
+ want = want.strip()[2:-1]
+ got = got.strip()[2:-1]
+ return want, got
+
+
+def compare_xml(want, got):
+ """Tries to do a 'xml-comparison' of want and got. Plain string
+ comparison doesn't always work because, for example, attribute
+ ordering should not be important. Comment nodes are not considered in the
+ comparison.
+ Based on http://codespeak.net/svn/lxml/trunk/src/lxml/doctestcompare.py
+ """
+ _norm_whitespace_re = re.compile(r'[ \t\n][ \t\n]+')
+
+ def norm_whitespace(v):
+ return _norm_whitespace_re.sub(' ', v)
+
+ def child_text(element):
+ return ''.join(c.data for c in element.childNodes
+ if c.nodeType == Node.TEXT_NODE)
+
+ def children(element):
+ return [c for c in element.childNodes
+ if c.nodeType == Node.ELEMENT_NODE]
+
+ def norm_child_text(element):
+ return norm_whitespace(child_text(element))
+
+ def attrs_dict(element):
+ return dict(element.attributes.items())
+
+ def check_element(want_element, got_element):
+ if want_element.tagName != got_element.tagName:
+ return False
+ if norm_child_text(want_element) != norm_child_text(got_element):
+ return False
+ if attrs_dict(want_element) != attrs_dict(got_element):
+ return False
+ want_children = children(want_element)
+ got_children = children(got_element)
+ if len(want_children) != len(got_children):
+ return False
+ for want, got in zip(want_children, got_children):
+ if not check_element(want, got):
+ return False
+ return True
+
+ def first_node(document):
+ for node in document.childNodes:
+ if node.nodeType != Node.COMMENT_NODE:
+ return node
+
+ want, got = strip_quotes(want, got)
+ want = want.replace('\\n', '\n')
+ got = got.replace('\\n', '\n')
+
+ # If the string is not a complete xml document, we may need to add a
+ # root element. This allow us to compare fragments, like ""
+ if not want.startswith('%s'
+ want = wrapper % want
+ got = wrapper % got
+
+ # Parse the want and got strings, and compare the parsings.
+ want_root = first_node(parseString(want))
+ got_root = first_node(parseString(got))
+
+ return check_element(want_root, got_root)
diff --git a/tests/manager.py b/tests/manager.py
index d364a0d0..02d3ead6 100644
--- a/tests/manager.py
+++ b/tests/manager.py
@@ -16,9 +16,38 @@
from xero import Xero
from xero.manager import Manager
from tests import mock_data
+from . import compare_xml
+from unittest.util import safe_repr
+import difflib
+import six
class ManagerTest(unittest.TestCase):
+ maxDiff = None
+
+ def assertXMLEqual(self, xml1, xml2, msg=None):
+ """
+ Asserts that two XML snippets are semantically the same.
+ Whitespace in most cases is ignored, and attribute ordering is not
+ significant. The passed-in arguments must be valid XML.
+ """
+ try:
+ result = compare_xml(xml1, xml2)
+ except Exception as e:
+ standardMsg = 'First or second argument is not valid XML\n%s' % e
+ self.fail(self._formatMessage(msg, standardMsg))
+ else:
+ if not result:
+ standardMsg = '%s != %s' % (safe_repr(xml1, True), safe_repr(xml2, True))
+ diff = ('\n' + '\n'.join(
+ difflib.ndiff(
+ six.text_type(xml1).splitlines(),
+ six.text_type(xml2).splitlines(),
+ )
+ ))
+ standardMsg = self._truncateMessage(standardMsg, diff)
+ self.fail(self._formatMessage(msg, standardMsg))
+
def test_serializer(self):
credentials = Mock(base_url="")
manager = Manager('contacts', credentials)
@@ -80,12 +109,87 @@ def test_serializer(self):
2015-07-06 16:25:02.711136
"""
- # @todo Need a py2/3 way to compare XML easily.
- # self.assertEqual(
- # resultant_xml,
- # expected_xml,
- # "Failed to serialize data to XML correctly."
- # )
+ self.assertXMLEqual(
+ resultant_xml,
+ expected_xml,
+ )
+
+
+ def test_serializer_phones_addresses(self):
+ credentials = Mock(base_url="")
+ manager = Manager('contacts', credentials)
+
+ example_contact_input = {
+ 'ContactID': '565acaa9-e7f3-4fbf-80c3-16b081ddae10',
+ 'ContactStatus': 'ACTIVE',
+ 'Name': 'Southside Office Supplies',
+ 'Addresses': [
+ {
+ 'AddressType': 'POBOX',
+ },
+ {
+ 'AddressType': 'STREET',
+ },
+ ],
+ 'Phones': [
+ {
+ 'PhoneType': 'DDI',
+ },
+ {
+ 'PhoneType': 'DEFAULT',
+ },
+ {
+ 'PhoneType': 'FAX',
+ },
+ {
+ 'PhoneType': 'MOBILE',
+ },
+ ],
+ 'UpdatedDateUTC': datetime.datetime(2015, 9, 18, 5, 6, 56, 893),
+ 'IsSupplier': False,
+ 'IsCustomer': False,
+ 'HasAttachments': False,
+ }
+ resultant_xml = manager._prepare_data_for_save(example_contact_input)
+
+ expected_xml = """
+
+ 565acaa9-e7f3-4fbf-80c3-16b081ddae10
+ ACTIVE
+ Southside Office Supplies
+
+
+ POBOX
+
+
+ STREET
+
+
+
+
+ DDI
+
+
+ DEFAULT
+
+
+ FAX
+
+
+ MOBILE
+
+
+ 2015-09-18T05:06:56.893
+ false
+ false
+ false
+
+ """
+
+ self.assertXMLEqual(
+ resultant_xml,
+ expected_xml,
+ )
def test_serializer_nested_singular(self):