Skip to content

Commit

Permalink
Add some tests to catch failing XML serialisation, #103
Browse files Browse the repository at this point in the history
  • Loading branch information
aidanlister committed Oct 11, 2015
1 parent 55c8fae commit 2ef0ce4
Show file tree
Hide file tree
Showing 2 changed files with 207 additions and 6 deletions.
97 changes: 97 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -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 "<foo/><bar/>"
if not want.startswith('<?xml'):
wrapper = '<root>%s</root>'
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)
116 changes: 110 additions & 6 deletions tests/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -80,12 +109,87 @@ def test_serializer(self):
<DueDate>2015-07-06 16:25:02.711136</DueDate>
"""

# @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 = """
<Contact>
<ContactID>565acaa9-e7f3-4fbf-80c3-16b081ddae10</ContactID>
<ContactStatus>ACTIVE</ContactStatus>
<Name>Southside Office Supplies</Name>
<Addresses>
<Address>
<AddressType>POBOX</AddressType>
</Address>
<Address>
<AddressType>STREET</AddressType>
</Address>
</Addresses>
<Phones>
<Phone>
<PhoneType>DDI</PhoneType>
</Phone>
<Phone>
<PhoneType>DEFAULT</PhoneType>
</Phone>
<Phone>
<PhoneType>FAX</PhoneType>
</Phone>
<Phone>
<PhoneType>MOBILE</PhoneType>
</Phone>
</Phones>
<UpdatedDateUTC>2015-09-18T05:06:56.893</UpdatedDateUTC>
<IsSupplier>false</IsSupplier>
<IsCustomer>false</IsCustomer>
<HasAttachments>false</HasAttachments>
</Contact>
"""

self.assertXMLEqual(
resultant_xml,
expected_xml,
)


def test_serializer_nested_singular(self):
Expand Down

0 comments on commit 2ef0ce4

Please sign in to comment.