test_models.py 16.8 KB
Newer Older
1
"""
Jason Bau committed
2
Tests for the Shopping Cart Models
3
"""
4 5
import smtplib
from boto.exception import BotoServerError  # this is a super-class of SESError and catches connection errors
6

7
from mock import patch, MagicMock
8
from django.core import mail
9
from django.conf import settings
Diana Huang committed
10
from django.db import DatabaseError
11
from django.test import TestCase
12
from django.test.utils import override_settings
13
from django.contrib.auth.models import AnonymousUser
14 15 16
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE
17 18
from shoppingcart.models import (Order, OrderItem, CertificateItem, InvalidCartItem, PaidCourseRegistration,
                                 OrderItemSubclassPK)
19
from student.tests.factories import UserFactory
20
from student.models import CourseEnrollment
21
from course_modes.models import CourseMode
Diana Huang committed
22
from shoppingcart.exceptions import PurchasedCallbackException
23

24

25
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
26
class OrderTest(ModuleStoreTestCase):
27 28
    def setUp(self):
        self.user = UserFactory.create()
29
        self.course_id = "org/test/Test_Course"
30
        CourseFactory.create(org='org', number='test', display_name='Test Course')
31 32
        for i in xrange(1, 5):
            CourseFactory.create(org='org', number='test', display_name='Test Course {0}'.format(i))
33 34 35 36 37 38
        self.cost = 40

    def test_get_cart_for_user(self):
        # create a cart
        cart = Order.get_cart_for_user(user=self.user)
        # add something to it
39
        CertificateItem.add_to_order(cart, self.course_id, self.cost, 'honor')
40 41 42 43
        # should return the same cart
        cart2 = Order.get_cart_for_user(user=self.user)
        self.assertEquals(cart2.orderitem_set.count(), 1)

44 45 46 47 48 49 50 51 52
    def test_user_cart_has_items(self):
        anon = AnonymousUser()
        self.assertFalse(Order.user_cart_has_items(anon))
        self.assertFalse(Order.user_cart_has_items(self.user))
        cart = Order.get_cart_for_user(self.user)
        item = OrderItem(order=cart, user=self.user)
        item.save()
        self.assertTrue(Order.user_cart_has_items(self.user))

53 54
    def test_cart_clear(self):
        cart = Order.get_cart_for_user(user=self.user)
55
        CertificateItem.add_to_order(cart, self.course_id, self.cost, 'honor')
56
        CertificateItem.add_to_order(cart, 'org/test/Test_Course_1', self.cost, 'honor')
57
        self.assertEquals(cart.orderitem_set.count(), 2)
58
        self.assertTrue(cart.has_items())
59 60
        cart.clear()
        self.assertEquals(cart.orderitem_set.count(), 0)
61
        self.assertFalse(cart.has_items())
Diana Huang committed
62

63 64
    def test_add_item_to_cart_currency_match(self):
        cart = Order.get_cart_for_user(user=self.user)
65
        CertificateItem.add_to_order(cart, self.course_id, self.cost, 'honor', currency='eur')
66 67 68 69 70
        # verify that a new item has been added
        self.assertEquals(cart.orderitem_set.count(), 1)
        # verify that the cart's currency was updated
        self.assertEquals(cart.currency, 'eur')
        with self.assertRaises(InvalidCartItem):
71
            CertificateItem.add_to_order(cart, self.course_id, self.cost, 'honor', currency='usd')
72 73
        # assert that this item did not get added to the cart
        self.assertEquals(cart.orderitem_set.count(), 1)
74

75
    def test_total_cost(self):
76
        cart = Order.get_cart_for_user(user=self.user)
77
        # add items to the order
78 79 80 81
        course_costs = [('org/test/Test_Course_1', 30),
                        ('org/test/Test_Course_2', 40),
                        ('org/test/Test_Course_3', 10),
                        ('org/test/Test_Course_4', 20)]
82
        for course, cost in course_costs:
83
            CertificateItem.add_to_order(cart, course, cost, 'honor')
84 85 86 87 88 89 90 91 92
        self.assertEquals(cart.orderitem_set.count(), len(course_costs))
        self.assertEquals(cart.total_cost, sum(cost for _course, cost in course_costs))

    def test_purchase(self):
        # This test is for testing the subclassing functionality of OrderItem, but in
        # order to do this, we end up testing the specific functionality of
        # CertificateItem, which is not quite good unit test form. Sorry.
        cart = Order.get_cart_for_user(user=self.user)
        self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course_id))
