tests.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. from __future__ import absolute_import, unicode_literals
  2. from xml.dom import minidom
  3. from django.contrib.syndication import views
  4. from django.core.exceptions import ImproperlyConfigured
  5. from django.test import TestCase
  6. from django.utils import tzinfo
  7. from django.utils.feedgenerator import rfc2822_date, rfc3339_date
  8. from .models import Entry
  9. class FeedTestCase(TestCase):
  10. fixtures = ['feeddata.json']
  11. def assertChildNodes(self, elem, expected):
  12. actual = set([n.nodeName for n in elem.childNodes])
  13. expected = set(expected)
  14. self.assertEqual(actual, expected)
  15. def assertChildNodeContent(self, elem, expected):
  16. for k, v in expected.items():
  17. self.assertEqual(
  18. elem.getElementsByTagName(k)[0].firstChild.wholeText, v)
  19. def assertCategories(self, elem, expected):
  20. self.assertEqual(set(i.firstChild.wholeText for i in elem.childNodes if i.nodeName == 'category'), set(expected))
  21. ######################################
  22. # Feed view
  23. ######################################
  24. class SyndicationFeedTest(FeedTestCase):
  25. """
  26. Tests for the high-level syndication feed framework.
  27. """
  28. urls = 'regressiontests.syndication.urls'
  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. # Assert that <guid> does not have any 'isPermaLink' attribute
  90. self.assertIsNone(item.getElementsByTagName(
  91. 'guid')[0].attributes.get('isPermaLink'))
  92. def test_rss2_feed_guid_permalink_false(self):
  93. """
  94. Test if the 'isPermaLink' attribute of <guid> element of an item
  95. in the RSS feed is 'false'.
  96. """
  97. response = self.client.get(
  98. '/syndication/rss2/guid_ispermalink_false/')
  99. doc = minidom.parseString(response.content)
  100. chan = doc.getElementsByTagName(
  101. 'rss')[0].getElementsByTagName('channel')[0]
  102. items = chan.getElementsByTagName('item')
  103. for item in items:
  104. self.assertEqual(
  105. item.getElementsByTagName('guid')[0].attributes.get(
  106. 'isPermaLink').value, "false")
  107. def test_rss2_feed_guid_permalink_true(self):
  108. """
  109. Test if the 'isPermaLink' attribute of <guid> element of an item
  110. in the RSS feed is 'true'.
  111. """
  112. response = self.client.get(
  113. '/syndication/rss2/guid_ispermalink_true/')
  114. doc = minidom.parseString(response.content)
  115. chan = doc.getElementsByTagName(
  116. 'rss')[0].getElementsByTagName('channel')[0]
  117. items = chan.getElementsByTagName('item')
  118. for item in items:
  119. self.assertEqual(
  120. item.getElementsByTagName('guid')[0].attributes.get(
  121. 'isPermaLink').value, "true")
  122. def test_rss091_feed(self):
  123. """
  124. Test the structure and content of feeds generated by RssUserland091Feed.
  125. """
  126. response = self.client.get('/syndication/rss091/')
  127. doc = minidom.parseString(response.content)
  128. # Making sure there's only 1 `rss` element and that the correct
  129. # RSS version was specified.
  130. feed_elem = doc.getElementsByTagName('rss')
  131. self.assertEqual(len(feed_elem), 1)
  132. feed = feed_elem[0]
  133. self.assertEqual(feed.getAttribute('version'), '0.91')
  134. # Making sure there's only one `channel` element w/in the
  135. # `rss` element.
  136. chan_elem = feed.getElementsByTagName('channel')
  137. self.assertEqual(len(chan_elem), 1)
  138. chan = chan_elem[0]
  139. self.assertChildNodes(chan, ['title', 'link', 'description', 'language', 'lastBuildDate', 'item', 'atom:link', 'ttl', 'copyright', 'category'])
  140. # Ensure the content of the channel is correct
  141. self.assertChildNodeContent(chan, {
  142. 'title': 'My blog',
  143. 'link': 'http://example.com/blog/',
  144. })
  145. self.assertCategories(chan, ['python', 'django'])
  146. # Check feed_url is passed
  147. self.assertEqual(
  148. chan.getElementsByTagName('atom:link')[0].getAttribute('href'),
  149. 'http://example.com/syndication/rss091/'
  150. )
  151. items = chan.getElementsByTagName('item')
  152. self.assertEqual(len(items), Entry.objects.count())
  153. self.assertChildNodeContent(items[0], {
  154. 'title': 'My first entry',
  155. 'description': 'Overridden description: My first entry',
  156. 'link': 'http://example.com/blog/1/',
  157. })
  158. for item in items:
  159. self.assertChildNodes(item, ['title', 'link', 'description'])
  160. self.assertCategories(item, [])
  161. def test_atom_feed(self):
  162. """
  163. Test the structure and content of feeds generated by Atom1Feed.
  164. """
  165. response = self.client.get('/syndication/atom/')
  166. feed = minidom.parseString(response.content).firstChild
  167. self.assertEqual(feed.nodeName, 'feed')
  168. self.assertEqual(feed.getAttribute('xmlns'), 'http://www.w3.org/2005/Atom')
  169. self.assertChildNodes(feed, ['title', 'subtitle', 'link', 'id', 'updated', 'entry', 'rights', 'category', 'author'])
  170. for link in feed.getElementsByTagName('link'):
  171. if link.getAttribute('rel') == 'self':
  172. self.assertEqual(link.getAttribute('href'), 'http://example.com/syndication/atom/')
  173. entries = feed.getElementsByTagName('entry')
  174. self.assertEqual(len(entries), Entry.objects.count())
  175. for entry in entries:
  176. self.assertChildNodes(entry, ['title', 'link', 'id', 'summary', 'category', 'updated', 'rights', 'author'])
  177. summary = entry.getElementsByTagName('summary')[0]
  178. self.assertEqual(summary.getAttribute('type'), 'html')
  179. def test_custom_feed_generator(self):
  180. response = self.client.get('/syndication/custom/')
  181. feed = minidom.parseString(response.content).firstChild
  182. self.assertEqual(feed.nodeName, 'feed')
  183. self.assertEqual(feed.getAttribute('django'), 'rocks')
  184. self.assertChildNodes(feed, ['title', 'subtitle', 'link', 'id', 'updated', 'entry', 'spam', 'rights', 'category', 'author'])
  185. entries = feed.getElementsByTagName('entry')
  186. self.assertEqual(len(entries), Entry.objects.count())
  187. for entry in entries:
  188. self.assertEqual(entry.getAttribute('bacon'), 'yum')
  189. self.assertChildNodes(entry, ['title', 'link', 'id', 'summary', 'ministry', 'rights', 'author', 'updated', 'category'])
  190. summary = entry.getElementsByTagName('summary')[0]
  191. self.assertEqual(summary.getAttribute('type'), 'html')
  192. def test_title_escaping(self):
  193. """
  194. Tests that titles are escaped correctly in RSS feeds.
  195. """
  196. response = self.client.get('/syndication/rss2/')
  197. doc = minidom.parseString(response.content)
  198. for item in doc.getElementsByTagName('item'):
  199. link = item.getElementsByTagName('link')[0]
  200. if link.firstChild.wholeText == 'http://example.com/blog/4/':
  201. title = item.getElementsByTagName('title')[0]
  202. self.assertEqual(title.firstChild.wholeText, 'A &amp; B &lt; C &gt; D')
  203. def test_naive_datetime_conversion(self):
  204. """
  205. Test that datetimes are correctly converted to the local time zone.
  206. """
  207. # Naive date times passed in get converted to the local time zone, so
  208. # check the recived zone offset against the local offset.
  209. response = self.client.get('/syndication/naive-dates/')
  210. doc = minidom.parseString(response.content)
  211. updated = doc.getElementsByTagName('updated')[0].firstChild.wholeText
  212. d = Entry.objects.latest('date').date
  213. ltz = tzinfo.LocalTimezone(d)
  214. latest = rfc3339_date(d.replace(tzinfo=ltz))
  215. self.assertEqual(updated, latest)
  216. def test_aware_datetime_conversion(self):
  217. """
  218. Test that datetimes with timezones don't get trodden on.
  219. """
  220. response = self.client.get('/syndication/aware-dates/')
  221. doc = minidom.parseString(response.content)
  222. updated = doc.getElementsByTagName('updated')[0].firstChild.wholeText
  223. self.assertEqual(updated[-6:], '+00:42')
  224. def test_feed_last_modified_time(self):
  225. response = self.client.get('/syndication/naive-dates/')
  226. self.assertEqual(response['Last-Modified'], 'Thu, 03 Jan 2008 19:30:00 GMT')
  227. # No last-modified when feed has no item_pubdate
  228. response = self.client.get('/syndication/no_pubdate/')
  229. self.assertFalse(response.has_header('Last-Modified'))
  230. def test_feed_url(self):
  231. """
  232. Test that the feed_url can be overridden.
  233. """
  234. response = self.client.get('/syndication/feedurl/')
  235. doc = minidom.parseString(response.content)
  236. for link in doc.getElementsByTagName('link'):
  237. if link.getAttribute('rel') == 'self':
  238. self.assertEqual(link.getAttribute('href'), 'http://example.com/customfeedurl/')
  239. def test_secure_urls(self):
  240. """
  241. Test URLs are prefixed with https:// when feed is requested over HTTPS.
  242. """
  243. response = self.client.get('/syndication/rss2/', **{
  244. 'wsgi.url_scheme': 'https',
  245. })
  246. doc = minidom.parseString(response.content)
  247. chan = doc.getElementsByTagName('channel')[0]
  248. self.assertEqual(
  249. chan.getElementsByTagName('link')[0].firstChild.wholeText[0:5],
  250. 'https'
  251. )
  252. atom_link = chan.getElementsByTagName('atom:link')[0]
  253. self.assertEqual(atom_link.getAttribute('href')[0:5], 'https')
  254. for link in doc.getElementsByTagName('link'):
  255. if link.getAttribute('rel') == 'self':
  256. self.assertEqual(link.getAttribute('href')[0:5], 'https')
  257. def test_item_link_error(self):
  258. """
  259. Test that a ImproperlyConfigured is raised if no link could be found
  260. for the item(s).
  261. """
  262. self.assertRaises(ImproperlyConfigured,
  263. self.client.get,
  264. '/syndication/articles/')
  265. def test_template_feed(self):
  266. """
  267. Test that the item title and description can be overridden with
  268. templates.
  269. """
  270. response = self.client.get('/syndication/template/')
  271. doc = minidom.parseString(response.content)
  272. feed = doc.getElementsByTagName('rss')[0]
  273. chan = feed.getElementsByTagName('channel')[0]
  274. items = chan.getElementsByTagName('item')
  275. self.assertChildNodeContent(items[0], {
  276. 'title': 'Title in your templates: My first entry',
  277. 'description': 'Description in your templates: My first entry',
  278. 'link': 'http://example.com/blog/1/',
  279. })
  280. def test_add_domain(self):
  281. """
  282. Test add_domain() prefixes domains onto the correct URLs.
  283. """
  284. self.assertEqual(
  285. views.add_domain('example.com', '/foo/?arg=value'),
  286. 'http://example.com/foo/?arg=value'
  287. )
  288. self.assertEqual(
  289. views.add_domain('example.com', '/foo/?arg=value', True),
  290. 'https://example.com/foo/?arg=value'
  291. )
  292. self.assertEqual(
  293. views.add_domain('example.com', 'http://djangoproject.com/doc/'),
  294. 'http://djangoproject.com/doc/'
  295. )
  296. self.assertEqual(
  297. views.add_domain('example.com', 'https://djangoproject.com/doc/'),
  298. 'https://djangoproject.com/doc/'
  299. )
  300. self.assertEqual(
  301. views.add_domain('example.com', 'mailto:uhoh@djangoproject.com'),
  302. 'mailto:uhoh@djangoproject.com'
  303. )
  304. self.assertEqual(
  305. views.add_domain('example.com', '//example.com/foo/?arg=value'),
  306. 'http://example.com/foo/?arg=value'
  307. )