tests.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. import datetime
  2. import warnings
  3. from xml.dom import minidom
  4. from django.contrib.syndication import feeds, views
  5. from django.core.exceptions import ImproperlyConfigured
  6. from django.test import TestCase
  7. from django.utils import tzinfo
  8. from django.utils.feedgenerator import rfc2822_date, rfc3339_date
  9. from models import Entry
  10. class FeedTestCase(TestCase):
  11. fixtures = ['feeddata.json']
  12. def assertChildNodes(self, elem, expected):
  13. actual = set([n.nodeName for n in elem.childNodes])
  14. expected = set(expected)
  15. self.assertEqual(actual, expected)
  16. def assertChildNodeContent(self, elem, expected):
  17. for k, v in expected.items():
  18. self.assertEqual(
  19. elem.getElementsByTagName(k)[0].firstChild.wholeText, v)
  20. def assertCategories(self, elem, expected):
  21. self.assertEqual(set(i.firstChild.wholeText for i in elem.childNodes if i.nodeName == 'category'), set(expected));
  22. ######################################
  23. # Feed view
  24. ######################################
  25. class SyndicationFeedTest(FeedTestCase):
  26. """
  27. Tests for the high-level syndication feed framework.
  28. """
  29. def test_rss2_feed(self):
  30. """
  31. Test the structure and content of feeds generated by Rss201rev2Feed.
  32. """
  33. response = self.client.get('/syndication/rss2/')
  34. doc = minidom.parseString(response.content)
  35. # Making sure there's only 1 `rss` element and that the correct
  36. # RSS version was specified.
  37. feed_elem = doc.getElementsByTagName('rss')
  38. self.assertEqual(len(feed_elem), 1)
  39. feed = feed_elem[0]
  40. self.assertEqual(feed.getAttribute('version'), '2.0')
  41. # Making sure there's only one `channel` element w/in the
  42. # `rss` element.
  43. chan_elem = feed.getElementsByTagName('channel')
  44. self.assertEqual(len(chan_elem), 1)
  45. chan = chan_elem[0]
  46. # Find the last build date
  47. d = Entry.objects.latest('date').date
  48. ltz = tzinfo.LocalTimezone(d)
  49. last_build_date = rfc2822_date(d.replace(tzinfo=ltz))
  50. self.assertChildNodes(chan, ['title', 'link', 'description', 'language', 'lastBuildDate', 'item', 'atom:link', 'ttl', 'copyright', 'category'])
  51. self.assertChildNodeContent(chan, {
  52. 'title': 'My blog',
  53. 'description': 'A more thorough description of my blog.',
  54. 'link': 'http://example.com/blog/',
  55. 'language': 'en',
  56. 'lastBuildDate': last_build_date,
  57. #'atom:link': '',
  58. 'ttl': '600',
  59. 'copyright': 'Copyright (c) 2007, Sally Smith',
  60. })
  61. self.assertCategories(chan, ['python', 'django']);
  62. # Ensure the content of the channel is correct
  63. self.assertChildNodeContent(chan, {
  64. 'title': 'My blog',
  65. 'link': 'http://example.com/blog/',
  66. })
  67. # Check feed_url is passed
  68. self.assertEqual(
  69. chan.getElementsByTagName('atom:link')[0].getAttribute('href'),
  70. 'http://example.com/syndication/rss2/'
  71. )
  72. # Find the pubdate of the first feed item
  73. d = Entry.objects.get(pk=1).date
  74. ltz = tzinfo.LocalTimezone(d)
  75. pub_date = rfc2822_date(d.replace(tzinfo=ltz))
  76. items = chan.getElementsByTagName('item')
  77. self.assertEqual(len(items), Entry.objects.count())
  78. self.assertChildNodeContent(items[0], {
  79. 'title': 'My first entry',
  80. 'description': 'Overridden description: My first entry',
  81. 'link': 'http://example.com/blog/1/',
  82. 'guid': 'http://example.com/blog/1/',
  83. 'pubDate': pub_date,
  84. 'author': 'test@example.com (Sally Smith)',
  85. })
  86. self.assertCategories(items[0], ['python', 'testing']);
  87. for item in items:
  88. self.assertChildNodes(item, ['title', 'link', 'description', 'guid', 'category', 'pubDate', 'author'])
  89. def test_rss091_feed(self):
  90. """
  91. Test the structure and content of feeds generated by RssUserland091Feed.
  92. """
  93. response = self.client.get('/syndication/rss091/')
  94. doc = minidom.parseString(response.content)
  95. # Making sure there's only 1 `rss` element and that the correct
  96. # RSS version was specified.
  97. feed_elem = doc.getElementsByTagName('rss')
  98. self.assertEqual(len(feed_elem), 1)
  99. feed = feed_elem[0]
  100. self.assertEqual(feed.getAttribute('version'), '0.91')
  101. # Making sure there's only one `channel` element w/in the
  102. # `rss` element.
  103. chan_elem = feed.getElementsByTagName('channel')
  104. self.assertEqual(len(chan_elem), 1)
  105. chan = chan_elem[0]
  106. self.assertChildNodes(chan, ['title', 'link', 'description', 'language', 'lastBuildDate', 'item', 'atom:link', 'ttl', 'copyright', 'category'])
  107. # Ensure the content of the channel is correct
  108. self.assertChildNodeContent(chan, {
  109. 'title': 'My blog',
  110. 'link': 'http://example.com/blog/',
  111. })
  112. self.assertCategories(chan, ['python', 'django'])
  113. # Check feed_url is passed
  114. self.assertEqual(
  115. chan.getElementsByTagName('atom:link')[0].getAttribute('href'),
  116. 'http://example.com/syndication/rss091/'
  117. )
  118. items = chan.getElementsByTagName('item')
  119. self.assertEqual(len(items), Entry.objects.count())
  120. self.assertChildNodeContent(items[0], {
  121. 'title': 'My first entry',
  122. 'description': 'Overridden description: My first entry',
  123. 'link': 'http://example.com/blog/1/',
  124. })
  125. for item in items:
  126. self.assertChildNodes(item, ['title', 'link', 'description'])
  127. self.assertCategories(item, [])
  128. def test_atom_feed(self):
  129. """
  130. Test the structure and content of feeds generated by Atom1Feed.
  131. """
  132. response = self.client.get('/syndication/atom/')
  133. feed = minidom.parseString(response.content).firstChild
  134. self.assertEqual(feed.nodeName, 'feed')
  135. self.assertEqual(feed.getAttribute('xmlns'), 'http://www.w3.org/2005/Atom')
  136. self.assertChildNodes(feed, ['title', 'subtitle', 'link', 'id', 'updated', 'entry', 'rights', 'category', 'author'])
  137. for link in feed.getElementsByTagName('link'):
  138. if link.getAttribute('rel') == 'self':
  139. self.assertEqual(link.getAttribute('href'), 'http://example.com/syndication/atom/')
  140. entries = feed.getElementsByTagName('entry')
  141. self.assertEqual(len(entries), Entry.objects.count())
  142. for entry in entries:
  143. self.assertChildNodes(entry, ['title', 'link', 'id', 'summary', 'category', 'updated', 'rights', 'author'])
  144. summary = entry.getElementsByTagName('summary')[0]
  145. self.assertEqual(summary.getAttribute('type'), 'html')
  146. def test_custom_feed_generator(self):
  147. response = self.client.get('/syndication/custom/')
  148. feed = minidom.parseString(response.content).firstChild
  149. self.assertEqual(feed.nodeName, 'feed')
  150. self.assertEqual(feed.getAttribute('django'), 'rocks')
  151. self.assertChildNodes(feed, ['title', 'subtitle', 'link', 'id', 'updated', 'entry', 'spam', 'rights', 'category', 'author'])
  152. entries = feed.getElementsByTagName('entry')
  153. self.assertEqual(len(entries), Entry.objects.count())
  154. for entry in entries:
  155. self.assertEqual(entry.getAttribute('bacon'), 'yum')
  156. self.assertChildNodes(entry, ['title', 'link', 'id', 'summary', 'ministry', 'rights', 'author', 'updated', 'category'])
  157. summary = entry.getElementsByTagName('summary')[0]
  158. self.assertEqual(summary.getAttribute('type'), 'html')
  159. def test_title_escaping(self):
  160. """
  161. Tests that titles are escaped correctly in RSS feeds.
  162. """
  163. response = self.client.get('/syndication/rss2/')
  164. doc = minidom.parseString(response.content)
  165. for item in doc.getElementsByTagName('item'):
  166. link = item.getElementsByTagName('link')[0]
  167. if link.firstChild.wholeText == 'http://example.com/blog/4/':
  168. title = item.getElementsByTagName('title')[0]
  169. self.assertEqual(title.firstChild.wholeText, u'A & B < C > D')
  170. def test_naive_datetime_conversion(self):
  171. """
  172. Test that datetimes are correctly converted to the local time zone.
  173. """
  174. # Naive date times passed in get converted to the local time zone, so
  175. # check the recived zone offset against the local offset.
  176. response = self.client.get('/syndication/naive-dates/')
  177. doc = minidom.parseString(response.content)
  178. updated = doc.getElementsByTagName('updated')[0].firstChild.wholeText
  179. d = Entry.objects.latest('date').date
  180. ltz = tzinfo.LocalTimezone(d)
  181. latest = rfc3339_date(d.replace(tzinfo=ltz))
  182. self.assertEqual(updated, latest)
  183. def test_aware_datetime_conversion(self):
  184. """
  185. Test that datetimes with timezones don't get trodden on.
  186. """
  187. response = self.client.get('/syndication/aware-dates/')
  188. doc = minidom.parseString(response.content)
  189. updated = doc.getElementsByTagName('updated')[0].firstChild.wholeText
  190. self.assertEqual(updated[-6:], '+00:42')
  191. def test_feed_url(self):
  192. """
  193. Test that the feed_url can be overridden.
  194. """
  195. response = self.client.get('/syndication/feedurl/')
  196. doc = minidom.parseString(response.content)
  197. for link in doc.getElementsByTagName('link'):
  198. if link.getAttribute('rel') == 'self':
  199. self.assertEqual(link.getAttribute('href'), 'http://example.com/customfeedurl/')
  200. def test_secure_urls(self):
  201. """
  202. Test URLs are prefixed with https:// when feed is requested over HTTPS.
  203. """
  204. response = self.client.get('/syndication/rss2/', **{
  205. 'wsgi.url_scheme': 'https',
  206. })
  207. doc = minidom.parseString(response.content)
  208. chan = doc.getElementsByTagName('channel')[0]
  209. self.assertEqual(
  210. chan.getElementsByTagName('link')[0].firstChild.wholeText[0:5],
  211. 'https'
  212. )
  213. atom_link = chan.getElementsByTagName('atom:link')[0]
  214. self.assertEqual(atom_link.getAttribute('href')[0:5], 'https')
  215. for link in doc.getElementsByTagName('link'):
  216. if link.getAttribute('rel') == 'self':
  217. self.assertEqual(link.getAttribute('href')[0:5], 'https')
  218. def test_item_link_error(self):
  219. """
  220. Test that a ImproperlyConfigured is raised if no link could be found
  221. for the item(s).
  222. """
  223. self.assertRaises(ImproperlyConfigured,
  224. self.client.get,
  225. '/syndication/articles/')
  226. def test_template_feed(self):
  227. """
  228. Test that the item title and description can be overridden with
  229. templates.
  230. """
  231. response = self.client.get('/syndication/template/')
  232. doc = minidom.parseString(response.content)
  233. feed = doc.getElementsByTagName('rss')[0]
  234. chan = feed.getElementsByTagName('channel')[0]
  235. items = chan.getElementsByTagName('item')
  236. self.assertChildNodeContent(items[0], {
  237. 'title': 'Title in your templates: My first entry',
  238. 'description': 'Description in your templates: My first entry',
  239. 'link': 'http://example.com/blog/1/',
  240. })
  241. def test_add_domain(self):
  242. """
  243. Test add_domain() prefixes domains onto the correct URLs.
  244. """
  245. self.assertEqual(
  246. views.add_domain('example.com', '/foo/?arg=value'),
  247. 'http://example.com/foo/?arg=value'
  248. )
  249. self.assertEqual(
  250. views.add_domain('example.com', '/foo/?arg=value', True),
  251. 'https://example.com/foo/?arg=value'
  252. )
  253. self.assertEqual(
  254. views.add_domain('example.com', 'http://djangoproject.com/doc/'),
  255. 'http://djangoproject.com/doc/'
  256. )
  257. self.assertEqual(
  258. views.add_domain('example.com', 'https://djangoproject.com/doc/'),
  259. 'https://djangoproject.com/doc/'
  260. )
  261. self.assertEqual(
  262. views.add_domain('example.com', 'mailto:uhoh@djangoproject.com'),
  263. 'mailto:uhoh@djangoproject.com'
  264. )
  265. ######################################
  266. # Deprecated feeds
  267. ######################################
  268. class DeprecatedSyndicationFeedTest(FeedTestCase):
  269. """
  270. Tests for the deprecated API (feed() view and the feed_dict etc).
  271. """
  272. def setUp(self):
  273. self.save_warnings_state()
  274. warnings.filterwarnings('ignore', category=DeprecationWarning,
  275. module='django.contrib.syndication.feeds')
  276. warnings.filterwarnings('ignore', category=DeprecationWarning,
  277. module='django.contrib.syndication.views')
  278. def tearDown(self):
  279. self.restore_warnings_state()
  280. def test_empty_feed_dict(self):
  281. """
  282. Test that an empty feed_dict raises a 404.
  283. """
  284. response = self.client.get('/syndication/depr-feeds-empty/aware-dates/')
  285. self.assertEqual(response.status_code, 404)
  286. def test_nonexistent_slug(self):
  287. """
  288. Test that a non-existent slug raises a 404.
  289. """
  290. response = self.client.get('/syndication/depr-feeds/foobar/')
  291. self.assertEqual(response.status_code, 404)
  292. def test_rss_feed(self):
  293. """
  294. A simple test for Rss201rev2Feed feeds generated by the deprecated
  295. system.
  296. """
  297. response = self.client.get('/syndication/depr-feeds/rss/')
  298. doc = minidom.parseString(response.content)
  299. feed = doc.getElementsByTagName('rss')[0]
  300. self.assertEqual(feed.getAttribute('version'), '2.0')
  301. chan = feed.getElementsByTagName('channel')[0]
  302. self.assertChildNodes(chan, ['title', 'link', 'description', 'language', 'lastBuildDate', 'item', 'atom:link'])
  303. items = chan.getElementsByTagName('item')
  304. self.assertEqual(len(items), Entry.objects.count())
  305. def test_complex_base_url(self):
  306. """
  307. Tests that the base url for a complex feed doesn't raise a 500
  308. exception.
  309. """
  310. response = self.client.get('/syndication/depr-feeds/complex/')
  311. self.assertEqual(response.status_code, 404)