93
        item = CertificateItem.add_to_order(cart, self.course_id, self.cost, 'honor')
94 95 96 97 98
        # course enrollment object should be created but still inactive
        self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course_id))
        cart.purchase()
        self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course_id))

99 100 101 102 103
        # test e-mail sending
        self.assertEquals(len(mail.outbox), 1)
        self.assertEquals('Order Payment Confirmation', mail.outbox[0].subject)
        self.assertIn(settings.PAYMENT_SUPPORT_EMAIL, mail.outbox[0].body)
        self.assertIn(unicode(cart.total_cost), mail.outbox[0].body)
104
        self.assertIn(item.additional_instruction_text, mail.outbox[0].body)
105

Diana Huang committed
106 107 108 109
    def test_purchase_item_failure(self):
        # once again, we're testing against the specific implementation of
        # CertificateItem
        cart = Order.get_cart_for_user(user=self.user)
110
        CertificateItem.add_to_order(cart, self.course_id, self.cost, 'honor')
Diana Huang committed
111 112 113 114 115
        with patch('shoppingcart.models.CertificateItem.save', side_effect=DatabaseError):
            with self.assertRaises(DatabaseError):
                cart.purchase()
                # verify that we rolled back the entire transaction
                self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course_id))
116 117
                # verify that e-mail wasn't sent
                self.assertEquals(len(mail.outbox), 0)
Diana Huang committed
118

119 120 121 122 123 124 125 126
    def test_purchase_twice(self):
        cart = Order.get_cart_for_user(self.user)
        CertificateItem.add_to_order(cart, self.course_id, self.cost, 'honor')
        # purchase the cart more than once
        cart.purchase()
        cart.purchase()
        self.assertEquals(len(mail.outbox), 1)

127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
    @patch('shoppingcart.models.log.error')
    def test_purchase_item_email_smtp_failure(self, error_logger):
        cart = Order.get_cart_for_user(user=self.user)
        CertificateItem.add_to_order(cart, self.course_id, self.cost, 'honor')
        with patch('shoppingcart.models.send_mail', side_effect=smtplib.SMTPException):
            cart.purchase()
            self.assertTrue(error_logger.called)

    @patch('shoppingcart.models.log.error')
    def test_purchase_item_email_boto_failure(self, error_logger):
        cart = Order.get_cart_for_user(user=self.user)
        CertificateItem.add_to_order(cart, self.course_id, self.cost, 'honor')
        with patch('shoppingcart.models.send_mail', side_effect=BotoServerError("status", "reason")):
            cart.purchase()
            self.assertTrue(error_logger.called)

143 144
    def purchase_with_data(self, cart):
        """ purchase a cart with billing information """
145
        CertificateItem.add_to_order(cart, self.course_id, self.cost, 'honor')
146 147 148 149 150 151 152 153 154 155 156 157 158
        cart.purchase(
            first='John',
            last='Smith',
            street1='11 Cambridge Center',
            street2='Suite 101',
            city='Cambridge',
            state='MA',
            postalcode='02412',
            country='US',
            ccnum='1111',
            cardtype='001',
        )

159
    @patch('shoppingcart.models.render_to_string')
160
    @patch.dict(settings.MITX_FEATURES, {'STORE_BILLING_INFO': True})
161
    def test_billing_info_storage_on(self, render):
162 163 164 165 166 167 168 169 170 171 172 173
        cart = Order.get_cart_for_user(self.user)
        self.purchase_with_data(cart)
        self.assertNotEqual(cart.bill_to_first, '')
        self.assertNotEqual(cart.bill_to_last, '')
        self.assertNotEqual(cart.bill_to_street1, '')
        self.assertNotEqual(cart.bill_to_street2, '')
        self.assertNotEqual(cart.bill_to_postalcode, '')
        self.assertNotEqual(cart.bill_to_ccnum, '')
        self.assertNotEqual(cart.bill_to_cardtype, '')
        self.assertNotEqual(cart.bill_to_city, '')
        self.assertNotEqual(cart.bill_to_state, '')
        self.assertNotEqual(cart.bill_to_country, '')
174 175
        ((_, context), _) = render.call_args
        self.assertTrue(context['has_billing_info'])
176

177
    @patch('shoppingcart.models.render_to_string')
