diff options
author | Daniel Baumann <daniel@debian.org> | 2024-12-12 12:17:53 +0100 |
---|---|---|
committer | Daniel Baumann <daniel@debian.org> | 2024-12-12 12:17:53 +0100 |
commit | 11cb8088832071b55bba2aff046d1542dda46fed (patch) | |
tree | d3f24cca0040ee5587df8c9e10371d05f206f94a /tests | |
parent | Initial commit. (diff) | |
download | python-feedgen-11cb8088832071b55bba2aff046d1542dda46fed.tar.xz python-feedgen-11cb8088832071b55bba2aff046d1542dda46fed.zip |
Adding upstream version 1.0.0.upstream/1.0.0upstream
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to '')
-rw-r--r-- | tests/__init__.py | 0 | ||||
-rw-r--r-- | tests/test_entry.py | 180 | ||||
-rw-r--r-- | tests/test_extensions/__init__.py | 0 | ||||
-rw-r--r-- | tests/test_extensions/test_dc.py | 31 | ||||
-rw-r--r-- | tests/test_extensions/test_geo.py | 430 | ||||
-rw-r--r-- | tests/test_extensions/test_media.py | 83 | ||||
-rw-r--r-- | tests/test_extensions/test_podcast.py | 106 | ||||
-rw-r--r-- | tests/test_extensions/test_syndication.py | 40 | ||||
-rw-r--r-- | tests/test_extensions/test_torrent.py | 38 | ||||
-rw-r--r-- | tests/test_feed.py | 401 | ||||
-rw-r--r-- | tests/test_main.py | 37 |
11 files changed, 1346 insertions, 0 deletions
diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/__init__.py diff --git a/tests/test_entry.py b/tests/test_entry.py new file mode 100644 index 0000000..adfd8b5 --- /dev/null +++ b/tests/test_entry.py @@ -0,0 +1,180 @@ +# -*- coding: utf-8 -*- + +""" +Tests for a basic entry + +These are test cases for a basic entry. +""" + +import unittest + +from feedgen.feed import FeedGenerator + + +class TestSequenceFunctions(unittest.TestCase): + + def setUp(self): + fg = FeedGenerator() + self.feedId = 'http://example.com' + self.title = 'Some Testfeed' + + fg.id(self.feedId) + fg.title(self.title) + fg.link(href='http://lkiesow.de', rel='alternate')[0] + fg.description('...') + + fe = fg.add_entry() + fe.id('http://lernfunk.de/media/654321/1') + fe.title('The First Episode') + fe.content(u'…') + + # Use also the different name add_item + fe = fg.add_item() + fe.id('http://lernfunk.de/media/654321/1') + fe.title('The Second Episode') + fe.content(u'…') + + fe = fg.add_entry() + fe.id('http://lernfunk.de/media/654321/1') + fe.title('The Third Episode') + fe.content(u'…') + + self.fg = fg + + def test_setEntries(self): + fg2 = FeedGenerator() + fg2.entry(self.fg.entry()) + self.assertEqual(len(fg2.entry()), 3) + self.assertEqual(self.fg.entry(), fg2.entry()) + + def test_loadExtension(self): + fe = self.fg.add_item() + fe.id('1') + fe.title(u'…') + fe.content(u'…') + fe.load_extension('base') + self.assertTrue(fe.base) + self.assertTrue(self.fg.atom_str()) + + def test_checkEntryNumbers(self): + fg = self.fg + self.assertEqual(len(fg.entry()), 3) + + def test_TestEntryItems(self): + fe = self.fg.add_item() + fe.title('qwe') + self.assertEqual(fe.title(), 'qwe') + author = fe.author(email='ldoe@example.com')[0] + self.assertFalse(author.get('name')) + self.assertEqual(author.get('email'), 'ldoe@example.com') + author = fe.author(name='John Doe', email='jdoe@example.com', + replace=True)[0] + self.assertEqual(author.get('name'), 'John Doe') + self.assertEqual(author.get('email'), 'jdoe@example.com') + contributor = fe.contributor(name='John Doe', email='jdoe@ex.com')[0] + self.assertEqual(contributor, fe.contributor()[0]) + self.assertEqual(contributor.get('name'), 'John Doe') + self.assertEqual(contributor.get('email'), 'jdoe@ex.com') + link = fe.link(href='http://lkiesow.de', rel='alternate')[0] + self.assertEqual(link, fe.link()[0]) + self.assertEqual(link.get('href'), 'http://lkiesow.de') + self.assertEqual(link.get('rel'), 'alternate') + fe.guid('123') + self.assertEqual(fe.guid().get('guid'), '123') + fe.updated('2017-02-05 13:26:58+01:00') + self.assertEqual(fe.updated().year, 2017) + fe.summary('asdf') + self.assertEqual(fe.summary(), {'summary': 'asdf'}) + fe.description('asdfx') + self.assertEqual(fe.description(), 'asdfx') + fe.pubDate('2017-02-05 13:26:58+01:00') + self.assertEqual(fe.pubDate().year, 2017) + fe.rights('asdfx') + self.assertEqual(fe.rights(), 'asdfx') + source = fe.source(url='https://example.com', title='Test') + self.assertEqual(source.get('title'), 'Test') + self.assertEqual(source.get('url'), 'https://example.com') + fe.comments('asdfx') + self.assertEqual(fe.comments(), 'asdfx') + fe.enclosure(url='http://lkiesow.de', type='text/plain', length='1') + self.assertEqual(fe.enclosure().get('url'), 'http://lkiesow.de') + fe.ttl(8) + self.assertEqual(fe.ttl(), 8) + + self.fg.rss_str() + self.fg.atom_str() + + def test_checkItemNumbers(self): + fg = self.fg + self.assertEqual(len(fg.item()), 3) + + def test_checkEntryContent(self): + fg = self.fg + self.assertTrue(fg.entry()) + + def test_removeEntryByIndex(self): + fg = FeedGenerator() + self.feedId = 'http://example.com' + self.title = 'Some Testfeed' + + fe = fg.add_entry() + fe.id('http://lernfunk.de/media/654321/1') + fe.title('The Third Episode') + self.assertEqual(len(fg.entry()), 1) + fg.remove_entry(0) + self.assertEqual(len(fg.entry()), 0) + + def test_removeEntryByEntry(self): + fg = FeedGenerator() + self.feedId = 'http://example.com' + self.title = 'Some Testfeed' + + fe = fg.add_entry() + fe.id('http://lernfunk.de/media/654321/1') + fe.title('The Third Episode') + + self.assertEqual(len(fg.entry()), 1) + fg.remove_entry(fe) + self.assertEqual(len(fg.entry()), 0) + + def test_categoryHasDomain(self): + fg = FeedGenerator() + fg.title('some title') + fg.link(href='http://www.dontcare.com', rel='alternate') + fg.description('description') + fe = fg.add_entry() + fe.id('http://lernfunk.de/media/654321/1') + fe.title('some title') + fe.category([ + {'term': 'category', + 'scheme': 'http://somedomain.com/category', + 'label': 'Category', + }]) + + result = fg.rss_str() + self.assertIn(b'domain="http://somedomain.com/category"', result) + + def test_content_cdata_type(self): + fg = FeedGenerator() + fg.title('some title') + fg.id('http://lernfunk.de/media/654322/1') + fe = fg.add_entry() + fe.id('http://lernfunk.de/media/654322/1') + fe.title('some title') + fe.content('content', type='CDATA') + result = fg.atom_str() + expected = b'<content type="CDATA"><![CDATA[content]]></content>' + self.assertIn(expected, result) + + def test_summary_html_type(self): + fg = FeedGenerator() + fg.title('some title') + fg.id('http://lernfunk.de/media/654322/1') + fe = fg.add_entry() + fe.id('http://lernfunk.de/media/654322/1') + fe.title('some title') + fe.link(href='http://lernfunk.de/media/654322/1') + fe.summary('<p>summary</p>', type='html') + result = fg.atom_str() + expected = b'<summary type="html"><p>summary</p></summary>' + self.assertIn(expected, result) diff --git a/tests/test_extensions/__init__.py b/tests/test_extensions/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/test_extensions/__init__.py diff --git a/tests/test_extensions/test_dc.py b/tests/test_extensions/test_dc.py new file mode 100644 index 0000000..af7dd94 --- /dev/null +++ b/tests/test_extensions/test_dc.py @@ -0,0 +1,31 @@ +import unittest + +from feedgen.feed import FeedGenerator + + +class TestExtensionDc(unittest.TestCase): + + def setUp(self): + self.fg = FeedGenerator() + self.fg.load_extension('dc') + self.fg.title('title') + self.fg.link(href='http://example.com', rel='self') + self.fg.description('description') + + def test_entryLoadExtension(self): + fe = self.fg.add_item() + try: + fe.load_extension('dc') + except ImportError: + pass # Extension already loaded + + def test_elements(self): + for method in dir(self.fg.dc): + if method.startswith('dc_'): + m = getattr(self.fg.dc, method) + m(method) + self.assertEqual(m(), [method]) + + self.fg.id('123') + self.assertTrue(self.fg.atom_str()) + self.assertTrue(self.fg.rss_str()) diff --git a/tests/test_extensions/test_geo.py b/tests/test_extensions/test_geo.py new file mode 100644 index 0000000..5de1f80 --- /dev/null +++ b/tests/test_extensions/test_geo.py @@ -0,0 +1,430 @@ +from itertools import chain +import unittest +import warnings + +from lxml import etree + +from feedgen.feed import FeedGenerator +from feedgen.ext.geo_entry import GeoRSSPolygonInteriorWarning, GeoRSSGeometryError # noqa: E501 + + +class Geom(object): + """ + Dummy geom to make testing easier + + When we use the geo-interface we need a class with a `__geo_interface__` + property. Makes it easier for the other tests as well. + + Ultimately this could be used to generate dummy geometries for testing + a wider variety of values (e.g. with the faker library, or the hypothesis + library) + """ + + def __init__(self, geom_type, coords): + self.geom_type = geom_type + self.coords = coords + + def __str__(self): + if self.geom_type == 'Point': + + coords = '{:f} {:f}'.format( + self.coords[1], # latitude is y + self.coords[0] + ) + return coords + + elif self.geom_type == 'LineString': + + coords = ' '.join( + '{:f} {:f}'.format(vertex[1], vertex[0]) + for vertex in + self.coords + ) + return coords + + elif self.geom_type == 'Polygon': + + coords = ' '.join( + '{:f} {:f}'.format(vertex[1], vertex[0]) + for vertex in + self.coords[0] + ) + return coords + + elif self.geom_type == 'Box': + # box not really supported by GeoJSON, but it's a handy cheat here + # for testing + coords = ' '.join( + '{:f} {:f}'.format(vertex[1], vertex[0]) + for vertex in + self.coords + ) + return coords[:2] + + else: + return 'Not a supported geometry' + + @property + def __geo_interface__(self): + return { + 'type': self.geom_type, + 'coordinates': self.coords + } + + +class TestExtensionGeo(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.point = Geom('Point', [-71.05, 42.36]) + cls.line = Geom('LineString', [[-71.05, 42.36], [-71.15, 42.46]]) + cls.polygon = Geom( + 'Polygon', + [[[-71.05, 42.36], [-71.15, 42.46], [-71.15, 42.36]]] + ) + cls.box = Geom('Box', [[-71.05, 42.36], [-71.15, 42.46]]) + cls.polygon_with_interior = Geom( + 'Polygon', + [ + [ # exterior + [0, 0], + [0, 1], + [1, 1], + [1, 0], + [0, 0] + ], + [ # interior + [0.25, 0.25], + [0.25, 0.75], + [0.75, 0.75], + [0.75, 0.25], + [0.25, 0.25] + ] + ] + ) + + def setUp(self): + self.fg = FeedGenerator() + self.fg.load_extension('geo') + self.fg.title('title') + self.fg.link(href='http://example.com', rel='self') + self.fg.description('description') + + def test_point(self): + fe = self.fg.add_item() + fe.title('y') + fe.geo.point(str(self.point)) + + self.assertEqual(fe.geo.point(), str(self.point)) + + # Check that we have the item in the resulting XML + ns = {'georss': 'http://www.georss.org/georss'} + root = etree.fromstring(self.fg.rss_str()) + point = root.xpath('/rss/channel/item/georss:point/text()', + namespaces=ns) + self.assertEqual(point, [str(self.point)]) + + def test_line(self): + fe = self.fg.add_item() + fe.title('y') + fe.geo.line(str(self.line)) + + self.assertEqual(fe.geo.line(), str(self.line)) + + # Check that we have the item in the resulting XML + ns = {'georss': 'http://www.georss.org/georss'} + root = etree.fromstring(self.fg.rss_str()) + line = root.xpath( + '/rss/channel/item/georss:line/text()', + namespaces=ns + ) + self.assertEqual(line, [str(self.line)]) + + def test_polygon(self): + fe = self.fg.add_item() + fe.title('y') + fe.geo.polygon(str(self.polygon)) + + self.assertEqual(fe.geo.polygon(), str(self.polygon)) + + # Check that we have the item in the resulting XML + ns = {'georss': 'http://www.georss.org/georss'} + root = etree.fromstring(self.fg.rss_str()) + poly = root.xpath( + '/rss/channel/item/georss:polygon/text()', + namespaces=ns + ) + self.assertEqual(poly, [str(self.polygon)]) + + def test_box(self): + fe = self.fg.add_item() + fe.title('y') + fe.geo.box(str(self.box)) + + self.assertEqual(fe.geo.box(), str(self.box)) + + # Check that we have the item in the resulting XML + ns = {'georss': 'http://www.georss.org/georss'} + root = etree.fromstring(self.fg.rss_str()) + box = root.xpath( + '/rss/channel/item/georss:box/text()', + namespaces=ns + ) + self.assertEqual(box, [str(self.box)]) + + def test_featuretypetag(self): + fe = self.fg.add_item() + fe.title('y') + fe.geo.featuretypetag('city') + + self.assertEqual(fe.geo.featuretypetag(), 'city') + + # Check that we have the item in the resulting XML + ns = {'georss': 'http://www.georss.org/georss'} + root = etree.fromstring(self.fg.rss_str()) + featuretypetag = root.xpath( + '/rss/channel/item/georss:featuretypetag/text()', + namespaces=ns + ) + self.assertEqual(featuretypetag, ['city']) + + def test_relationshiptag(self): + fe = self.fg.add_item() + fe.title('y') + fe.geo.relationshiptag('is-centred-at') + + self.assertEqual(fe.geo.relationshiptag(), 'is-centred-at') + + # Check that we have the item in the resulting XML + ns = {'georss': 'http://www.georss.org/georss'} + root = etree.fromstring(self.fg.rss_str()) + relationshiptag = root.xpath( + '/rss/channel/item/georss:relationshiptag/text()', + namespaces=ns + ) + self.assertEqual(relationshiptag, ['is-centred-at']) + + def test_featurename(self): + fe = self.fg.add_item() + fe.title('y') + fe.geo.featurename('Footscray') + + self.assertEqual(fe.geo.featurename(), 'Footscray') + + # Check that we have the item in the resulting XML + ns = {'georss': 'http://www.georss.org/georss'} + root = etree.fromstring(self.fg.rss_str()) + featurename = root.xpath( + '/rss/channel/item/georss:featurename/text()', + namespaces=ns + ) + self.assertEqual(featurename, ['Footscray']) + + def test_elev(self): + fe = self.fg.add_item() + fe.title('y') + fe.geo.elev(100.3) + + self.assertEqual(fe.geo.elev(), 100.3) + + # Check that we have the item in the resulting XML + ns = {'georss': 'http://www.georss.org/georss'} + root = etree.fromstring(self.fg.rss_str()) + elev = root.xpath( + '/rss/channel/item/georss:elev/text()', + namespaces=ns + ) + self.assertEqual(elev, ['100.3']) + + def test_elev_fails_nonnumeric(self): + fe = self.fg.add_item() + fe.title('y') + + with self.assertRaises(ValueError): + fe.geo.elev('100.3') + + def test_floor(self): + fe = self.fg.add_item() + fe.title('y') + fe.geo.floor(4) + + self.assertEqual(fe.geo.floor(), 4) + + # Check that we have the item in the resulting XML + ns = {'georss': 'http://www.georss.org/georss'} + root = etree.fromstring(self.fg.rss_str()) + floor = root.xpath( + '/rss/channel/item/georss:floor/text()', + namespaces=ns + ) + self.assertEqual(floor, ['4']) + + def test_floor_fails_nonint(self): + fe = self.fg.add_item() + fe.title('y') + + with self.assertRaises(ValueError): + fe.geo.floor(100.3) + + with self.assertRaises(ValueError): + fe.geo.floor('4') + + def test_radius(self): + fe = self.fg.add_item() + fe.title('y') + fe.geo.radius(100.3) + + self.assertEqual(fe.geo.radius(), 100.3) + + # Check that we have the item in the resulting XML + ns = {'georss': 'http://www.georss.org/georss'} + root = etree.fromstring(self.fg.rss_str()) + radius = root.xpath( + '/rss/channel/item/georss:radius/text()', + namespaces=ns + ) + self.assertEqual(radius, ['100.3']) + + def test_radius_fails_nonnumeric(self): + fe = self.fg.add_item() + fe.title('y') + + with self.assertRaises(ValueError): + fe.geo.radius('100.3') + + def test_geom_from_geointerface_point(self): + fe = self.fg.add_item() + fe.title('y') + fe.geo.geom_from_geo_interface(self.point) + + self.assertEqual(fe.geo.point(), str(self.point)) + + # Check that we have the item in the resulting XML + ns = {'georss': 'http://www.georss.org/georss'} + root = etree.fromstring(self.fg.rss_str()) + point = root.xpath('/rss/channel/item/georss:point/text()', + namespaces=ns) + self.assertEqual(point, [str(self.point)]) + + coords = [float(c) for c in point[0].split()] + + try: + self.assertCountEqual( + coords, + self.point.coords + ) + except AttributeError: # was assertItemsEqual in Python 2.7 + self.assertItemsEqual( + coords, + self.point.coords + ) + + def test_geom_from_geointerface_line(self): + fe = self.fg.add_item() + fe.title('y') + fe.geo.geom_from_geo_interface(self.line) + + self.assertEqual(fe.geo.line(), str(self.line)) + + # Check that we have the item in the resulting XML + ns = {'georss': 'http://www.georss.org/georss'} + root = etree.fromstring(self.fg.rss_str()) + line = root.xpath('/rss/channel/item/georss:line/text()', + namespaces=ns) + self.assertEqual(line, [str(self.line)]) + + coords = [float(c) for c in line[0].split()] + + try: + self.assertCountEqual( + coords, + list(chain.from_iterable(self.line.coords)) + ) + except AttributeError: # was assertItemsEqual in Python 2.7 + self.assertItemsEqual( + coords, + list(chain.from_iterable(self.line.coords)) + ) + + def test_geom_from_geointerface_poly(self): + fe = self.fg.add_item() + fe.title('y') + fe.geo.geom_from_geo_interface(self.polygon) + + self.assertEqual(fe.geo.polygon(), str(self.polygon)) + + # Check that we have the item in the resulting XML + ns = {'georss': 'http://www.georss.org/georss'} + root = etree.fromstring(self.fg.rss_str()) + poly = root.xpath('/rss/channel/item/georss:polygon/text()', + namespaces=ns) + self.assertEqual(poly, [str(self.polygon)]) + + coords = [float(c) for c in poly[0].split()] + + try: + self.assertCountEqual( + coords, + list(chain.from_iterable(self.polygon.coords[0])) + ) + except AttributeError: # was assertItemsEqual in Python 2.7 + self.assertItemsEqual( + coords, + list(chain.from_iterable(self.polygon.coords[0])) + ) + + def test_geom_from_geointerface_fail_other_geom(self): + fe = self.fg.add_item() + fe.title('y') + + with self.assertRaises(GeoRSSGeometryError): + fe.geo.geom_from_geo_interface(self.box) + + def test_geom_from_geointerface_fail_requires_geo_interface(self): + fe = self.fg.add_item() + fe.title('y') + + with self.assertRaises(AttributeError): + fe.geo.geom_from_geo_interface(str(self.box)) + + def test_geom_from_geointerface_warn_poly_interior(self): + """ + Test complex polygons warn as expected. Taken from + + https://stackoverflow.com/a/3892301/379566 and + https://docs.python.org/2.7/library/warnings.html#testing-warnings + """ + fe = self.fg.add_item() + fe.title('y') + + with warnings.catch_warnings(record=True) as w: + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + # Trigger a warning. + fe.geo.geom_from_geo_interface(self.polygon_with_interior) + # Verify some things + self.assertEqual(len(w), 1) + self.assertTrue(issubclass(w[-1].category, + GeoRSSPolygonInteriorWarning)) + + self.assertEqual(fe.geo.polygon(), str(self.polygon_with_interior)) + + # Check that we have the item in the resulting XML + ns = {'georss': 'http://www.georss.org/georss'} + root = etree.fromstring(self.fg.rss_str()) + poly = root.xpath('/rss/channel/item/georss:polygon/text()', + namespaces=ns) + self.assertEqual(poly, [str(self.polygon_with_interior)]) + + coords = [float(c) for c in poly[0].split()] + + try: + self.assertCountEqual( + coords, + list(chain.from_iterable(self.polygon_with_interior.coords[0])) + ) + except AttributeError: # was assertItemsEqual in Python 2.7 + self.assertItemsEqual( + coords, + list(chain.from_iterable(self.polygon_with_interior.coords[0])) + ) diff --git a/tests/test_extensions/test_media.py b/tests/test_extensions/test_media.py new file mode 100644 index 0000000..1f5a349 --- /dev/null +++ b/tests/test_extensions/test_media.py @@ -0,0 +1,83 @@ +import unittest + +from lxml import etree + +from feedgen.feed import FeedGenerator + + +class TestExtensionMedia(unittest.TestCase): + + def setUp(self): + self.fg = FeedGenerator() + self.fg.load_extension('media') + self.fg.id('id') + self.fg.title('title') + self.fg.link(href='http://example.com', rel='self') + self.fg.description('description') + + def test_media_content(self): + fe = self.fg.add_item() + fe.id('id') + fe.title('title') + fe.content('content') + fe.media.content(url='file1.xy') + fe.media.content(url='file2.xy') + fe.media.content(url='file1.xy', group=2) + fe.media.content(url='file2.xy', group=2) + fe.media.content(url='file.xy', group=None) + + ns = {'media': 'http://search.yahoo.com/mrss/', + 'a': 'http://www.w3.org/2005/Atom'} + # Check that we have the item in the resulting RSS + root = etree.fromstring(self.fg.rss_str()) + url = root.xpath('/rss/channel/item/media:group/media:content[1]/@url', + namespaces=ns) + self.assertEqual(url, ['file1.xy', 'file1.xy']) + + # There is one without a group + url = root.xpath('/rss/channel/item/media:content[1]/@url', + namespaces=ns) + self.assertEqual(url, ['file.xy']) + + # Check that we have the item in the resulting Atom feed + root = etree.fromstring(self.fg.atom_str()) + url = root.xpath('/a:feed/a:entry/media:group/media:content[1]/@url', + namespaces=ns) + self.assertEqual(url, ['file1.xy', 'file1.xy']) + + fe.media.content(content=[], replace=True) + self.assertEqual(fe.media.content(), []) + + def test_media_thumbnail(self): + fe = self.fg.add_item() + fe.id('id') + fe.title('title') + fe.content('content') + fe.media.thumbnail(url='file1.xy') + fe.media.thumbnail(url='file2.xy') + fe.media.thumbnail(url='file1.xy', group=2) + fe.media.thumbnail(url='file2.xy', group=2) + fe.media.thumbnail(url='file.xy', group=None) + + ns = {'media': 'http://search.yahoo.com/mrss/', + 'a': 'http://www.w3.org/2005/Atom'} + # Check that we have the item in the resulting RSS + root = etree.fromstring(self.fg.rss_str()) + url = root.xpath( + '/rss/channel/item/media:group/media:thumbnail[1]/@url', + namespaces=ns) + self.assertEqual(url, ['file1.xy', 'file1.xy']) + + # There is one without a group + url = root.xpath('/rss/channel/item/media:thumbnail[1]/@url', + namespaces=ns) + self.assertEqual(url, ['file.xy']) + + # Check that we have the item in the resulting Atom feed + root = etree.fromstring(self.fg.atom_str()) + url = root.xpath('/a:feed/a:entry/media:group/media:thumbnail[1]/@url', + namespaces=ns) + self.assertEqual(url, ['file1.xy', 'file1.xy']) + + fe.media.thumbnail(thumbnail=[], replace=True) + self.assertEqual(fe.media.thumbnail(), []) diff --git a/tests/test_extensions/test_podcast.py b/tests/test_extensions/test_podcast.py new file mode 100644 index 0000000..84c8c96 --- /dev/null +++ b/tests/test_extensions/test_podcast.py @@ -0,0 +1,106 @@ +import unittest + +from lxml import etree + +from feedgen.feed import FeedGenerator + + +class TestExtensionPodcast(unittest.TestCase): + + def setUp(self): + self.fg = FeedGenerator() + self.fg.load_extension('podcast') + self.fg.title('title') + self.fg.link(href='http://example.com', rel='self') + self.fg.description('description') + + def test_category_new(self): + self.fg.podcast.itunes_category([{'cat': 'Technology', + 'sub': 'Podcasting'}]) + self.fg.podcast.itunes_explicit('no') + self.fg.podcast.itunes_complete('no') + self.fg.podcast.itunes_new_feed_url('http://example.com/new-feed.rss') + self.fg.podcast.itunes_owner('John Doe', 'john@example.com') + ns = {'itunes': 'http://www.itunes.com/dtds/podcast-1.0.dtd'} + root = etree.fromstring(self.fg.rss_str()) + cat = root.xpath('/rss/channel/itunes:category/@text', namespaces=ns) + scat = root.xpath('/rss/channel/itunes:category/itunes:category/@text', + namespaces=ns) + self.assertEqual(cat[0], 'Technology') + self.assertEqual(scat[0], 'Podcasting') + + def test_category(self): + self.fg.podcast.itunes_category('Technology', 'Podcasting') + self.fg.podcast.itunes_explicit('no') + self.fg.podcast.itunes_complete('no') + self.fg.podcast.itunes_new_feed_url('http://example.com/new-feed.rss') + self.fg.podcast.itunes_owner('John Doe', 'john@example.com') + ns = {'itunes': 'http://www.itunes.com/dtds/podcast-1.0.dtd'} + root = etree.fromstring(self.fg.rss_str()) + cat = root.xpath('/rss/channel/itunes:category/@text', namespaces=ns) + scat = root.xpath('/rss/channel/itunes:category/itunes:category/@text', + namespaces=ns) + self.assertEqual(cat[0], 'Technology') + self.assertEqual(scat[0], 'Podcasting') + + def test_podcastItems(self): + fg = self.fg + fg.podcast.itunes_author('Lars Kiesow') + fg.podcast.itunes_block('x') + fg.podcast.itunes_complete(False) + fg.podcast.itunes_explicit('no') + fg.podcast.itunes_image('x.png') + fg.podcast.itunes_subtitle('x') + fg.podcast.itunes_summary('x') + fg.podcast.itunes_type('episodic') + self.assertEqual(fg.podcast.itunes_author(), 'Lars Kiesow') + self.assertEqual(fg.podcast.itunes_block(), 'x') + self.assertEqual(fg.podcast.itunes_complete(), 'no') + self.assertEqual(fg.podcast.itunes_explicit(), 'no') + self.assertEqual(fg.podcast.itunes_image(), 'x.png') + self.assertEqual(fg.podcast.itunes_subtitle(), 'x') + self.assertEqual(fg.podcast.itunes_summary(), 'x') + self.assertEqual(fg.podcast.itunes_type(), 'episodic') + + # Check that we have the item in the resulting XML + ns = {'itunes': 'http://www.itunes.com/dtds/podcast-1.0.dtd'} + root = etree.fromstring(self.fg.rss_str()) + author = root.xpath('/rss/channel/itunes:author/text()', namespaces=ns) + self.assertEqual(author, ['Lars Kiesow']) + + def test_podcastEntryItems(self): + fe = self.fg.add_item() + fe.title('y') + fe.podcast.itunes_author('Lars Kiesow') + fe.podcast.itunes_block('x') + fe.podcast.itunes_duration('00:01:30') + fe.podcast.itunes_explicit('no') + fe.podcast.itunes_image('x.png') + fe.podcast.itunes_is_closed_captioned('yes') + fe.podcast.itunes_order(1) + fe.podcast.itunes_subtitle('x') + fe.podcast.itunes_summary('x') + fe.podcast.itunes_season(1) + fe.podcast.itunes_episode(1) + fe.podcast.itunes_title('Podcast Title') + fe.podcast.itunes_episode_type('full') + self.assertEqual(fe.podcast.itunes_author(), 'Lars Kiesow') + self.assertEqual(fe.podcast.itunes_block(), 'x') + self.assertEqual(fe.podcast.itunes_duration(), '00:01:30') + self.assertEqual(fe.podcast.itunes_explicit(), 'no') + self.assertEqual(fe.podcast.itunes_image(), 'x.png') + self.assertTrue(fe.podcast.itunes_is_closed_captioned()) + self.assertEqual(fe.podcast.itunes_order(), 1) + self.assertEqual(fe.podcast.itunes_subtitle(), 'x') + self.assertEqual(fe.podcast.itunes_summary(), 'x') + self.assertEqual(fe.podcast.itunes_season(), 1) + self.assertEqual(fe.podcast.itunes_episode(), 1) + self.assertEqual(fe.podcast.itunes_title(), 'Podcast Title') + self.assertEqual(fe.podcast.itunes_episode_type(), 'full') + + # Check that we have the item in the resulting XML + ns = {'itunes': 'http://www.itunes.com/dtds/podcast-1.0.dtd'} + root = etree.fromstring(self.fg.rss_str()) + author = root.xpath('/rss/channel/item/itunes:author/text()', + namespaces=ns) + self.assertEqual(author, ['Lars Kiesow']) diff --git a/tests/test_extensions/test_syndication.py b/tests/test_extensions/test_syndication.py new file mode 100644 index 0000000..029e100 --- /dev/null +++ b/tests/test_extensions/test_syndication.py @@ -0,0 +1,40 @@ +import unittest + +from lxml import etree + +from feedgen.feed import FeedGenerator + + +class TestExtensionSyndication(unittest.TestCase): + + SYN_NS = {'sy': 'http://purl.org/rss/1.0/modules/syndication/'} + + def setUp(self): + self.fg = FeedGenerator() + self.fg.load_extension('syndication') + self.fg.title('title') + self.fg.link(href='http://example.com', rel='self') + self.fg.description('description') + + def test_update_period(self): + for period_type in ('hourly', 'daily', 'weekly', 'monthly', 'yearly'): + self.fg.syndication.update_period(period_type) + root = etree.fromstring(self.fg.rss_str()) + a = root.xpath('/rss/channel/sy:UpdatePeriod', + namespaces=self.SYN_NS) + self.assertEqual(a[0].text, period_type) + + def test_update_frequency(self): + for frequency in (1, 100, 2000, 100000): + self.fg.syndication.update_frequency(frequency) + root = etree.fromstring(self.fg.rss_str()) + a = root.xpath('/rss/channel/sy:UpdateFrequency', + namespaces=self.SYN_NS) + self.assertEqual(a[0].text, str(frequency)) + + def test_update_base(self): + base = '2000-01-01T12:00+00:00' + self.fg.syndication.update_base(base) + root = etree.fromstring(self.fg.rss_str()) + a = root.xpath('/rss/channel/sy:UpdateBase', namespaces=self.SYN_NS) + self.assertEqual(a[0].text, base) diff --git a/tests/test_extensions/test_torrent.py b/tests/test_extensions/test_torrent.py new file mode 100644 index 0000000..230ec1a --- /dev/null +++ b/tests/test_extensions/test_torrent.py @@ -0,0 +1,38 @@ +import unittest + +from lxml import etree + +from feedgen.feed import FeedGenerator + + +class TestExtensionTorrent(unittest.TestCase): + + def setUp(self): + self.fg = FeedGenerator() + self.fg.load_extension('torrent') + self.fg.title('title') + self.fg.link(href='http://example.com', rel='self') + self.fg.description('description') + + def test_podcastEntryItems(self): + fe = self.fg.add_item() + fe.title('y') + fe.torrent.filename('file.xy') + fe.torrent.infohash('123') + fe.torrent.contentlength('23') + fe.torrent.seeds('1') + fe.torrent.peers('2') + fe.torrent.verified('1') + self.assertEqual(fe.torrent.filename(), 'file.xy') + self.assertEqual(fe.torrent.infohash(), '123') + self.assertEqual(fe.torrent.contentlength(), '23') + self.assertEqual(fe.torrent.seeds(), '1') + self.assertEqual(fe.torrent.peers(), '2') + self.assertEqual(fe.torrent.verified(), '1') + + # Check that we have the item in the resulting XML + ns = {'torrent': 'http://xmlns.ezrss.it/0.1/dtd/'} + root = etree.fromstring(self.fg.rss_str()) + filename = root.xpath('/rss/channel/item/torrent:filename/text()', + namespaces=ns) + self.assertEqual(filename, ['file.xy']) diff --git a/tests/test_feed.py b/tests/test_feed.py new file mode 100644 index 0000000..d09014d --- /dev/null +++ b/tests/test_feed.py @@ -0,0 +1,401 @@ +# -*- coding: utf-8 -*- + +""" +Tests for a basic feed + +These are test cases for a basic feed. +A basic feed does not contain entries so far. +""" + +import os +import tempfile +import unittest + +from lxml import etree + +from feedgen.ext.dc import DcEntryExtension, DcExtension +from feedgen.feed import FeedGenerator + + +class TestSequenceFunctions(unittest.TestCase): + + def setUp(self): + + fg = FeedGenerator() + + self.nsAtom = "http://www.w3.org/2005/Atom" + self.nsRss = "http://purl.org/rss/1.0/modules/content/" + + self.feedId = 'http://lernfunk.de/media/654321' + self.title = 'Some Testfeed' + + self.authorName = 'John Doe' + self.authorMail = 'john@example.de' + self.author = {'name': self.authorName, 'email': self.authorMail} + + self.linkHref = 'http://example.com' + self.linkRel = 'alternate' + + self.logo = 'http://ex.com/logo.jpg' + self.subtitle = 'This is a cool feed!' + + self.link2Href = 'http://larskiesow.de/test.atom' + self.link2Rel = 'self' + + self.language = 'en' + + self.categoryTerm = 'This category term' + self.categoryScheme = 'This category scheme' + self.categoryLabel = 'This category label' + + self.cloudDomain = 'example.com' + self.cloudPort = '4711' + self.cloudPath = '/ws/example' + self.cloudRegisterProcedure = 'registerProcedure' + self.cloudProtocol = 'SOAP 1.1' + + self.icon = "http://example.com/icon.png" + self.contributor = {'name': "Contributor Name", + 'uri': "Contributor Uri", + 'email': 'Contributor email'} + self.copyright = "The copyright notice" + self.docs = 'http://www.rssboard.org/rss-specification' + self.managingEditor = 'mail@example.com' + self.rating = '(PICS-1.1 "http://www.classify.org/safesurf/" ' + \ + '1 r (SS~~000 1))' + self.skipDays = 'Tuesday' + self.skipHours = 23 + + self.textInputTitle = "Text input title" + self.textInputDescription = "Text input description" + self.textInputName = "Text input name" + self.textInputLink = "Text input link" + + self.ttl = 900 + + self.webMaster = 'webmaster@example.com' + + fg.id(self.feedId) + fg.title(self.title) + fg.author(self.author) + fg.link(href=self.linkHref, rel=self.linkRel) + fg.logo(self.logo) + fg.subtitle(self.subtitle) + fg.link(href=self.link2Href, rel=self.link2Rel) + fg.language(self.language) + fg.cloud(domain=self.cloudDomain, port=self.cloudPort, + path=self.cloudPath, + registerProcedure=self.cloudRegisterProcedure, + protocol=self.cloudProtocol) + fg.icon(self.icon) + fg.category(term=self.categoryTerm, scheme=self.categoryScheme, + label=self.categoryLabel) + fg.contributor(self.contributor) + fg.copyright(self.copyright) + fg.docs(docs=self.docs) + fg.managingEditor(self.managingEditor) + fg.rating(self.rating) + fg.skipDays(self.skipDays) + fg.skipHours(self.skipHours) + fg.textInput(title=self.textInputTitle, + description=self.textInputDescription, + name=self.textInputName, link=self.textInputLink) + fg.ttl(self.ttl) + fg.webMaster(self.webMaster) + fg.updated('2017-02-05 13:26:58+01:00') + fg.pubDate('2017-02-05 13:26:58+01:00') + fg.generator('python-feedgen', 'x', uri='http://github.com/lkie...') + fg.image(url=self.logo, + title=self.title, + link=self.link2Href, + width='123', + height='123', + description='Example Inage') + + self.fg = fg + + def test_baseFeed(self): + fg = self.fg + + self.assertEqual(fg.id(), self.feedId) + self.assertEqual(fg.title(), self.title) + + self.assertEqual(fg.author()[0]['name'], self.authorName) + self.assertEqual(fg.author()[0]['email'], self.authorMail) + + self.assertEqual(fg.link()[0]['href'], self.linkHref) + self.assertEqual(fg.link()[0]['rel'], self.linkRel) + + self.assertEqual(fg.logo(), self.logo) + self.assertEqual(fg.subtitle(), self.subtitle) + + self.assertEqual(fg.link()[1]['href'], self.link2Href) + self.assertEqual(fg.link()[1]['rel'], self.link2Rel) + + self.assertEqual(fg.language(), self.language) + + def test_atomFeedFile(self): + fg = self.fg + fh, filename = tempfile.mkstemp() + fg.atom_file(filename=filename, pretty=True, xml_declaration=False) + + with open(filename, "r") as myfile: + atomString = myfile.read().replace('\n', '') + + self.checkAtomString(atomString) + os.close(fh) + os.remove(filename) + + def test_atomFeedString(self): + fg = self.fg + + atomString = fg.atom_str(pretty=True, xml_declaration=False) + self.checkAtomString(atomString) + + def test_rel_values_for_atom(self): + values_for_rel = [ + 'about', 'alternate', 'appendix', 'archives', 'author', 'bookmark', + 'canonical', 'chapter', 'collection', 'contents', 'copyright', + 'create-form', 'current', 'derivedfrom', 'describedby', + 'describes', 'disclosure', 'duplicate', 'edit', 'edit-form', + 'edit-media', 'enclosure', 'first', 'glossary', 'help', 'hosts', + 'hub', 'icon', 'index', 'item', 'last', 'latest-version', + 'license', 'lrdd', 'memento', 'monitor', 'monitor-group', 'next', + 'next-archive', 'nofollow', 'noreferrer', 'original', 'payment', + 'predecessor-version', 'prefetch', 'prev', 'preview', 'previous', + 'prev-archive', 'privacy-policy', 'profile', 'related', 'replies', + 'search', 'section', 'self', 'service', 'start', 'stylesheet', + 'subsection', 'successor-version', 'tag', 'terms-of-service', + 'timegate', 'timemap', 'type', 'up', 'version-history', 'via', + 'working-copy', 'working-copy-of'] + links = [{'href': '%s/%s' % (self.linkHref, + val.replace('-', '_')), 'rel': val} + for val in values_for_rel] + fg = self.fg + fg.link(links, replace=True) + atomString = fg.atom_str(pretty=True, xml_declaration=False) + feed = etree.fromstring(atomString) + nsAtom = self.nsAtom + feed_links = feed.findall("{%s}link" % nsAtom) + idx = 0 + self.assertEqual(len(links), len(feed_links)) + while idx < len(values_for_rel): + self.assertEqual(feed_links[idx].get('href'), links[idx]['href']) + self.assertEqual(feed_links[idx].get('rel'), links[idx]['rel']) + idx += 1 + + def test_rel_values_for_rss(self): + values_for_rel = [ + 'about', 'alternate', 'appendix', 'archives', 'author', 'bookmark', + 'canonical', 'chapter', 'collection', 'contents', 'copyright', + 'create-form', 'current', 'derivedfrom', 'describedby', + 'describes', 'disclosure', 'duplicate', 'edit', 'edit-form', + 'edit-media', 'enclosure', 'first', 'glossary', 'help', 'hosts', + 'hub', 'icon', 'index', 'item', 'last', 'latest-version', + 'license', 'lrdd', 'memento', 'monitor', 'monitor-group', 'next', + 'next-archive', 'nofollow', 'noreferrer', 'original', 'payment', + 'predecessor-version', 'prefetch', 'prev', 'preview', 'previous', + 'prev-archive', 'privacy-policy', 'profile', 'related', 'replies', + 'search', 'section', 'self', 'service', 'start', 'stylesheet', + 'subsection', 'successor-version', 'tag', 'terms-of-service', + 'timegate', 'timemap', 'type', 'up', 'version-history', 'via', + 'working-copy', 'working-copy-of'] + links = [{'href': '%s/%s' % (self.linkHref, + val.replace('-', '_')), 'rel': val} + for val in values_for_rel] + fg = self.fg + fg.link(links, replace=True) + rssString = fg.rss_str(pretty=True, xml_declaration=False) + feed = etree.fromstring(rssString) + channel = feed.find("channel") + nsAtom = self.nsAtom + + atom_links = channel.findall("{%s}link" % nsAtom) + # rss feed only implements atom's 'self' link + self.assertEqual(len(atom_links), 1) + self.assertEqual(atom_links[0].get('href'), + '%s/%s' % (self.linkHref, 'self')) + self.assertEqual(atom_links[0].get('rel'), 'self') + + rss_links = channel.findall('link') + # RSS only needs one URL. We use the first link for RSS: + self.assertEqual(len(rss_links), 1) + self.assertEqual( + rss_links[0].text, + '%s/%s' % (self.linkHref, 'working-copy-of'.replace('-', '_'))) + + def checkAtomString(self, atomString): + + feed = etree.fromstring(atomString) + + nsAtom = "{" + self.nsAtom + "}" + + print(nsAtom) + print(f"{nsAtom}title") + testcases = [ + ( + feed.find(f"{nsAtom}title").text, + self.title + ), ( + feed.find(f"{nsAtom}id").text, + self.feedId + ), ( + feed.find(f"{nsAtom}category").get('term'), + self.categoryTerm + ), ( + feed.find(f"{nsAtom}category").get('label'), + self.categoryLabel + ), ( + feed.find(f"{nsAtom}author").find(f"{nsAtom}name").text, + self.authorName + ), ( + feed.find(f"{nsAtom}author").find(f"{nsAtom}email").text, + self.authorMail + ), ( + feed.findall(f"{nsAtom}link")[0].get('href'), + self.linkHref + ), ( + feed.findall(f"{nsAtom}link")[0].get('rel'), + self.linkRel + ), ( + feed.findall(f"{nsAtom}link")[1].get('href'), + self.link2Href + ), ( + feed.findall(f"{nsAtom}link")[1].get('rel'), + self.link2Rel + ), ( + feed.find(f"{nsAtom}logo").text, + self.logo + ), ( + feed.find(f"{nsAtom}icon").text, + self.icon + ), ( + feed.find(f"{nsAtom}subtitle").text, + self.subtitle + ), ( + feed.find(f"{nsAtom}contributor").find(f"{nsAtom}name").text, + self.contributor['name'] + ), ( + feed.find(f"{nsAtom}contributor").find(f"{nsAtom}email").text, + self.contributor['email'] + ), ( + feed.find(f"{nsAtom}contributor").find(f"{nsAtom}uri").text, + self.contributor['uri'] + ), ( + feed.find(f"{nsAtom}rights").text, + self.copyright + )] + for actual, expected in testcases: + self.assertEqual(actual, expected) + + self.assertIsNot( + feed.find(f"{nsAtom}updated").text, + None) + + def test_rssFeedFile(self): + fg = self.fg + _, filename = tempfile.mkstemp() + fg.rss_file(filename=filename, pretty=True, xml_declaration=False) + + with open(filename, "r") as myfile: + rssString = myfile.read().replace('\n', '') + + self.checkRssString(rssString) + + def test_rssFeedString(self): + fg = self.fg + rssString = fg.rss_str(pretty=True, xml_declaration=False) + self.checkRssString(rssString) + + def test_loadPodcastExtension(self): + fg = self.fg + fg.add_entry() + fg.load_extension('podcast', atom=True, rss=True) + fg.add_entry() + + def test_loadDcExtension(self): + fg = self.fg + fg.add_entry() + fg.load_extension('dc', atom=True, rss=True) + fg.add_entry() + + def test_extensionAlreadyLoaded(self): + fg = self.fg + fg.load_extension('dc', atom=True, rss=True) + with self.assertRaises(ImportError): + fg.load_extension('dc') + + def test_registerCustomExtension(self): + fg = self.fg + fg.add_entry() + fg.register_extension('dc', DcExtension, DcEntryExtension) + fg.add_entry() + + def checkRssString(self, rssString): + + feed = etree.fromstring(rssString) + nsAtom = self.nsAtom + + ch = feed.find("channel") + self.assertIsNot(ch, None) + + self.assertEqual(ch.find("title").text, + self.title) + self.assertEqual(ch.find("description").text, + self.subtitle) + self.assertIsNot(ch.find("lastBuildDate").text, + None) + self.assertEqual(ch.find("docs").text, + "http://www.rssboard.org/rss-specification") + self.assertEqual(ch.find("generator").text, + "python-feedgen") + self.assertEqual(ch.findall("{%s}link" % nsAtom)[0].get('href'), + self.link2Href) + self.assertEqual(ch.findall("{%s}link" % nsAtom)[0].get('rel'), + self.link2Rel) + self.assertEqual(ch.find("image").find("url").text, + self.logo) + self.assertEqual(ch.find("image").find("title").text, + self.title) + self.assertEqual(ch.find("image").find("link").text, + self.link2Href) + self.assertEqual(ch.find("category").text, + self.categoryLabel) + self.assertEqual(ch.find("cloud").get('domain'), + self.cloudDomain) + self.assertEqual(ch.find("cloud").get('port'), + self.cloudPort) + self.assertEqual(ch.find("cloud").get('path'), + self.cloudPath) + self.assertEqual(ch.find("cloud").get('registerProcedure'), + self.cloudRegisterProcedure) + self.assertEqual(ch.find("cloud").get('protocol'), + self.cloudProtocol) + self.assertEqual(ch.find("copyright").text, + self.copyright) + self.assertEqual(ch.find("docs").text, + self.docs) + self.assertEqual(ch.find("managingEditor").text, + self.managingEditor) + self.assertEqual(ch.find("rating").text, + self.rating) + self.assertEqual(ch.find("skipDays").find("day").text, + self.skipDays) + self.assertEqual(int(ch.find("skipHours").find("hour").text), + self.skipHours) + self.assertEqual(ch.find("textInput").get('title'), + self.textInputTitle) + self.assertEqual(ch.find("textInput").get('description'), + self.textInputDescription) + self.assertEqual(ch.find("textInput").get('name'), + self.textInputName) + self.assertEqual(ch.find("textInput").get('link'), + self.textInputLink) + self.assertEqual(int(ch.find("ttl").text), + self.ttl) + self.assertEqual(ch.find("webMaster").text, + self.webMaster) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_main.py b/tests/test_main.py new file mode 100644 index 0000000..af7e981 --- /dev/null +++ b/tests/test_main.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- + +''' +Tests for feedgen main +''' + +import os +import sys +import tempfile +import unittest + +from feedgen import __main__ + + +class TestSequenceFunctions(unittest.TestCase): + + def test_usage(self): + sys.argv = ['feedgen'] + with self.assertRaises(SystemExit) as e: + __main__.main() + self.assertEqual(e.exception.code, None) + + def test_feed(self): + for ftype in 'rss', 'atom', 'podcast', 'torrent', 'dc.rss', \ + 'dc.atom', 'syndication.rss', 'syndication.atom': + sys.argv = ['feedgen', ftype] + __main__.main() + + def test_file(self): + for extemsion in '.atom', '.rss': + fh, filename = tempfile.mkstemp(extemsion) + sys.argv = ['feedgen', filename] + try: + __main__.main() + finally: + os.close(fh) + os.remove(filename) |