"""Test for resync.sitemap."""
import io
from testfixtures import LogCapture
import re
import sys
import unittest
import xml.etree.ElementTree # for xml.etree.ElementTree.ParseError
from defusedxml.ElementTree import parse
from resync.resource import Resource
from resync.resource_list import ResourceList
from resync.sitemap import Sitemap, SitemapIndexError, SitemapParseError
class TestSitemapIndexError(unittest.TestCase):
def test_str(self):
"""Test str(...) gives just message part."""
err = SitemapIndexError("howdy", "this should be the etree")
self.assertEqual(str(err), "howdy")
class TestSitemap(unittest.TestCase):
def test_01_resource_str(self):
r1 = Resource('a3')
r1.lastmod = '2012-01-11T01:02:03Z'
self.assertEqual(Sitemap().resource_as_xml(r1),
"a32012-01-11T01:02:03Z")
self.assertEqual(Sitemap(spec_version='1.1').resource_as_xml(r1),
"a32012-01-11T01:02:03Z")
self.assertEqual(Sitemap(spec_version='1.0').resource_as_xml(r1),
"a32012-01-11T01:02:03Z")
self.assertEqual(Sitemap(spec_version='1.1', add_lastmod=True).resource_as_xml(r1),
"a32012-01-11T01:02:03Z")
# Now just datetime specified
r2 = Resource('aardvark')
r2.datetime = '2012-01-11T04:05:06Z'
self.assertEqual(Sitemap().resource_as_xml(r2),
'aardvark')
self.assertEqual(Sitemap(spec_version='1.1').resource_as_xml(r2),
'aardvark')
self.assertEqual(Sitemap(spec_version='1.0').resource_as_xml(r2),
'aardvark2012-01-11T04:05:06Z')
self.assertEqual(Sitemap(spec_version='1.1', add_lastmod=True).resource_as_xml(r2),
'aardvark2012-01-11T04:05:06Z')
def test_02_resource_str(self):
r1 = Resource('3b', 1234.1, length=9999, md5='ab54de')
self.assertEqual(Sitemap().resource_as_xml(r1),
"3b1970-01-01T00:20:34.100000Z")
r1 = Resource('3c', datetime='2013-01-02T13:00:00Z')
self.assertEqual(Sitemap().resource_as_xml(r1),
"3c")
def test_03_resource_str_hashes(self):
r1 = Resource('03hashes', 1234.1)
r1.md5 = 'aaa'
r1.sha1 = 'bbb'
self.assertEqual(Sitemap().resource_as_xml(
r1), "03hashes1970-01-01T00:20:34.100000Z")
r1.sha256 = 'ccc'
self.assertEqual(Sitemap().resource_as_xml(
r1), "03hashes1970-01-01T00:20:34.100000Z")
r1.sha1 = None
self.assertEqual(Sitemap().resource_as_xml(
r1), "03hashes1970-01-01T00:20:34.100000Z")
def test_04_resource_str(self):
r1 = Resource(uri='4a', lastmod="2013-01-02",
length=9999, md5='ab54de')
r1.ln = [{'rel': 'duplicate',
'pri': '1',
'href': 'http://mirror1.example.com/res1',
'modified': '2013-01-02T18:00:00Z'}]
self.assertEqual(Sitemap().resource_as_xml(
r1), "4a2013-01-02T00:00:00Z")
# add another two rs:ln's
r1.ln.append({'rel': 'num2'})
r1.ln.append({'rel': 'num3'})
self.assertEqual(Sitemap().resource_as_xml(
r1), "4a2013-01-02T00:00:00Z")
def test_07_print(self):
r1 = Resource(uri='a', lastmod='2001-01-01', length=1234)
r2 = Resource(uri='b', lastmod='2002-02-02', length=56789)
r3 = Resource(uri='c', lastmod='2003-03-03', length=0)
m = ResourceList(md={'capability': 'resourcelist', 'modified': None})
m.add(r1)
m.add(r2)
m.add(r3)
# print m
self.assertEqual(Sitemap().resources_as_xml(m), "\na2001-01-01T00:00:00Zb2002-02-02T00:00:00Zc2003-03-03T00:00:00Z")
def test_08_print_non_ascii_uri(self):
"""Verify that valid Unicode uri values give good XML out."""
m = ResourceList(md={'capability': 'resourcelist', 'modified': None})
m.add(Resource(uri=u'a_\u00c3_b'))
m.add(Resource(uri=u'c_\u1234_d'))
xml = Sitemap().resources_as_xml(m)
self.assertTrue(re.search(u'a_.*_b', xml))
self.assertTrue(re.search(u'a_\u00c3_b', xml))
self.assertTrue(re.search(u'c_\u1234_d', xml))
def test_09_print_from_iter(self):
r1 = Resource(uri='a', lastmod='2001-01-01', length=1234)
r2 = Resource(uri='b', lastmod='2002-02-02', length=56789)
m = ResourceList()
m.add(r1)
m.add(r2)
i = iter(m)
self.assertEqual(Sitemap().resources_as_xml(i), "\na2001-01-01T00:00:00Zb2002-02-02T00:00:00Z")
def test_10_parse_xml(self):
"""Test parse_xml method with string XML."""
xml = '\n\
\
http://e.com/a2012-03-14T18:37:36Z\
'
s = Sitemap()
i = s.parse_xml(fh=io.StringIO(xml))
self.assertFalse(s.parsed_index, 'was a sitemap')
self.assertEqual(s.resources_created, 1, 'got 1 resources')
for r in i.resources:
self.assertTrue(r is not None, 'got the uri expected')
self.assertEqual(r.uri, 'http://e.com/a')
self.assertEqual(r.lastmod, '2012-03-14T18:37:36Z')
self.assertEqual(r.length, 12)
self.assertEqual(r.md5, 'Q2hlY2sgSW50ZWdyaXR5IQ==')
# ..another
xml = '\n\
\
/tmp/rs_test/src/file_a2012-03-14T18:37:36Z\
/tmp/rs_test/src/file_b2012-03-14T18:37:36Z\
'
s = Sitemap()
i = s.parse_xml(fh=io.StringIO(xml))
self.assertFalse(s.parsed_index, 'was a sitemap')
self.assertEqual(s.resources_created, 2, 'got 2 resources')
def test_11_parse_xml_error(self):
"""Test exceptiona from parse_xml method."""
# bad params
s = Sitemap()
self.assertRaises(ValueError, s.parse_xml)
# got a sitemap when told to expect and indexp
xml = '\n\
\
'
self.assertRaises(SitemapIndexError, s.parse_xml, fh=io.StringIO(xml), sitemapindex=True)
# dupe entries DO NOT create an error
xml = '\n\
\
/mouse2020-12-21T00:00:00Z\
/mouse2020-12-21T00:00:00Z\
'
s = Sitemap()
i = s.parse_xml(fh=io.StringIO(xml))
self.assertEqual(len(i.resources), 2)
# preamble rs:md after is error
xml = '\n\
\
/frog2020-12-21T00:01:00Z\
\
/toad2020-12-21T00:02:00Z\
'
self.assertRaises(SitemapParseError, s.parse_xml, fh=io.StringIO(xml))
# preamble rs:ln after is also error
xml = '\n\
\
/wills2020-12-21T00:01:00Z\
\
'
s = Sitemap()
self.assertRaises(SitemapParseError, s.parse_xml, fh=io.StringIO(xml))
# but random unknown junk should be ignored...
xml = '\n\
\
beetle\
\
fly\
/whale2020-12-21T00:01:00Z\
ant\
'
s = Sitemap()
i = s.parse_xml(fh=io.StringIO(xml))
self.assertEqual(len(i.resources), 1)
def test_12_parse_multi_loc(self):
xml_start = '\n\
\
'
xml_end = '2012-03-14T18:37:36Z'
s = Sitemap()
two_loc = '/tmp/rs_test/src/file_a/tmp/rs_test/src/file_b'
self.assertRaises(SitemapParseError, s.parse_xml,
io.StringIO(xml_start + two_loc + xml_end))
mt_loc = ''
self.assertRaises(SitemapParseError, s.parse_xml,
io.StringIO(xml_start + mt_loc + xml_end))
mt_loc_att = ''
self.assertRaises(SitemapParseError, s.parse_xml,
io.StringIO(xml_start + mt_loc_att + xml_end))
def test_13_parse_multi_lastmod(self):
xml_start = '\n\
\
uri:a'
xml_end = ''
s = Sitemap()
two_lastmod = '2013-01-012013-01-02'
self.assertRaises(SitemapParseError, s.parse_xml,
io.StringIO(xml_start + two_lastmod + xml_end))
# While it not ideal to omit, is not required and
# thus either empty lastmod or lastmod with just an attribute
# and no content are not ambiguous and thus should be accepted
# with resulting None for resource.lastmod
mt_lastmod = ''
i = s.parse_xml(fh=io.StringIO(xml_start + mt_lastmod + xml_end))
self.assertEqual(s.resources_created, 1)
self.assertEqual(i.resources[0].lastmod, None)
mt_lastmod_att = ''
i = s.parse_xml(fh=io.StringIO(xml_start + mt_lastmod_att + xml_end))
self.assertEqual(s.resources_created, 1)
self.assertEqual(i.resources[0].lastmod, None)
def test_14_parse_multi(self):
xml_start = '\n\
'
xml_end = 'uri:a'
s = Sitemap()
two_md = ''
self.assertRaises(SitemapParseError, s.parse_xml,
io.StringIO(xml_start + two_md + xml_end))
def test_15_parse_illformed(self):
s = Sitemap()
# ExpatError in python2.6, ParserError in 2.7,3.x
self.assertRaises(xml.etree.ElementTree.ParseError, s.parse_xml,
io.StringIO('not xml'))
self.assertRaises(xml.etree.ElementTree.ParseError, s.parse_xml,
io.StringIO('something'))
def test_16_parse_valid_xml_but_other(self):
s = Sitemap()
self.assertRaises(SitemapParseError, s.parse_xml, io.StringIO(
' '))
self.assertRaises(SitemapParseError, s.parse_xml, io.StringIO(
' '))
def test_17_parse_sitemapindex_as_sitemap(self):
s = Sitemap()
self.assertRaises(SitemapIndexError, s.parse_xml, io.StringIO(
' '), sitemapindex=False)
def test_18_parse_with_rs_ln_on_resource(self):
xml = '\n\
\
\
\
http://example.com/file_a2012-03-14T18:37:36Z\
\
\
\
\
\
\
http://example.com/file_b2012-03-14T18:37:36Z\
\
\
'
s = Sitemap()
rc = s.parse_xml(fh=io.StringIO(xml))
self.assertFalse(s.parsed_index, 'was a sitemap')
self.assertEqual(s.resources_created, 2, 'got 2 resources')
i = iter(rc)
r1 = next(i)
r2 = next(i)
self.assertEqual(r1.uri, 'http://example.com/file_a')
self.assertEqual(r1.ln[0]['rel'], 'duplicate')
self.assertEqual(r1.ln[0]['href'], 'http://mirror1.example.com/res1')
self.assertEqual(r1.ln[0]['modified'], '2013-01-02')
self.assertEqual(r1.ln[0]['pri'], 1)
self.assertEqual(r2.uri, 'http://example.com/file_b')
def test_19_parse_with_bad_rs_ln(self):
xmlstart = '\n\
\
\
http://example.com/file_a'
xmlend = ''
s = Sitemap()
#
# missing href
xml = xmlstart + '' + xmlend
self.assertRaises(SitemapParseError, s.parse_xml, fh=io.StringIO(xml))
# missing rel
xml = xmlstart + '' + xmlend
self.assertRaises(SitemapParseError, s.parse_xml, fh=io.StringIO(xml))
# bad length
xml = xmlstart + '' + xmlend
self.assertRaises(SitemapParseError, s.parse_xml, fh=io.StringIO(xml))
# bad pri
xml = xmlstart + '' + xmlend
self.assertRaises(SitemapParseError, s.parse_xml, fh=io.StringIO(xml))
xml = xmlstart + '' + xmlend
self.assertRaises(SitemapParseError, s.parse_xml, fh=io.StringIO(xml))
xml = xmlstart + '' + xmlend
self.assertRaises(SitemapParseError, s.parse_xml, fh=io.StringIO(xml))
# and finally OK with errors fixes
xml = xmlstart + '' + xmlend
rc = s.parse_xml(fh=io.StringIO(xml))
self.assertEqual(len(rc.resources), 1,
'good at last, extra attribute ignored')
def test_20_parse_sitemapindex_empty(self):
s = Sitemap()
si = s.parse_xml(fh=io.StringIO(
' '), sitemapindex=True)
self.assertTrue(s.parsed_index, 'was a sitemapindex')
self.assertEqual(len(si.resources), 0, '0 sitemaps')
def test_21_parse_sitemapindex(self):
s = Sitemap()
si = s.parse_xml(fh=io.StringIO(
'aaabbb'), sitemapindex=True)
self.assertEqual(len(si.resources), 2, '2 sitemaps')
sms = sorted(si.uris())
self.assertEqual(sms, ['aaa', 'bbb'])
# add a couple more
s.parse_xml(fh=io.StringIO(
'ccdd'), resources=si)
self.assertTrue(s.parsed_index, 'was a sitemapindex')
self.assertEqual(len(si.resources), 4, '4 sitemaps total')
sms = sorted(si.uris())
self.assertEqual(sms, ['aaa', 'bbb', 'cc', 'dd'])
def test_22_parse_sitemapindex_file(self):
s = Sitemap()
fh = open('tests/testdata/sitemapindex1/sitemap.xml', 'r')
si = s.parse_xml(fh=fh, sitemapindex=True)
self.assertTrue(s.parsed_index, 'was a sitemapindex')
self.assertEqual(len(si.resources), 3, '3 sitemaps')
sms = sorted(si.uris())
self.assertEqual(sms, ['http://localhost:8888/sitemap00000.xml',
'http://localhost:8888/sitemap00001.xml', 'http://localhost:8888/sitemap00002.xml'])
# self.assertEqual( si.resources['http://localhost:8888/sitemap00000.xml'].lastmod, '2012-06-13T18:09:13Z' )
def test_21_parse_multi_sitemapindex(self):
s = Sitemap()
fh = open('tests/testdata/sitemapindex2/sitemap.xml', 'r')
si = s.parse_xml(fh=fh, sitemapindex=True)
self.assertTrue(s.parsed_index, 'was a sitemapindex')
self.assertEqual(len(si.resources), 3, '3 sitemaps listed')
def test_30_parse_change_list(self):
xml = '\n\
\
/tmp/rs_test/src/file_a2012-03-14T18:37:36Z\
/tmp/rs_test/src/file_b2012-03-14T18:37:36Z\
'
s = Sitemap()
s.resource_class = Resource
c = s.parse_xml(fh=io.StringIO(xml))
self.assertEqual(s.resources_created, 2, 'got 2 resources')
i = iter(c)
r1 = next(i)
self.assertEqual(r1.uri, '/tmp/rs_test/src/file_a')
self.assertEqual(r1.change, 'updated')
r2 = next(i)
self.assertEqual(r2.uri, '/tmp/rs_test/src/file_b')
self.assertEqual(r2.change, None)
def test_31_resource_from_etree(self):
"""Test resource_from_etree method."""
# multiple
xml = '''
\
a_name
another_name_oops
'''
et = parse(io.StringIO(xml))
self.assertRaises(SitemapParseError, Sitemap().resource_from_etree, et, Resource)
# no
xml = '''
\
'''
et = parse(io.StringIO(xml))
self.assertRaises(SitemapParseError, Sitemap().resource_from_etree, et, Resource)
# muktiple not allowed
xml = '''
\
a_name
'''
et = parse(io.StringIO(xml))
self.assertRaises(SitemapParseError, Sitemap().resource_from_etree, et, Resource)
# warn is hash invalid
with LogCapture() as lc:
xml = '''
\
a_name
'''
et = parse(io.StringIO(xml))
Sitemap().resource_from_etree(et, Resource)
self.assertIn('Ignored unsupported hash type UNKNOWN-TYPE', lc.records[-1].msg)
def test_32_md_from_etree(self):
"""Test md_from_etree method."""
# Warning for unknwon capability
with LogCapture() as lc:
xml = '''
'''
et = parse(io.StringIO(xml)).getroot()
Sitemap().md_from_etree(et)
self.assertIn("Unknown capability name 'WHY'", lc.records[-1].msg)
# Bad value for change is an error
with LogCapture() as lc:
xml = '''
'''
et = parse(io.StringIO(xml)).getroot()
self.assertRaises(SitemapParseError, Sitemap().md_from_etree, et)
# length must be an integer
with LogCapture() as lc:
xml = '''
'''
et = parse(io.StringIO(xml)).getroot()
self.assertRaises(SitemapParseError, Sitemap().md_from_etree, et)