178
    @patch.dict(settings.MITX_FEATURES, {'STORE_BILLING_INFO': False})
179
    def test_billing_info_storage_off(self, render):
180 181 182 183 184 185 186 187 188 189 190 191 192
        cart = Order.get_cart_for_user(self.user)
        self.purchase_with_data(cart)
        self.assertNotEqual(cart.bill_to_first, '')
        self.assertNotEqual(cart.bill_to_last, '')
        self.assertNotEqual(cart.bill_to_city, '')
        self.assertNotEqual(cart.bill_to_state, '')
        self.assertNotEqual(cart.bill_to_country, '')
        self.assertNotEqual(cart.bill_to_postalcode, '')
        # things we expect to be missing when the feature is off
        self.assertEqual(cart.bill_to_street1, '')
        self.assertEqual(cart.bill_to_street2, '')
        self.assertEqual(cart.bill_to_ccnum, '')
        self.assertEqual(cart.bill_to_cardtype, '')
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
        ((_, context), _) = render.call_args
        self.assertFalse(context['has_billing_info'])

    mock_gen_inst = MagicMock(return_value=(OrderItemSubclassPK(OrderItem, 1), set([])))

    def test_generate_receipt_instructions_callchain(self):
        """
        This tests the generate_receipt_instructions call chain (ie calling the function on the
        cart also calls it on items in the cart
        """
        cart = Order.get_cart_for_user(self.user)
        item = OrderItem(user=self.user, order=cart)
        item.save()
        self.assertTrue(cart.has_items())
        with patch.object(OrderItem, 'generate_receipt_instructions', self.mock_gen_inst):
            cart.generate_receipt_instructions()
            self.mock_gen_inst.assert_called_with()
Diana Huang committed
210

211

212 213 214 215
class OrderItemTest(TestCase):
    def setUp(self):
        self.user = UserFactory.create()

216
    def test_order_item_purchased_callback(self):
217 218 219 220 221 222 223
        """
        This tests that calling purchased_callback on the base OrderItem class raises NotImplementedError
        """
        item = OrderItem(user=self.user, order=Order.get_cart_for_user(self.user))
        with self.assertRaises(NotImplementedError):
            item.purchased_callback()

224 225 226 227 228 229 230 231 232 233 234 235 236
    def test_order_item_generate_receipt_instructions(self):
        """
        This tests that the generate_receipt_instructions call chain and also
        that calling it on the base OrderItem class returns an empty list
        """
        cart = Order.get_cart_for_user(self.user)
        item = OrderItem(user=self.user, order=cart)
        item.save()
        self.assertTrue(cart.has_items())
        (inst_dict, inst_set) = cart.generate_receipt_instructions()
        self.assertDictEqual({item.pk_with_subclass: set([])}, inst_dict)
        self.assertEquals(set([]), inst_set)

237

238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
class PaidCourseRegistrationTest(ModuleStoreTestCase):
    def setUp(self):
        self.user = UserFactory.create()
        self.course_id = "MITx/999/Robot_Super_Course"
        self.cost = 40
        self.course = CourseFactory.create(org='MITx', number='999', display_name='Robot Super Course')
        self.course_mode = CourseMode(course_id=self.course_id,
                                      mode_slug="honor",
                                      mode_display_name="honor cert",
                                      min_price=self.cost)
        self.course_mode.save()
        self.cart = Order.get_cart_for_user(self.user)

    def test_add_to_order(self):
        reg1 = PaidCourseRegistration.add_to_order(self.cart, self.course_id)

        self.assertEqual(reg1.unit_cost, self.cost)
        self.assertEqual(reg1.line_cost, self.cost)
        self.assertEqual(reg1.unit_cost, self.course_mode.min_price)
        self.assertEqual(reg1.mode, "honor")
        self.assertEqual(reg1.user, self.user)
        self.assertEqual(reg1.status, "cart")
261 262
        self.assertTrue(PaidCourseRegistration.contained_in_order(self.cart, self.course_id))
        self.assertFalse(PaidCourseRegistration.contained_in_order(self.cart, self.course_id + "abcd"))
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
        self.assertEqual(self.cart.total_cost, self.cost)

    def test_add_with_default_mode(self):
        """
        Tests add_to_cart where the mode specified in the argument is NOT in the database
        and NOT the default "honor".  In this case it just adds the user in the CourseMode.DEFAULT_MODE, 0 price
        """
        reg1 = PaidCourseRegistration.add_to_order(self.cart, self.course_id, mode_slug="DNE")

        self.assertEqual(reg1.unit_cost, 0)
        self.assertEqual(reg1.line_cost, 0)
        self.assertEqual(reg1.mode, "honor")
        self.assertEqual(reg1.user, self.user)
        self.assertEqual(reg1.status, "cart")
        self.assertEqual(self.cart.total_cost, 0)
