"""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)