You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

test_alert.py 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. import mock
  2. import warnings
  3. from django_webtest import WebTest
  4. from django.contrib.auth.models import AnonymousUser
  5. from django.urls import reverse
  6. from django.core import mail
  7. from django.test import TestCase
  8. from oscar.utils.deprecation import RemovedInOscar20Warning
  9. from oscar.apps.customer.alerts.utils import (
  10. send_alert_confirmation, send_product_alerts)
  11. from oscar.apps.customer.forms import ProductAlertForm
  12. from oscar.apps.customer.models import ProductAlert
  13. from oscar.test.factories import (
  14. create_product, create_stockrecord, ProductAlertFactory, UserFactory)
  15. class TestAUser(WebTest):
  16. def setUp(self):
  17. self.user = UserFactory()
  18. self.product = create_product(num_in_stock=0)
  19. def test_can_create_a_stock_alert(self):
  20. product_page = self.app.get(self.product.get_absolute_url(), user=self.user)
  21. form = product_page.forms['alert_form']
  22. form.submit()
  23. alerts = ProductAlert.objects.filter(user=self.user)
  24. self.assertEqual(1, len(alerts))
  25. alert = alerts[0]
  26. self.assertEqual(ProductAlert.ACTIVE, alert.status)
  27. self.assertEqual(alert.product, self.product)
  28. def test_cannot_create_multiple_alerts_for_one_product(self):
  29. ProductAlertFactory(user=self.user, product=self.product,
  30. status=ProductAlert.ACTIVE)
  31. # Alert form should not allow creation of additional alerts.
  32. form = ProductAlertForm(user=self.user, product=self.product, data={})
  33. self.assertFalse(form.is_valid())
  34. self.assertIn(
  35. "You already have an active alert for this product",
  36. form.errors['__all__'][0])
  37. class TestAUserWithAnActiveStockAlert(WebTest):
  38. def setUp(self):
  39. self.user = UserFactory()
  40. self.product = create_product()
  41. self.stockrecord = create_stockrecord(self.product, num_in_stock=0)
  42. product_page = self.app.get(self.product.get_absolute_url(),
  43. user=self.user)
  44. form = product_page.forms['alert_form']
  45. form.submit()
  46. def test_can_cancel_it(self):
  47. alerts = ProductAlert.objects.filter(user=self.user)
  48. self.assertEqual(1, len(alerts))
  49. alert = alerts[0]
  50. self.assertFalse(alert.is_cancelled)
  51. self.app.get(
  52. reverse('customer:alerts-cancel-by-pk', kwargs={'pk': alert.pk}),
  53. user=self.user)
  54. alerts = ProductAlert.objects.filter(user=self.user)
  55. self.assertEqual(1, len(alerts))
  56. alert = alerts[0]
  57. self.assertTrue(alert.is_cancelled)
  58. def test_gets_notified_when_it_is_back_in_stock(self):
  59. self.stockrecord.num_in_stock = 10
  60. self.stockrecord.save()
  61. self.assertEqual(1, self.user.notifications.all().count())
  62. def test_gets_emailed_when_it_is_back_in_stock(self):
  63. self.stockrecord.num_in_stock = 10
  64. self.stockrecord.save()
  65. self.assertEqual(1, len(mail.outbox))
  66. def test_does_not_get_emailed_when_it_is_saved_but_still_zero_stock(self):
  67. self.stockrecord.num_in_stock = 0
  68. self.stockrecord.save()
  69. self.assertEqual(0, len(mail.outbox))
  70. @mock.patch('oscar.apps.customer.alerts.utils.services.notify_user')
  71. def test_site_notification_sent(self, mock_notify):
  72. self.stockrecord.num_in_stock = 10
  73. self.stockrecord.save()
  74. mock_notify.assert_called_once_with(
  75. self.user,
  76. u'{} is back in stock'.format(self.product.title),
  77. body=u'<a href="{}">{}</a> is back in stock'.format(
  78. self.product.get_absolute_url(), self.product.title)
  79. )
  80. @mock.patch('oscar.apps.customer.alerts.utils.services.notify_user')
  81. def test_product_title_truncated_in_alert_notification_subject(self, mock_notify):
  82. self.product.title = ('Aut nihil dignissimos perspiciatis. Beatae sed consequatur odit incidunt. '
  83. 'Quaerat labore perferendis quasi aut sunt maxime accusamus laborum. '
  84. 'Ut quam repudiandae occaecati eligendi. Nihil rem vel eos.')
  85. self.product.save()
  86. self.stockrecord.num_in_stock = 10
  87. self.stockrecord.save()
  88. mock_notify.assert_called_once_with(
  89. self.user,
  90. u'{} is back in stock'.format(self.product.title[:200]),
  91. body=u'<a href="{}">{}</a> is back in stock'.format(
  92. self.product.get_absolute_url(), self.product.title)
  93. )
  94. class TestAnAnonymousUser(WebTest):
  95. def test_can_create_a_stock_alert(self):
  96. product = create_product(num_in_stock=0)
  97. product_page = self.app.get(product.get_absolute_url())
  98. form = product_page.forms['alert_form']
  99. form['email'] = 'john@smith.com'
  100. form.submit()
  101. alerts = ProductAlert.objects.filter(email='john@smith.com')
  102. self.assertEqual(1, len(alerts))
  103. alert = alerts[0]
  104. self.assertEqual(ProductAlert.UNCONFIRMED, alert.status)
  105. self.assertEqual(alert.product, product)
  106. def test_can_cancel_unconfirmed_stock_alert(self):
  107. alert = ProductAlertFactory(
  108. user=None, email='john@smith.com', status=ProductAlert.UNCONFIRMED)
  109. self.app.get(
  110. reverse('customer:alerts-cancel-by-key', kwargs={'key': alert.key}))
  111. alert.refresh_from_db()
  112. self.assertTrue(alert.is_cancelled)
  113. def test_cannot_create_multiple_alerts_for_one_product(self):
  114. product = create_product(num_in_stock=0)
  115. alert = ProductAlertFactory(user=None, product=product,
  116. email='john@smith.com')
  117. alert.status = ProductAlert.ACTIVE
  118. alert.save()
  119. # Alert form should not allow creation of additional alerts.
  120. form = ProductAlertForm(user=AnonymousUser(), product=product,
  121. data={'email': 'john@smith.com'})
  122. self.assertFalse(form.is_valid())
  123. self.assertIn(
  124. "There is already an active stock alert for john@smith.com",
  125. form.errors['__all__'][0])
  126. def test_cannot_create_multiple_unconfirmed_alerts(self):
  127. # Create an unconfirmed alert
  128. ProductAlertFactory(
  129. user=None, email='john@smith.com', status=ProductAlert.UNCONFIRMED)
  130. # Alert form should not allow creation of additional alerts.
  131. form = ProductAlertForm(
  132. user=AnonymousUser(),
  133. product=create_product(num_in_stock=0),
  134. data={'email': 'john@smith.com'},
  135. )
  136. self.assertFalse(form.is_valid())
  137. self.assertIn(
  138. "john@smith.com has been sent a confirmation email for another "
  139. "product alert on this site.", form.errors['__all__'][0])
  140. class TestHurryMode(TestCase):
  141. def setUp(self):
  142. self.user = UserFactory()
  143. self.product = create_product()
  144. def test_hurry_mode_not_set_when_stock_high(self):
  145. # One alert, 5 items in stock. No need to hurry.
  146. create_stockrecord(self.product, num_in_stock=5)
  147. ProductAlert.objects.create(user=self.user, product=self.product)
  148. send_product_alerts(self.product)
  149. self.assertEqual(1, len(mail.outbox))
  150. self.assertNotIn(
  151. 'Beware that the amount of items in stock is limited',
  152. mail.outbox[0].body)
  153. def test_hurry_mode_set_when_stock_low(self):
  154. # Two alerts, 1 item in stock. Hurry mode should be set.
  155. create_stockrecord(self.product, num_in_stock=1)
  156. ProductAlert.objects.create(user=self.user, product=self.product)
  157. ProductAlert.objects.create(user=UserFactory(), product=self.product)
  158. send_product_alerts(self.product)
  159. self.assertEqual(2, len(mail.outbox))
  160. self.assertIn(
  161. 'Beware that the amount of items in stock is limited',
  162. mail.outbox[0].body)
  163. def test_hurry_mode_not_set_multiple_stockrecords(self):
  164. # Two stockrecords, 5 items in stock for one. No need to hurry.
  165. create_stockrecord(self.product, num_in_stock=1)
  166. create_stockrecord(self.product, num_in_stock=5)
  167. ProductAlert.objects.create(user=self.user, product=self.product)
  168. send_product_alerts(self.product)
  169. self.assertNotIn(
  170. 'Beware that the amount of items in stock is limited',
  171. mail.outbox[0].body)
  172. def test_hurry_mode_set_multiple_stockrecords(self):
  173. # Two stockrecords, low stock on both. Hurry mode should be set.
  174. create_stockrecord(self.product, num_in_stock=1)
  175. create_stockrecord(self.product, num_in_stock=1)
  176. ProductAlert.objects.create(user=self.user, product=self.product)
  177. ProductAlert.objects.create(user=UserFactory(), product=self.product)
  178. send_product_alerts(self.product)
  179. self.assertIn(
  180. 'Beware that the amount of items in stock is limited',
  181. mail.outbox[0].body)
  182. class TestAlertMessageSending(TestCase):
  183. def setUp(self):
  184. self.user = UserFactory()
  185. self.product = create_product()
  186. create_stockrecord(self.product, num_in_stock=1)
  187. @mock.patch('oscar.apps.customer.utils.Dispatcher.dispatch_direct_messages')
  188. def test_alert_confirmation_uses_dispatcher(self, mock_dispatch):
  189. alert = ProductAlert.objects.create(
  190. email='test@example.com',
  191. key='dummykey',
  192. status=ProductAlert.UNCONFIRMED,
  193. product=self.product
  194. )
  195. send_alert_confirmation(alert)
  196. self.assertEqual(mock_dispatch.call_count, 1)
  197. self.assertEqual(mock_dispatch.call_args[0][0], 'test@example.com')
  198. @mock.patch('oscar.apps.customer.utils.Dispatcher.dispatch_user_messages')
  199. def test_alert_uses_dispatcher(self, mock_dispatch):
  200. ProductAlert.objects.create(user=self.user, product=self.product)
  201. send_product_alerts(self.product)
  202. self.assertEqual(mock_dispatch.call_count, 1)
  203. self.assertEqual(mock_dispatch.call_args[0][0], self.user)
  204. def test_alert_creates_email_obj(self):
  205. ProductAlert.objects.create(user=self.user, product=self.product)
  206. send_product_alerts(self.product)
  207. self.assertEqual(self.user.emails.count(), 1)
  208. @staticmethod
  209. def _load_deprecated_template(path):
  210. from django.template.loader import select_template
  211. # Replace the old template paths with new ones, thereby mocking
  212. # the presence of the deprecated templates.
  213. if path.startswith('customer/alerts/emails/'):
  214. old_path = path.replace('customer/alerts/emails/', '')
  215. path_map = {
  216. 'confirmation_body.txt': 'confirmation_body.txt',
  217. 'confirmation_subject.txt': 'confirmation_subject.txt',
  218. 'alert_body.txt': 'body.txt',
  219. 'alert_subject.txt': 'subject.txt',
  220. }
  221. new_path = 'customer/emails/commtype_product_alert_{}'.format(
  222. path_map[old_path]
  223. )
  224. # We use 'select_template' because 'load_template' is mocked...
  225. return select_template([new_path])
  226. return select_template([path])
  227. @mock.patch('oscar.apps.customer.alerts.utils.loader.get_template')
  228. def test_deprecated_notification_templates_work(self, mock_loader):
  229. """
  230. This test can be removed when support for the deprecated templates is
  231. dropped.
  232. """
  233. mock_loader.side_effect = self._load_deprecated_template
  234. with warnings.catch_warnings(record=True) as warning_list:
  235. warnings.simplefilter("always")
  236. alert = ProductAlert.objects.create(
  237. email='test@example.com',
  238. key='dummykey',
  239. status=ProductAlert.UNCONFIRMED,
  240. product=self.product
  241. )
  242. send_alert_confirmation(alert)
  243. # Check that warnings were raised
  244. self.assertTrue(
  245. any(item.category == RemovedInOscar20Warning for item in warning_list))
  246. # Check that alerts were still sent
  247. self.assertEqual(len(mail.outbox), 1)
  248. @mock.patch('oscar.apps.customer.alerts.utils.loader.get_template')
  249. def test_deprecated_alert_templates_work(self, mock_loader):
  250. """
  251. This test can be removed when support for the deprecated templates is
  252. dropped.
  253. """
  254. mock_loader.side_effect = self._load_deprecated_template
  255. with warnings.catch_warnings(record=True) as warning_list:
  256. warnings.simplefilter("always")
  257. ProductAlert.objects.create(user=self.user, product=self.product)
  258. send_product_alerts(self.product)
  259. # Check that warnings were raised
  260. self.assertTrue(
  261. any(item.category == RemovedInOscar20Warning for item in warning_list))
  262. # Check that alerts were still sent
  263. self.assertEqual(len(mail.outbox), 1)