278
        self.assertTrue(PaidCourseRegistration.contained_in_order(self.cart, self.course_id))
279 280 281

    def test_purchased_callback(self):
        reg1 = PaidCourseRegistration.add_to_order(self.cart, self.course_id)
282
        self.cart.purchase()
283
        self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course_id))
284
        reg1 = PaidCourseRegistration.objects.get(id=reg1.id)  # reload from DB to get side-effect
285
        self.assertEqual(reg1.status, "purchased")
286

287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
    def test_generate_receipt_instructions(self):
        """
        Add 2 courses to the order and make sure the instruction_set only contains 1 element (no dups)
        """
        course2 = CourseFactory.create(org='MITx', number='998', display_name='Robot Duper Course')
        course_mode2 = CourseMode(course_id=course2.id,
                                  mode_slug="honor",
                                  mode_display_name="honor cert",
                                  min_price=self.cost)
        course_mode2.save()
        pr1 = PaidCourseRegistration.add_to_order(self.cart, self.course_id)
        pr2 = PaidCourseRegistration.add_to_order(self.cart, course2.id)
        self.cart.purchase()
        inst_dict, inst_set = self.cart.generate_receipt_instructions()
        self.assertEqual(2, len(inst_dict))
        self.assertEqual(1, len(inst_set))
        self.assertIn("dashboard", inst_set.pop())
        self.assertIn(pr1.pk_with_subclass, inst_dict)
        self.assertIn(pr2.pk_with_subclass, inst_dict)

307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
    def test_purchased_callback_exception(self):
        reg1 = PaidCourseRegistration.add_to_order(self.cart, self.course_id)
        reg1.course_id = "changedforsomereason"
        reg1.save()
        with self.assertRaises(PurchasedCallbackException):
            reg1.purchased_callback()
        self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course_id))

        reg1.course_id = "abc/efg/hij"
        reg1.save()
        with self.assertRaises(PurchasedCallbackException):
            reg1.purchased_callback()
        self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course_id))


322
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
323
class CertificateItemTest(ModuleStoreTestCase):
324 325 326 327 328
    """
    Tests for verifying specific CertificateItem functionality
    """
    def setUp(self):
        self.user = UserFactory.create()
329
        self.course_id = "org/test/Test_Course"
330
        self.cost = 40
331
        CourseFactory.create(org='org', number='test', run='course', display_name='Test Course')
332 333 334 335 336 337 338 339 340 341
        course_mode = CourseMode(course_id=self.course_id,
                                 mode_slug="honor",
                                 mode_display_name="honor cert",
                                 min_price=self.cost)
        course_mode.save()
        course_mode = CourseMode(course_id=self.course_id,
                                 mode_slug="verified",
                                 mode_display_name="verified cert",
                                 min_price=self.cost)
        course_mode.save()
Diana Huang committed
342

343 344 345 346 347 348 349 350 351
    def test_existing_enrollment(self):
        CourseEnrollment.enroll(self.user, self.course_id)
        cart = Order.get_cart_for_user(user=self.user)
        CertificateItem.add_to_order(cart, self.course_id, self.cost, 'verified')
        # verify that we are still enrolled
        self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course_id))
        cart.purchase()
        enrollment = CourseEnrollment.objects.get(user=self.user, course_id=self.course_id)
        self.assertEquals(enrollment.mode, u'verified')
352 353 354 355 356 357 358 359 360 361 362

    def test_single_item_template(self):
        cart = Order.get_cart_for_user(user=self.user)
        cert_item = CertificateItem.add_to_order(cart, self.course_id, self.cost, 'verified')

        self.assertEquals(cert_item.single_item_receipt_template,
                          'shoppingcart/verified_cert_receipt.html')

        cert_item = CertificateItem.add_to_order(cart, self.course_id, self.cost, 'honor')
        self.assertEquals(cert_item.single_item_receipt_template,
                          'shoppingcart/receipt.html')