PageRenderTime 208ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/hyperkitty/tests/views/test_archives.py

https://gitlab.com/Acidburn0zzz/hyperkitty
Python | 341 lines | 254 code | 39 blank | 48 comment | 10 complexity | 34161386200d9fb30816cb80875a971e MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. #
  3. # Copyright (C) 2012-2015 by the Free Software Foundation, Inc.
  4. #
  5. # This file is part of HyperKitty.
  6. #
  7. # HyperKitty is free software: you can redistribute it and/or modify it under
  8. # the terms of the GNU General Public License as published by the Free
  9. # Software Foundation, either version 3 of the License, or (at your option)
  10. # any later version.
  11. #
  12. # HyperKitty is distributed in the hope that it will be useful, but WITHOUT
  13. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  14. # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  15. # more details.
  16. #
  17. # You should have received a copy of the GNU General Public License along with
  18. # HyperKitty. If not, see <http://www.gnu.org/licenses/>.
  19. #
  20. # Author: Aamir Khan <syst3m.w0rm@gmail.com>
  21. # Author: Aurelien Bompard <abompard@fedoraproject.org>
  22. #
  23. from __future__ import absolute_import, print_function, unicode_literals
  24. import os
  25. import datetime
  26. import gzip
  27. import mailbox
  28. import shutil
  29. from email.message import Message
  30. from mock import Mock
  31. from bs4 import BeautifulSoup
  32. from django.contrib.auth.models import User
  33. from django.core.urlresolvers import reverse
  34. from django_mailman3.lib.cache import cache
  35. from django_mailman3.tests.utils import FakeMMList, FakeMMMember
  36. from hyperkitty.models import (
  37. MailingList, ArchivePolicy, Sender, Thread, Favorite, Email)
  38. from hyperkitty.lib.incoming import add_to_list
  39. from hyperkitty.tests.utils import TestCase
  40. class ListArchivesTestCase(TestCase):
  41. def setUp(self):
  42. # Create the list by adding a dummy message
  43. msg = Message()
  44. msg["From"] = "dummy@example.com"
  45. msg["Message-ID"] = "<msg>"
  46. msg.set_payload("Dummy message")
  47. add_to_list("list@example.com", msg)
  48. def test_no_date(self):
  49. today = datetime.date.today()
  50. response = self.client.get(reverse(
  51. 'hk_archives_latest', args=['list@example.com']))
  52. final_url = reverse(
  53. 'hk_archives_with_month',
  54. kwargs={
  55. 'mlist_fqdn': 'list@example.com',
  56. 'year': today.year,
  57. 'month': today.month,
  58. })
  59. self.assertRedirects(response, final_url)
  60. def test_wrong_date(self):
  61. response = self.client.get(reverse(
  62. 'hk_archives_with_month', kwargs={
  63. 'mlist_fqdn': 'list@example.com',
  64. 'year': '9999',
  65. 'month': '0',
  66. }))
  67. self.assertEqual(response.status_code, 404)
  68. def test_overview(self):
  69. response = self.client.get(reverse(
  70. 'hk_list_overview', args=["list@example.com"]))
  71. self.assertEqual(response.status_code, 200)
  72. self.assertEqual(response.context["view_name"], "overview")
  73. self.assertEqual(len(response.context["top_threads"]), 1)
  74. self.assertEqual(len(response.context["most_active_threads"]), 1)
  75. self.assertEqual(len(response.context["pop_threads"]), 0)
  76. def test_overview_with_user(self):
  77. user = User.objects.create_user(
  78. 'testuser', 'dummy@example.com', 'testPass')
  79. sender = Sender.objects.get(address='dummy@example.com')
  80. sender.mailman_id = "dummy"
  81. sender.save()
  82. mm_user = Mock()
  83. mm_user.user_id = "dummy"
  84. self.mailman_client.get_user.side_effect = lambda name: mm_user
  85. self.client.login(username='testuser', password='testPass')
  86. thread = Thread.objects.first()
  87. Favorite.objects.create(thread=thread, user=user)
  88. response = self.client.get(
  89. reverse('hk_list_overview', args=["list@example.com"]))
  90. self.assertEqual(response.status_code, 200)
  91. self.assertEqual(len(response.context["threads_posted_to"]), 1)
  92. self.assertEqual(len(response.context["flagged_threads"]), 1)
  93. def test_overview_cleaned_cache(self):
  94. # Test the overview page with a clean cache (different code path for
  95. # MailingList.recent_threads)
  96. cache.delete("MailingList:list@example.com:recent_threads")
  97. response = self.client.get(
  98. reverse('hk_list_overview', args=["list@example.com"]))
  99. self.assertEqual(response.status_code, 200)
  100. self.assertEqual(response.context["view_name"], "overview")
  101. self.assertEqual(len(response.context["top_threads"]), 1)
  102. self.assertEqual(len(response.context["most_active_threads"]), 1)
  103. self.assertEqual(len(response.context["pop_threads"]), 0)
  104. def test_email_escaped_sender(self):
  105. url = reverse('hk_list_overview', args=["list@example.com"])
  106. response = self.client.get(url)
  107. self.assertNotContains(response, "dummy@example.com", status_code=200)
  108. class ExportMboxTestCase(TestCase):
  109. def setUp(self):
  110. # Create the list by adding a dummy message
  111. msg = Message()
  112. msg["From"] = "dummy@example.com"
  113. msg["Message-ID"] = "<msg>"
  114. msg.set_payload("Dummy message")
  115. add_to_list("list@example.com", msg)
  116. # We need a temp dir for the mailbox, Python's mailbox module needs a
  117. # filesystem path, it does not accept a file-like object.
  118. def _get_mbox(self, qs=None):
  119. url = reverse(
  120. "hk_list_export_mbox", args=["list@example.com", "dummy"])
  121. if qs:
  122. url += "?" + qs
  123. response = self.client.get(url)
  124. self.assertEqual(response.status_code, 200)
  125. self.assertEqual(response["Content-Type"], "application/gzip")
  126. self.assertEqual(
  127. response["Content-Disposition"],
  128. 'attachment; filename="dummy.mbox.gz"')
  129. mboxfilepath = os.path.join(self.tmpdir, "dummy.mbox")
  130. # Store the gzipped mailbox
  131. with open(mboxfilepath + ".gz", "wb") as mboxfile:
  132. for line in response.streaming_content:
  133. mboxfile.write(line)
  134. # Decompress the mailbox
  135. with gzip.open(mboxfilepath + ".gz", 'rb') as f_in, \
  136. open(mboxfilepath, 'wb') as f_out:
  137. shutil.copyfileobj(f_in, f_out)
  138. mbox = mailbox.mbox(mboxfilepath)
  139. return mbox
  140. def test_basic(self):
  141. mbox = self._get_mbox()
  142. content = open(mbox._path).read()
  143. self.assertTrue(content.startswith("From dummy at example.com "))
  144. self.assertEqual(len(mbox), 1)
  145. email = mbox.values()[0]
  146. self.assertEqual(email["From"], "dummy at example.com")
  147. self.assertEqual(email["Message-ID"], "<msg>")
  148. self.assertTrue(email.is_multipart())
  149. content = email.get_payload()[0]
  150. self.assertEqual(content.get_payload(decode=True), "Dummy message")
  151. def test_with_sender_name(self):
  152. sender = Sender.objects.get(address='dummy@example.com')
  153. sender.name = "Dummy Sender"
  154. sender.save()
  155. mbox = self._get_mbox()
  156. email = mbox.values()[0]
  157. self.assertEqual(email["From"], "Dummy Sender <dummy at example.com>")
  158. def test_between_dates(self):
  159. msg = Message()
  160. msg["From"] = "dummy@example.com"
  161. msg["Date"] = "2015-09-01 00:00:00"
  162. msg["Message-ID"] = "<msg2>"
  163. msg.set_payload("Dummy message")
  164. add_to_list("list@example.com", msg)
  165. mbox = self._get_mbox(qs="start=2015-09-01&end=2015-10-01")
  166. self.assertEqual(len(mbox), 1)
  167. mbox_msg = mbox.values()[0]
  168. self.assertEqual(mbox_msg["Message-ID"], "<msg2>")
  169. def test_thread(self):
  170. msg = Message()
  171. msg["From"] = "dummy@example.com"
  172. msg["Message-ID"] = "<msg2>"
  173. msg["In-Reply-To"] = "<msg>"
  174. msg.set_payload("Dummy message")
  175. add_to_list("list@example.com", msg)
  176. # Add a message in a different thread:
  177. msg = Message()
  178. msg["From"] = "dummy@example.com"
  179. msg["Message-ID"] = "<msg3>"
  180. msg.set_payload("Dummy message")
  181. add_to_list("list@example.com", msg)
  182. thread_id = Email.objects.get(message_id="msg").thread.thread_id
  183. mbox = self._get_mbox(qs="thread=%s" % thread_id)
  184. self.assertEqual(len(mbox), 2)
  185. self.assertEqual(
  186. [m["Message-ID"] for m in mbox], ["<msg>", "<msg2>"])
  187. def test_message(self):
  188. msg = Message()
  189. msg["From"] = "dummy@example.com"
  190. msg["Message-ID"] = "<msg2>"
  191. msg["In-Reply-To"] = "<msg>"
  192. msg.set_payload("Dummy message")
  193. msg_id = add_to_list("list@example.com", msg)
  194. mbox = self._get_mbox(qs="message=%s" % msg_id)
  195. self.assertEqual(len(mbox), 1)
  196. self.assertEqual([m["Message-ID"] for m in mbox], ["<msg2>"])
  197. class PrivateArchivesTestCase(TestCase):
  198. def setUp(self):
  199. self.user = User.objects.create_user(
  200. 'testuser', 'test@example.com', 'testPass')
  201. MailingList.objects.create(
  202. name="list@example.com", subject_prefix="[example] ",
  203. archive_policy=ArchivePolicy.private.value)
  204. msg = Message()
  205. msg["From"] = "dummy@example.com"
  206. msg["Message-ID"] = "<msgid>"
  207. msg["Subject"] = "Dummy message"
  208. msg.set_payload("Dummy message")
  209. msg["Message-ID-Hash"] = self.msgid = add_to_list(
  210. "list@example.com", msg)
  211. # Set the mailman_client after the message has been added to the list,
  212. # otherwise MailingList.update_from_mailman() will overwrite the list
  213. # properties.
  214. self.mailman_client.get_list.side_effect = \
  215. lambda name: FakeMMList(name)
  216. self.mm_user = Mock()
  217. self.mm_user.user_id = "dummy"
  218. self.mailman_client.get_user.side_effect = lambda name: self.mm_user
  219. self.mm_user.subscriptions = [
  220. FakeMMMember("list.example.com", self.user.email),
  221. ]
  222. self.mm_user.addresses = ['test@example.com']
  223. def tearDown(self):
  224. self.client.logout()
  225. def _do_test(self, url, query=None):
  226. if query is None:
  227. query = {}
  228. response = self.client.get(url, query)
  229. self.assertEqual(response.status_code, 403)
  230. self.client.login(username='testuser', password='testPass')
  231. # # use a temp variable below because self.client.session is actually a
  232. # # property which returns a new instance en each call :-/
  233. # http://blog.joshcrompton.com/2012/09/how-to-use-sessions-in-django-unit-tests.html
  234. # session = self.client.session
  235. # session["subscribed"] = ["list@example.com"]
  236. # session.save()
  237. response = self.client.get(url, query)
  238. self.assertEqual(response.status_code, 200)
  239. self.assertContains(response, "Dummy message")
  240. def test_month_view(self):
  241. now = datetime.datetime.now()
  242. self._do_test(reverse(
  243. 'hk_archives_with_month',
  244. args=["list@example.com", now.year, now.month]))
  245. def test_overview(self):
  246. self._do_test(reverse('hk_list_overview', args=["list@example.com"]))
  247. def test_thread_view(self):
  248. self._do_test(reverse(
  249. 'hk_thread', args=["list@example.com", self.msgid]))
  250. def test_message_view(self):
  251. self._do_test(reverse(
  252. 'hk_message_index', args=["list@example.com", self.msgid]))
  253. class MonthsListTestCase(TestCase):
  254. def setUp(self):
  255. # Create the list by adding a dummy message
  256. # The message must be old to create multiple year accordion panels in
  257. # the months list.
  258. msg = Message()
  259. msg["From"] = "dummy@example.com"
  260. msg["Message-ID"] = "<msg>"
  261. msg["Date"] = "2010-02-01 00:00:00 UTC"
  262. msg.set_payload("Dummy message")
  263. add_to_list("list@example.com", msg)
  264. def _assertCollapsed(self, panel):
  265. self.assertTrue(
  266. "in" not in panel["class"],
  267. "Panel %s has the 'in' class" % panel["id"])
  268. self.assertTrue(
  269. "collapse" in panel["class"],
  270. "Panel %s has no 'collapse' class" % panel["id"])
  271. def _assertNotCollapsed(self, panel):
  272. self.assertTrue(
  273. "in" in panel["class"],
  274. "Panel %s has no 'in' class" % panel["id"])
  275. def _assertActivePanel(self, html, panel_num):
  276. """ Checks that the <panel_num> year is active.
  277. The panel_num arg is the id in the years list. Example: panel_num=0
  278. means the current year is active, panel_num=-1 means the year of the
  279. first archived email is active.
  280. """
  281. soup = BeautifulSoup(html, "html.parser")
  282. months_list = soup.find(id="months-list")
  283. panels = months_list.find_all(class_="panel-collapse")
  284. for panel in panels:
  285. if panel == panels[panel_num]:
  286. self._assertNotCollapsed(panel)
  287. else:
  288. self._assertCollapsed(panel)
  289. def test_overview(self):
  290. response = self.client.get(reverse(
  291. 'hk_list_overview', args=["list@example.com"]))
  292. self.assertEqual(response.status_code, 200)
  293. self._assertActivePanel(response.content, 0)
  294. def test_month_list(self):
  295. response = self.client.get(reverse(
  296. 'hk_archives_with_month', kwargs={
  297. 'mlist_fqdn': 'list@example.com',
  298. 'year': '2011',
  299. 'month': '1',
  300. }))
  301. self.assertEqual(response.status_code, 200)
  302. self._assertActivePanel(response.content, -2)