import mock import warnings from django_webtest import WebTest from django.contrib.auth.models import AnonymousUser from django.core.urlresolvers import reverse from django.core import mail from django.test import TestCase from oscar.utils.deprecation import RemovedInOscar20Warning from oscar.apps.customer.alerts.utils import ( send_alert_confirmation, send_product_alerts) from oscar.apps.customer.forms import ProductAlertForm from oscar.apps.customer.models import ProductAlert from oscar.test.factories import ( create_product, create_stockrecord, ProductAlertFactory, UserFactory) class TestAUser(WebTest): def setUp(self): self.user = UserFactory() self.product = create_product(num_in_stock=0) def test_can_create_a_stock_alert(self): product_page = self.app.get(self.product.get_absolute_url(), user=self.user) form = product_page.forms['alert_form'] form.submit() alerts = ProductAlert.objects.filter(user=self.user) self.assertEqual(1, len(alerts)) alert = alerts[0] self.assertEqual(ProductAlert.ACTIVE, alert.status) self.assertEqual(alert.product, self.product) def test_cannot_create_multiple_alerts_for_one_product(self): ProductAlertFactory(user=self.user, product=self.product, status=ProductAlert.ACTIVE) # Alert form should not allow creation of additional alerts. form = ProductAlertForm(user=self.user, product=self.product, data={}) self.assertFalse(form.is_valid()) self.assertIn( "You already have an active alert for this product", form.errors['__all__'][0]) class TestAUserWithAnActiveStockAlert(WebTest): def setUp(self): self.user = UserFactory() self.product = create_product() self.stockrecord = create_stockrecord(self.product, num_in_stock=0) product_page = self.app.get(self.product.get_absolute_url(), user=self.user) form = product_page.forms['alert_form'] form.submit() def test_can_cancel_it(self): alerts = ProductAlert.objects.filter(user=self.user) self.assertEqual(1, len(alerts)) alert = alerts[0] self.assertFalse(alert.is_cancelled) self.app.get( reverse('customer:alerts-cancel-by-pk', kwargs={'pk': alert.pk}), user=self.user) alerts = ProductAlert.objects.filter(user=self.user) self.assertEqual(1, len(alerts)) alert = alerts[0] self.assertTrue(alert.is_cancelled) def test_gets_notified_when_it_is_back_in_stock(self): self.stockrecord.num_in_stock = 10 self.stockrecord.save() self.assertEqual(1, self.user.notifications.all().count()) def test_gets_emailed_when_it_is_back_in_stock(self): self.stockrecord.num_in_stock = 10 self.stockrecord.save() self.assertEqual(1, len(mail.outbox)) def test_does_not_get_emailed_when_it_is_saved_but_still_zero_stock(self): self.stockrecord.num_in_stock = 0 self.stockrecord.save() self.assertEqual(0, len(mail.outbox)) @mock.patch('oscar.apps.customer.alerts.utils.services.notify_user') def test_site_notification_sent(self, mock_notify): self.stockrecord.num_in_stock = 10 self.stockrecord.save() mock_notify.assert_called_once_with( self.user, u'{} is back in stock'.format(self.product.title), body=u'{} is back in stock'.format( self.product.get_absolute_url(), self.product.title) ) @mock.patch('oscar.apps.customer.alerts.utils.services.notify_user') def test_product_title_truncated_in_alert_notification_subject(self, mock_notify): self.product.title = ('Aut nihil dignissimos perspiciatis. Beatae sed consequatur odit incidunt. ' 'Quaerat labore perferendis quasi aut sunt maxime accusamus laborum. ' 'Ut quam repudiandae occaecati eligendi. Nihil rem vel eos.') self.product.save() self.stockrecord.num_in_stock = 10 self.stockrecord.save() mock_notify.assert_called_once_with( self.user, u'{} is back in stock'.format(self.product.title[:200]), body=u'{} is back in stock'.format( self.product.get_absolute_url(), self.product.title) ) class TestAnAnonymousUser(WebTest): def test_can_create_a_stock_alert(self): product = create_product(num_in_stock=0) product_page = self.app.get(product.get_absolute_url()) form = product_page.forms['alert_form'] form['email'] = 'john@smith.com' form.submit() alerts = ProductAlert.objects.filter(email='john@smith.com') self.assertEqual(1, len(alerts)) alert = alerts[0] self.assertEqual(ProductAlert.UNCONFIRMED, alert.status) self.assertEqual(alert.product, product) def test_can_cancel_unconfirmed_stock_alert(self): alert = ProductAlertFactory( user=None, email='john@smith.com', status=ProductAlert.UNCONFIRMED) self.app.get( reverse('customer:alerts-cancel-by-key', kwargs={'key': alert.key})) alert.refresh_from_db() self.assertTrue(alert.is_cancelled) def test_cannot_create_multiple_alerts_for_one_product(self): product = create_product(num_in_stock=0) alert = ProductAlertFactory(user=None, product=product, email='john@smith.com') alert.status = ProductAlert.ACTIVE alert.save() # Alert form should not allow creation of additional alerts. form = ProductAlertForm(user=AnonymousUser(), product=product, data={'email': 'john@smith.com'}) self.assertFalse(form.is_valid()) self.assertIn( "There is already an active stock alert for john@smith.com", form.errors['__all__'][0]) def test_cannot_create_multiple_unconfirmed_alerts(self): # Create an unconfirmed alert ProductAlertFactory( user=None, email='john@smith.com', status=ProductAlert.UNCONFIRMED) # Alert form should not allow creation of additional alerts. form = ProductAlertForm( user=AnonymousUser(), product=create_product(num_in_stock=0), data={'email': 'john@smith.com'}, ) self.assertFalse(form.is_valid()) self.assertIn( "john@smith.com has been sent a confirmation email for another " "product alert on this site.", form.errors['__all__'][0]) class TestHurryMode(TestCase): def setUp(self): self.user = UserFactory() self.product = create_product() def test_hurry_mode_not_set_when_stock_high(self): # One alert, 5 items in stock. No need to hurry. create_stockrecord(self.product, num_in_stock=5) ProductAlert.objects.create(user=self.user, product=self.product) send_product_alerts(self.product) self.assertEqual(1, len(mail.outbox)) self.assertNotIn( 'Beware that the amount of items in stock is limited', mail.outbox[0].body) def test_hurry_mode_set_when_stock_low(self): # Two alerts, 1 item in stock. Hurry mode should be set. create_stockrecord(self.product, num_in_stock=1) ProductAlert.objects.create(user=self.user, product=self.product) ProductAlert.objects.create(user=UserFactory(), product=self.product) send_product_alerts(self.product) self.assertEqual(2, len(mail.outbox)) self.assertIn( 'Beware that the amount of items in stock is limited', mail.outbox[0].body) def test_hurry_mode_not_set_multiple_stockrecords(self): # Two stockrecords, 5 items in stock for one. No need to hurry. create_stockrecord(self.product, num_in_stock=1) create_stockrecord(self.product, num_in_stock=5) ProductAlert.objects.create(user=self.user, product=self.product) send_product_alerts(self.product) self.assertNotIn( 'Beware that the amount of items in stock is limited', mail.outbox[0].body) def test_hurry_mode_set_multiple_stockrecords(self): # Two stockrecords, low stock on both. Hurry mode should be set. create_stockrecord(self.product, num_in_stock=1) create_stockrecord(self.product, num_in_stock=1) ProductAlert.objects.create(user=self.user, product=self.product) ProductAlert.objects.create(user=UserFactory(), product=self.product) send_product_alerts(self.product) self.assertIn( 'Beware that the amount of items in stock is limited', mail.outbox[0].body) class TestAlertMessageSending(TestCase): def setUp(self): self.user = UserFactory() self.product = create_product() create_stockrecord(self.product, num_in_stock=1) @mock.patch('oscar.apps.customer.utils.Dispatcher.dispatch_direct_messages') def test_alert_confirmation_uses_dispatcher(self, mock_dispatch): alert = ProductAlert.objects.create( email='test@example.com', key='dummykey', status=ProductAlert.UNCONFIRMED, product=self.product ) send_alert_confirmation(alert) self.assertEqual(mock_dispatch.call_count, 1) self.assertEqual(mock_dispatch.call_args[0][0], 'test@example.com') @mock.patch('oscar.apps.customer.utils.Dispatcher.dispatch_user_messages') def test_alert_uses_dispatcher(self, mock_dispatch): ProductAlert.objects.create(user=self.user, product=self.product) send_product_alerts(self.product) self.assertEqual(mock_dispatch.call_count, 1) self.assertEqual(mock_dispatch.call_args[0][0], self.user) def test_alert_creates_email_obj(self): ProductAlert.objects.create(user=self.user, product=self.product) send_product_alerts(self.product) self.assertEqual(self.user.emails.count(), 1) @staticmethod def _load_deprecated_template(path): from django.template.loader import select_template # Replace the old template paths with new ones, thereby mocking # the presence of the deprecated templates. if path.startswith('customer/alerts/emails/'): old_path = path.replace('customer/alerts/emails/', '') path_map = { 'confirmation_body.txt': 'confirmation_body.txt', 'confirmation_subject.txt': 'confirmation_subject.txt', 'alert_body.txt': 'body.txt', 'alert_subject.txt': 'subject.txt', } new_path = 'customer/emails/commtype_product_alert_{}'.format( path_map[old_path] ) # We use 'select_template' because 'load_template' is mocked... return select_template([new_path]) return select_template([path]) @mock.patch('oscar.apps.customer.alerts.utils.loader.get_template') def test_deprecated_notification_templates_work(self, mock_loader): """ This test can be removed when support for the deprecated templates is dropped. """ mock_loader.side_effect = self._load_deprecated_template with warnings.catch_warnings(record=True) as warning_list: warnings.simplefilter("always") alert = ProductAlert.objects.create( email='test@example.com', key='dummykey', status=ProductAlert.UNCONFIRMED, product=self.product ) send_alert_confirmation(alert) # Check that warnings were raised self.assertTrue( any(item.category == RemovedInOscar20Warning for item in warning_list)) # Check that alerts were still sent self.assertEqual(len(mail.outbox), 1) @mock.patch('oscar.apps.customer.alerts.utils.loader.get_template') def test_deprecated_alert_templates_work(self, mock_loader): """ This test can be removed when support for the deprecated templates is dropped. """ mock_loader.side_effect = self._load_deprecated_template with warnings.catch_warnings(record=True) as warning_list: warnings.simplefilter("always") ProductAlert.objects.create(user=self.user, product=self.product) send_product_alerts(self.product) # Check that warnings were raised self.assertTrue( any(item.category == RemovedInOscar20Warning for item in warning_list)) # Check that alerts were still sent self.assertEqual(len(mail.outbox), 1)