diff options
Diffstat (limited to 'tests/test_rss_feeds.py')
| -rw-r--r-- | tests/test_rss_feeds.py | 308 |
1 files changed, 166 insertions, 142 deletions
diff --git a/tests/test_rss_feeds.py b/tests/test_rss_feeds.py index f67ed40..d976579 100644 --- a/tests/test_rss_feeds.py +++ b/tests/test_rss_feeds.py @@ -1,156 +1,180 @@ -# -*- coding: utf-8 -*- - -from __future__ import unicode_literals, absolute_import - import os -import sys - - +import re from collections import defaultdict from io import StringIO -import os -import re -import unittest +from unittest import mock import dateutil.tz +import pytest from lxml import etree -import mock - -from .base import LocaleSupportInTesting -import nikola -fake_conf = defaultdict(str) -fake_conf['TIMEZONE'] = 'UTC' -fake_conf['__tzinfo__'] = dateutil.tz.tzutc() -fake_conf['DEFAULT_LANG'] = 'en' -fake_conf['TRANSLATIONS'] = {'en': ''} -fake_conf['BASE_URL'] = 'http://some.blog/' -fake_conf['BLOG_AUTHOR'] = nikola.nikola.utils.TranslatableSetting('BLOG_AUTHOR', 'Nikola Tesla', ['en']) -fake_conf['TRANSLATIONS_PATTERN'] = '{path}.{lang}.{ext}' - - -class FakeCompiler(object): +from nikola.nikola import Nikola, Post +from nikola.utils import LocaleBorg, TranslatableSetting + + +def test_feed_is_valid(rss_feed_content, rss_schema): + """ + A testcase to check if the generated feed is valid. + + Validation can be tested with W3 FEED Validator that can be found + at http://feedvalidator.org + """ + document = etree.parse(StringIO(rss_feed_content)) + + assert rss_schema.validate(document) + + +@pytest.fixture +def rss_schema(rss_schema_filename): + with open(rss_schema_filename, "r") as rss_schema_file: + xmlschema_doc = etree.parse(rss_schema_file) + + return etree.XMLSchema(xmlschema_doc) + + +@pytest.fixture +def rss_schema_filename(test_dir): + return os.path.join(test_dir, "data", "rss-2_0.xsd") + + +@pytest.mark.parametrize("element", ["guid", "link"]) +def test_feed_items_have_valid_URLs(rss_feed_content, blog_url, element): + """ + The items in the feed need to have valid urls in link and guid. + + As stated by W3 FEED Validator: + * "link must be a full and valid URL" + * "guid must be a full URL, unless isPermaLink attribute is false: /weblog/posts/the-minimal-server.html" + """ + # This validation regex is taken from django.core.validators + url_validation_regex = re.compile( + r"^(?:http|ftp)s?://" # http:// or https:// + # domain... + r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|" + r"localhost|" # localhost... + # ...or ipv4 + r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|" + # ...or ipv6 + r"\[?[A-F0-9]*:[A-F0-9:]+\]?)" r"(?::\d+)?" r"(?:/?|[/?]\S+)$", # optional port + re.IGNORECASE, + ) + + def is_valid_URL(url): + return url_validation_regex.match(url) is not None + + et = etree.parse(StringIO(rss_feed_content)) + channel = et.find("channel") + item = channel.find("item") + element = item.find(element) + + assert is_valid_URL(element.text) + assert blog_url in element.text + + +@pytest.fixture(autouse=True) +def localeborg(default_locale): + """ + LocaleBorg with default settings + """ + LocaleBorg.reset() + LocaleBorg.initialize({}, default_locale) + try: + yield + finally: + LocaleBorg.reset() + + +@pytest.fixture +def rss_feed_content(blog_url, config, default_locale): + default_post = { + "title": "post title", + "slug": "awesome_article", + "date": "2012-10-01 22:41", + "author": None, + "tags": "tags", + "link": "link", + "description": "description", + "enclosure": "http://www.example.org/foo.mp3", + "enclosure_length": "5", + } + meta_mock = mock.Mock(return_value=(defaultdict(str, default_post), None)) + with mock.patch("nikola.post.get_meta", meta_mock): + with \ + mock.patch( + "nikola.nikola.utils.os.path.isdir", mock.Mock(return_value=True)), \ + mock.patch( + "nikola.nikola.Post.text", mock.Mock(return_value="some long text") + ): + with mock.patch( + "nikola.post.os.path.isfile", mock.Mock(return_value=True)): + example_post = Post( + "source.file", + config, + "blog_folder", + True, + {"en": ""}, + "post.tmpl", + FakeCompiler(), + ) + + filename = "testfeed.rss" + opener_mock = mock.mock_open() + + with mock.patch("nikola.nikola.io.open", opener_mock, create=True): + Nikola().generic_rss_renderer( + default_locale, + "blog_title", + blog_url, + "blog_description", + [example_post, ], + filename, + True, + False, + ) + + opener_mock.assert_called_once_with(filename, "w+", encoding="utf-8") + + # Python 3 / unicode strings workaround + # lxml will complain if the encoding is specified in the + # xml when running with unicode strings. + # We do not include this in our content. + file_content = [call[1][0] for call in opener_mock.mock_calls[2:-1]][0] + splitted_content = file_content.split("\n") + # encoding_declaration = splitted_content[0] + content_without_encoding_declaration = splitted_content[1:] + yield "\n".join(content_without_encoding_declaration) + + +@pytest.fixture +def config(blog_url, default_locale): + fake_conf = defaultdict(str) + fake_conf["TIMEZONE"] = "UTC" + fake_conf["__tzinfo__"] = dateutil.tz.tzutc() + fake_conf["DEFAULT_LANG"] = default_locale + fake_conf["TRANSLATIONS"] = {default_locale: ""} + fake_conf["BASE_URL"] = blog_url + fake_conf["BLOG_AUTHOR"] = TranslatableSetting( + "BLOG_AUTHOR", "Nikola Tesla", [default_locale] + ) + fake_conf["TRANSLATIONS_PATTERN"] = "{path}.{lang}.{ext}" + + return fake_conf + + +@pytest.fixture +def blog_url(): + return "http://some.blog" + + +class FakeCompiler: demote_headers = False - compile_html = None - extension = lambda self: '.html' - name = "fake" + compile = None + + def extension(self): + return ".html" def read_metadata(*args, **kwargs): return {} def register_extra_dependencies(self, post): pass - - -class RSSFeedTest(unittest.TestCase): - def setUp(self): - LocaleSupportInTesting.initialize_locales_for_testing('unilingual') - self.blog_url = "http://some.blog" - - with mock.patch('nikola.post.get_meta', - mock.Mock(return_value=( - ({'title': 'post title', - 'slug': 'awesome_article', - 'date': '2012-10-01 22:41', - 'author': None, - 'tags': 'tags', - 'link': 'link', - 'description': 'description', - 'enclosure': 'http://www.example.org/foo.mp3'}, True) - ))): - with mock.patch('nikola.nikola.utils.os.path.isdir', - mock.Mock(return_value=True)): - with mock.patch('nikola.nikola.Post.text', - mock.Mock(return_value='some long text')): - - example_post = nikola.nikola.Post('source.file', - fake_conf, - 'blog_folder', - True, - {'en': ''}, - 'post.tmpl', - FakeCompiler()) - - opener_mock = mock.mock_open() - - with mock.patch('nikola.nikola.io.open', opener_mock, create=True): - nikola.nikola.Nikola().generic_rss_renderer('en', - "blog_title", - self.blog_url, - "blog_description", - [example_post, - ], - 'testfeed.rss', - True, - False) - - opener_mock.assert_called_once_with( - 'testfeed.rss', 'w+', encoding='utf-8') - - # Python 3 / unicode strings workaround - # lxml will complain if the encoding is specified in the - # xml when running with unicode strings. - # We do not include this in our content. - file_content = [ - call[1][0] - for call in opener_mock.mock_calls[2:-1]][0] - splitted_content = file_content.split('\n') - self.encoding_declaration = splitted_content[0] - content_without_encoding_declaration = splitted_content[1:] - self.file_content = '\n'.join( - content_without_encoding_declaration) - - def tearDown(self): - pass - - def test_feed_items_have_valid_URLs(self): - '''The items in the feed need to have valid urls in link and guid.''' - # This validation regex is taken from django.core.validators - url_validation_regex = re.compile(r'^(?:http|ftp)s?://' # http:// or https:// - r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain... - r'localhost|' # localhost... - r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4 - r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6 - r'(?::\d+)?' # optional port - r'(?:/?|[/?]\S+)$', re.IGNORECASE) - - def is_valid_URL(url): - return url_validation_regex.match(url) is not None - - et = etree.parse(StringIO(self.file_content)) - channel = et.find('channel') - item = channel.find('item') - guid = item.find('guid') - link = item.find('link') - - # As stated by W3 FEED Validator: "link must be a full and valid URL" - self.assertTrue(is_valid_URL(link.text), - 'The following URL is not valid: %s' % link.text) - self.assertTrue(self.blog_url in link.text) - - # "guid must be a full URL, unless isPermaLink attribute - # is false: /weblog/posts/the-minimal-server.html " - self.assertTrue(is_valid_URL(guid.text), - 'The following URL is not valid: %s' % - guid.text) - self.assertTrue(self.blog_url in guid.text) - - def test_feed_is_valid(self): - ''' - A testcase to check if the generated feed is valid. - - Validation can be tested with W3 FEED Validator that can be found - at http://feedvalidator.org - ''' - rss_schema_filename = os.path.join(os.path.dirname(__file__), - 'rss-2_0.xsd') - with open(rss_schema_filename, 'r') as rss_schema_file: - xmlschema_doc = etree.parse(rss_schema_file) - - xmlschema = etree.XMLSchema(xmlschema_doc) - document = etree.parse(StringIO(self.file_content)) - - self.assertTrue(xmlschema.validate(document)) - -if __name__ == '__main__': - unittest.main() |
