Ex-74 Registration Code redemption fix the translation issues added a check if a user is already registered in a course. Changed the messages added course depth=0 and removed pep8 violations Ex-72-added additional billing information Added a new CSV file in the instructor dashboard sales tab to download all the order sales separated from the invoice sales fix path to image updated the failed unit tests and add some minor tweaks in the Shoppingcart.scss Ex-78 New UI In receipt page EX-73 Purchasers can buy a course on behalf of a different student WL-78 updated the receipt page UI. Wl-72 updated Billing Information UI and removed the Order Address fields WL-71 Remove Purchase Type Buttons from UI WL-71 Updated minor UI issues and updated test cases WL-78 updated the enrollment links in the receipt page made changes in Order generated sales csv in Instructor Dashboard. The total_registration_codes and total_used_codes were not correctly stored in the csv file. 1) The total_registration_codes were not filtered with course_id. 2) The total_used_codes that a user had redeemed were not correctly included in the CSV. added a fix in the courseware view to let the users visit the courseware if they have enrolled in the course by clicking on the enrollment link rebase and resolved conflicts with master WL-97 Bulk Registration Email Confirmation Below is the commit summary. - Make email text bold as per requirement. - Improve email template quality and reorder points. - Add text in billing details page : "if no additional billing details are populated the payment confirmation will be sent to the user making the purchase" - Update text on receipt page "You have successfully purchase 3 course registration codes" WL-100 fixed the bug on the edit/add coupon and set course price. Ajax requests were duplicating in each callback. fixed this issue by creating the manual ajax request rather than the Lean Modal Ajax requests allow for better White Label branding in shopping cart purchase emails fix up typos and text fix goof fix fix incorporated model changes as suggested by Jason. updated order sales csv updated test cases for CourseRegCodeItem model and csv for the order generated sales updated the migrations history fixed the lms acceptance tests Be sure to check for multiple types address PR feedback PR feedback PR feedback pep8 fix
1390 lines
65 KiB
Python
1390 lines
65 KiB
Python
"""
|
|
Tests for Shopping Cart views
|
|
"""
|
|
from urlparse import urlparse
|
|
|
|
from django.http import HttpRequest
|
|
from django.conf import settings
|
|
from django.test import TestCase
|
|
from django.test.utils import override_settings
|
|
from django.core.urlresolvers import reverse
|
|
from django.utils.translation import ugettext as _
|
|
from django.contrib.admin.sites import AdminSite
|
|
from django.contrib.auth.models import Group, User
|
|
from django.contrib.messages.storage.fallback import FallbackStorage
|
|
from django.core import mail
|
|
|
|
from django.core.cache import cache
|
|
from pytz import UTC
|
|
from freezegun import freeze_time
|
|
from datetime import datetime, timedelta
|
|
from mock import patch, Mock
|
|
import ddt
|
|
|
|
from xmodule.modulestore.tests.django_utils import (
|
|
ModuleStoreTestCase, mixed_store_config
|
|
)
|
|
from xmodule.modulestore.tests.factories import CourseFactory
|
|
from shoppingcart.views import _can_download_report, _get_date_from_str
|
|
from shoppingcart.models import (
|
|
Order, CertificateItem, PaidCourseRegistration, CourseRegCodeItem,
|
|
Coupon, CourseRegistrationCode, RegistrationCodeRedemption,
|
|
DonationConfiguration
|
|
)
|
|
from student.tests.factories import UserFactory, AdminFactory
|
|
from courseware.tests.factories import InstructorFactory
|
|
from student.models import CourseEnrollment
|
|
from course_modes.models import CourseMode
|
|
from edxmako.shortcuts import render_to_response
|
|
from shoppingcart.processors import render_purchase_form_html
|
|
from shoppingcart.admin import SoftDeleteCouponAdmin
|
|
from shoppingcart.views import initialize_report
|
|
from shoppingcart.tests.payment_fake import PaymentFakeView
|
|
from decimal import Decimal
|
|
import json
|
|
|
|
|
|
def mock_render_purchase_form_html(*args, **kwargs):
|
|
return render_purchase_form_html(*args, **kwargs)
|
|
|
|
form_mock = Mock(side_effect=mock_render_purchase_form_html)
|
|
|
|
def mock_render_to_response(*args, **kwargs):
|
|
return render_to_response(*args, **kwargs)
|
|
|
|
render_mock = Mock(side_effect=mock_render_to_response)
|
|
|
|
postpay_mock = Mock()
|
|
|
|
|
|
# Since we don't need any XML course fixtures, use a modulestore configuration
|
|
# that disables the XML modulestore.
|
|
MODULESTORE_CONFIG = mixed_store_config(settings.COMMON_TEST_DATA_ROOT, {}, include_xml=False)
|
|
|
|
|
|
@override_settings(MODULESTORE=MODULESTORE_CONFIG)
|
|
class ShoppingCartViewsTests(ModuleStoreTestCase):
|
|
def setUp(self):
|
|
patcher = patch('student.models.tracker')
|
|
self.mock_tracker = patcher.start()
|
|
self.user = UserFactory.create()
|
|
self.user.set_password('password')
|
|
self.user.save()
|
|
self.instructor = AdminFactory.create()
|
|
self.cost = 40
|
|
self.coupon_code = 'abcde'
|
|
self.reg_code = 'qwerty'
|
|
self.percentage_discount = 10
|
|
self.course = CourseFactory.create(org='MITx', number='999', display_name='Robot Super Course')
|
|
self.course_key = self.course.id
|
|
self.course_mode = CourseMode(course_id=self.course_key,
|
|
mode_slug="honor",
|
|
mode_display_name="honor cert",
|
|
min_price=self.cost)
|
|
self.course_mode.save()
|
|
|
|
#Saving another testing course mode
|
|
self.testing_cost = 20
|
|
self.testing_course = CourseFactory.create(org='edX', number='888', display_name='Testing Super Course')
|
|
self.testing_course_mode = CourseMode(course_id=self.testing_course.id,
|
|
mode_slug="honor",
|
|
mode_display_name="testing honor cert",
|
|
min_price=self.testing_cost)
|
|
self.testing_course_mode.save()
|
|
|
|
verified_course = CourseFactory.create(org='org', number='test', display_name='Test Course')
|
|
self.verified_course_key = verified_course.id
|
|
self.cart = Order.get_cart_for_user(self.user)
|
|
self.addCleanup(patcher.stop)
|
|
|
|
def get_discount(self, cost):
|
|
"""
|
|
This method simple return the discounted amount
|
|
"""
|
|
val = Decimal("{0:.2f}".format(Decimal(self.percentage_discount / 100.00) * cost))
|
|
return cost - val
|
|
|
|
def add_coupon(self, course_key, is_active, code):
|
|
"""
|
|
add dummy coupon into models
|
|
"""
|
|
coupon = Coupon(code=code, description='testing code', course_id=course_key,
|
|
percentage_discount=self.percentage_discount, created_by=self.user, is_active=is_active)
|
|
coupon.save()
|
|
|
|
def add_reg_code(self, course_key):
|
|
"""
|
|
add dummy registration code into models
|
|
"""
|
|
course_reg_code = CourseRegistrationCode(code=self.reg_code, course_id=course_key, created_by=self.user)
|
|
course_reg_code.save()
|
|
|
|
def add_course_to_user_cart(self, course_key):
|
|
"""
|
|
adding course to user cart
|
|
"""
|
|
self.login_user()
|
|
reg_item = PaidCourseRegistration.add_to_order(self.cart, course_key)
|
|
return reg_item
|
|
|
|
def login_user(self):
|
|
self.client.login(username=self.user.username, password="password")
|
|
|
|
def test_add_course_to_cart_anon(self):
|
|
resp = self.client.post(reverse('shoppingcart.views.add_course_to_cart', args=[self.course_key.to_deprecated_string()]))
|
|
self.assertEqual(resp.status_code, 403)
|
|
|
|
def test_billing_details(self):
|
|
billing_url = reverse('billing_details')
|
|
self.login_user()
|
|
|
|
# page not found error because order_type is not business
|
|
resp = self.client.get(billing_url)
|
|
self.assertEqual(resp.status_code, 404)
|
|
|
|
#chagne the order_type to business
|
|
self.cart.order_type = 'business'
|
|
self.cart.save()
|
|
resp = self.client.get(billing_url)
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
data = {'company_name': 'Test Company', 'company_contact_name': 'JohnDoe',
|
|
'company_contact_email': 'john@est.com', 'recipient_name': 'Mocker',
|
|
'recipient_email': 'mock@germ.com', 'company_address_line_1': 'DC Street # 1',
|
|
'company_address_line_2': '',
|
|
'company_city': 'DC', 'company_state': 'NY', 'company_zip': '22003', 'company_country': 'US',
|
|
'customer_reference_number': 'PO#23'}
|
|
|
|
resp = self.client.post(billing_url, data)
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
def test_add_course_to_cart_already_in_cart(self):
|
|
PaidCourseRegistration.add_to_order(self.cart, self.course_key)
|
|
self.login_user()
|
|
resp = self.client.post(reverse('shoppingcart.views.add_course_to_cart', args=[self.course_key.to_deprecated_string()]))
|
|
self.assertEqual(resp.status_code, 400)
|
|
self.assertIn('The course {0} is already in your cart.'.format(self.course_key.to_deprecated_string()), resp.content)
|
|
|
|
def test_course_discount_invalid_coupon(self):
|
|
self.add_coupon(self.course_key, True, self.coupon_code)
|
|
self.add_course_to_user_cart(self.course_key)
|
|
non_existing_code = "non_existing_code"
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': non_existing_code})
|
|
self.assertEqual(resp.status_code, 404)
|
|
self.assertIn("Discount does not exist against code '{0}'.".format(non_existing_code), resp.content)
|
|
|
|
def test_valid_qty_greater_then_one_and_purchase_type_should_business(self):
|
|
qty = 2
|
|
item = self.add_course_to_user_cart(self.course_key)
|
|
resp = self.client.post(reverse('shoppingcart.views.update_user_cart'), {'ItemId': item.id, 'qty': qty})
|
|
self.assertEqual(resp.status_code, 200)
|
|
data = json.loads(resp.content)
|
|
self.assertEqual(data['total_cost'], item.unit_cost * qty)
|
|
cart = Order.get_cart_for_user(self.user)
|
|
self.assertEqual(cart.order_type, 'business')
|
|
|
|
def test_in_valid_qty_case(self):
|
|
# invalid quantity, Quantity must be between 1 and 1000.
|
|
qty = 0
|
|
item = self.add_course_to_user_cart(self.course_key)
|
|
resp = self.client.post(reverse('shoppingcart.views.update_user_cart'), {'ItemId': item.id, 'qty': qty})
|
|
self.assertEqual(resp.status_code, 400)
|
|
self.assertIn("Quantity must be between 1 and 1000.", resp.content)
|
|
|
|
# invalid quantity, Quantity must be an integer.
|
|
qty = 'abcde'
|
|
resp = self.client.post(reverse('shoppingcart.views.update_user_cart'), {'ItemId': item.id, 'qty': qty})
|
|
self.assertEqual(resp.status_code, 400)
|
|
self.assertIn("Quantity must be an integer.", resp.content)
|
|
|
|
# invalid quantity, Quantity is not present in request
|
|
resp = self.client.post(reverse('shoppingcart.views.update_user_cart'), {'ItemId': item.id})
|
|
self.assertEqual(resp.status_code, 400)
|
|
self.assertIn("Quantity must be between 1 and 1000.", resp.content)
|
|
|
|
def test_valid_qty_but_item_not_found(self):
|
|
qty = 2
|
|
item_id = '-1'
|
|
self.login_user()
|
|
resp = self.client.post(reverse('shoppingcart.views.update_user_cart'), {'ItemId': item_id, 'qty': qty})
|
|
self.assertEqual(resp.status_code, 404)
|
|
self.assertEqual('Order item does not exist.', resp.content)
|
|
|
|
# now testing the case if item id not found in request,
|
|
resp = self.client.post(reverse('shoppingcart.views.update_user_cart'), {'qty': qty})
|
|
self.assertEqual(resp.status_code, 400)
|
|
self.assertEqual('Order item not found in request.', resp.content)
|
|
|
|
def test_purchase_type_should_be_personal_when_qty_is_one(self):
|
|
qty = 1
|
|
item = self.add_course_to_user_cart(self.course_key)
|
|
resp = self.client.post(reverse('shoppingcart.views.update_user_cart'), {'ItemId': item.id, 'qty': qty})
|
|
self.assertEqual(resp.status_code, 200)
|
|
data = json.loads(resp.content)
|
|
self.assertEqual(data['total_cost'], item.unit_cost * 1)
|
|
cart = Order.get_cart_for_user(self.user)
|
|
self.assertEqual(cart.order_type, 'personal')
|
|
|
|
def test_purchase_type_on_removing_item_and_cart_has_item_with_qty_one(self):
|
|
qty = 5
|
|
self.add_course_to_user_cart(self.course_key)
|
|
item2 = self.add_course_to_user_cart(self.testing_course.id)
|
|
resp = self.client.post(reverse('shoppingcart.views.update_user_cart'), {'ItemId': item2.id, 'qty': qty})
|
|
self.assertEqual(resp.status_code, 200)
|
|
cart = Order.get_cart_for_user(self.user)
|
|
cart_items = cart.orderitem_set.all()
|
|
test_flag = False
|
|
for cartitem in cart_items:
|
|
if cartitem.qty == 5:
|
|
test_flag = True
|
|
resp = self.client.post(reverse('shoppingcart.views.remove_item', args=[]), {'id': cartitem.id})
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertTrue(test_flag)
|
|
|
|
cart = Order.get_cart_for_user(self.user)
|
|
self.assertEqual(cart.order_type, 'personal')
|
|
|
|
def test_billing_details_btn_in_cart_when_qty_is_greater_than_one(self):
|
|
qty = 5
|
|
item = self.add_course_to_user_cart(self.course_key)
|
|
resp = self.client.post(reverse('shoppingcart.views.update_user_cart'), {'ItemId': item.id, 'qty': qty})
|
|
self.assertEqual(resp.status_code, 200)
|
|
resp = self.client.get(reverse('shoppingcart.views.show_cart', args=[]))
|
|
self.assertIn("Billing Details", resp.content)
|
|
|
|
def test_purchase_type_should_be_personal_when_remove_all_items_from_cart(self):
|
|
item1 = self.add_course_to_user_cart(self.course_key)
|
|
resp = self.client.post(reverse('shoppingcart.views.update_user_cart'), {'ItemId': item1.id, 'qty': 2})
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
item2 = self.add_course_to_user_cart(self.testing_course.id)
|
|
resp = self.client.post(reverse('shoppingcart.views.update_user_cart'), {'ItemId': item2.id, 'qty': 5})
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
cart = Order.get_cart_for_user(self.user)
|
|
cart_items = cart.orderitem_set.all()
|
|
test_flag = False
|
|
for cartitem in cart_items:
|
|
test_flag = True
|
|
resp = self.client.post(reverse('shoppingcart.views.remove_item', args=[]), {'id': cartitem.id})
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertTrue(test_flag)
|
|
|
|
cart = Order.get_cart_for_user(self.user)
|
|
self.assertEqual(cart.order_type, 'personal')
|
|
|
|
def test_use_valid_coupon_code_and_qty_is_greater_than_one(self):
|
|
qty = 5
|
|
item = self.add_course_to_user_cart(self.course_key)
|
|
resp = self.client.post(reverse('shoppingcart.views.update_user_cart'), {'ItemId': item.id, 'qty': qty})
|
|
self.assertEqual(resp.status_code, 200)
|
|
data = json.loads(resp.content)
|
|
self.assertEqual(data['total_cost'], item.unit_cost * qty)
|
|
|
|
# use coupon code
|
|
self.add_coupon(self.course_key, True, self.coupon_code)
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code})
|
|
item = self.cart.orderitem_set.all().select_subclasses()[0]
|
|
self.assertEquals(item.unit_cost * qty, 180)
|
|
|
|
def test_course_discount_invalid_reg_code(self):
|
|
self.add_reg_code(self.course_key)
|
|
self.add_course_to_user_cart(self.course_key)
|
|
non_existing_code = "non_existing_code"
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': non_existing_code})
|
|
self.assertEqual(resp.status_code, 404)
|
|
self.assertIn("Discount does not exist against code '{0}'.".format(non_existing_code), resp.content)
|
|
|
|
def test_course_discount_inactive_coupon(self):
|
|
self.add_coupon(self.course_key, False, self.coupon_code)
|
|
self.add_course_to_user_cart(self.course_key)
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code})
|
|
self.assertEqual(resp.status_code, 404)
|
|
self.assertIn("Discount does not exist against code '{0}'.".format(self.coupon_code), resp.content)
|
|
|
|
def test_course_does_not_exist_in_cart_against_valid_coupon(self):
|
|
course_key = self.course_key.to_deprecated_string() + 'testing'
|
|
self.add_coupon(course_key, True, self.coupon_code)
|
|
self.add_course_to_user_cart(self.course_key)
|
|
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code})
|
|
self.assertEqual(resp.status_code, 404)
|
|
self.assertIn("Coupon '{0}' is not valid for any course in the shopping cart.".format(self.coupon_code), resp.content)
|
|
|
|
def test_course_does_not_exist_in_cart_against_valid_reg_code(self):
|
|
course_key = self.course_key.to_deprecated_string() + 'testing'
|
|
self.add_reg_code(course_key)
|
|
self.add_course_to_user_cart(self.course_key)
|
|
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.reg_code})
|
|
self.assertEqual(resp.status_code, 404)
|
|
self.assertIn("Code '{0}' is not valid for any course in the shopping cart.".format(self.reg_code), resp.content)
|
|
|
|
def test_course_discount_for_valid_active_coupon_code(self):
|
|
|
|
self.add_coupon(self.course_key, True, self.coupon_code)
|
|
self.add_course_to_user_cart(self.course_key)
|
|
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code})
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
# unit price should be updated for that course
|
|
item = self.cart.orderitem_set.all().select_subclasses()[0]
|
|
self.assertEquals(item.unit_cost, self.get_discount(self.cost))
|
|
|
|
# after getting 10 percent discount
|
|
self.assertEqual(self.cart.total_cost, self.get_discount(self.cost))
|
|
|
|
# now using the same coupon code against the same order.
|
|
# Only one coupon redemption should be allowed per order.
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code})
|
|
self.assertEqual(resp.status_code, 400)
|
|
self.assertIn("Only one coupon redemption is allowed against an order", resp.content)
|
|
|
|
def test_course_discount_against_two_distinct_coupon_codes(self):
|
|
|
|
self.add_coupon(self.course_key, True, self.coupon_code)
|
|
self.add_course_to_user_cart(self.course_key)
|
|
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code})
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
# unit price should be updated for that course
|
|
item = self.cart.orderitem_set.all().select_subclasses()[0]
|
|
self.assertEquals(item.unit_cost, self.get_discount(self.cost))
|
|
|
|
# now using another valid active coupon code.
|
|
# Only one coupon redemption should be allowed per order.
|
|
self.add_coupon(self.course_key, True, 'abxyz')
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': 'abxyz'})
|
|
self.assertEqual(resp.status_code, 400)
|
|
self.assertIn("Only one coupon redemption is allowed against an order", resp.content)
|
|
|
|
def test_same_coupons_code_on_multiple_courses(self):
|
|
|
|
# add two same coupon codes on two different courses
|
|
self.add_coupon(self.course_key, True, self.coupon_code)
|
|
self.add_coupon(self.testing_course.id, True, self.coupon_code)
|
|
self.add_course_to_user_cart(self.course_key)
|
|
self.add_course_to_user_cart(self.testing_course.id)
|
|
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code})
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
# unit price should be updated for that course
|
|
item = self.cart.orderitem_set.all().select_subclasses()[0]
|
|
self.assertEquals(item.unit_cost, self.get_discount(self.cost))
|
|
|
|
item = self.cart.orderitem_set.all().select_subclasses()[1]
|
|
self.assertEquals(item.unit_cost, self.get_discount(self.testing_cost))
|
|
|
|
def test_soft_delete_coupon(self): # pylint: disable=E1101
|
|
self.add_coupon(self.course_key, True, self.coupon_code)
|
|
coupon = Coupon(code='TestCode', description='testing', course_id=self.course_key,
|
|
percentage_discount=12, created_by=self.user, is_active=True)
|
|
coupon.save()
|
|
self.assertEquals(coupon.__unicode__(), '[Coupon] code: TestCode course: MITx/999/Robot_Super_Course')
|
|
admin = User.objects.create_user('Mark', 'admin+courses@edx.org', 'foo')
|
|
admin.is_staff = True
|
|
get_coupon = Coupon.objects.get(id=1)
|
|
request = HttpRequest()
|
|
request.user = admin
|
|
setattr(request, 'session', 'session') # pylint: disable=E1101
|
|
messages = FallbackStorage(request) # pylint: disable=E1101
|
|
setattr(request, '_messages', messages) # pylint: disable=E1101
|
|
coupon_admin = SoftDeleteCouponAdmin(Coupon, AdminSite())
|
|
test_query_set = coupon_admin.queryset(request)
|
|
test_actions = coupon_admin.get_actions(request)
|
|
self.assertTrue('really_delete_selected' in test_actions['really_delete_selected'])
|
|
self.assertEqual(get_coupon.is_active, True)
|
|
coupon_admin.really_delete_selected(request, test_query_set) # pylint: disable=E1101
|
|
for coupon in test_query_set:
|
|
self.assertEqual(coupon.is_active, False)
|
|
coupon_admin.delete_model(request, get_coupon) # pylint: disable=E1101
|
|
self.assertEqual(get_coupon.is_active, False)
|
|
|
|
coupon = Coupon(code='TestCode123', description='testing123', course_id=self.course_key,
|
|
percentage_discount=22, created_by=self.user, is_active=True)
|
|
coupon.save()
|
|
test_query_set = coupon_admin.queryset(request)
|
|
coupon_admin.really_delete_selected(request, test_query_set) # pylint: disable=E1101
|
|
for coupon in test_query_set:
|
|
self.assertEqual(coupon.is_active, False)
|
|
|
|
def test_course_free_discount_for_valid_active_reg_code(self):
|
|
|
|
self.add_reg_code(self.course_key)
|
|
self.add_course_to_user_cart(self.course_key)
|
|
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.reg_code})
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
# unit price should be updated to 0 for that course
|
|
item = self.cart.orderitem_set.all().select_subclasses()[0]
|
|
self.assertEquals(item.unit_cost, 0)
|
|
self.assertEqual(self.cart.total_cost, 0)
|
|
|
|
# now testing registration code already used scenario, reusing the same code
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.reg_code})
|
|
self.assertEqual(resp.status_code, 400)
|
|
self.assertIn("Oops! The code '{0}' you entered is either invalid or expired".format(self.reg_code), resp.content)
|
|
|
|
@patch('shoppingcart.views.log.debug')
|
|
def test_non_existing_coupon_redemption_on_removing_item(self, debug_log):
|
|
|
|
reg_item = self.add_course_to_user_cart(self.course_key)
|
|
resp = self.client.post(reverse('shoppingcart.views.remove_item', args=[]),
|
|
{'id': reg_item.id})
|
|
debug_log.assert_called_with(
|
|
'Code redemption does not exist for order item id={0}.'.format(reg_item.id))
|
|
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertEquals(self.cart.orderitem_set.count(), 0)
|
|
|
|
@patch('shoppingcart.views.log.info')
|
|
def test_existing_coupon_redemption_on_removing_item(self, info_log):
|
|
|
|
self.add_coupon(self.course_key, True, self.coupon_code)
|
|
reg_item = self.add_course_to_user_cart(self.course_key)
|
|
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code})
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
resp = self.client.post(reverse('shoppingcart.views.remove_item', args=[]),
|
|
{'id': reg_item.id})
|
|
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertEquals(self.cart.orderitem_set.count(), 0)
|
|
info_log.assert_called_with(
|
|
'Coupon "{0}" redemption entry removed for user "{1}" for order item "{2}"'.format(self.coupon_code, self.user, reg_item.id))
|
|
|
|
@patch('shoppingcart.views.log.info')
|
|
def test_reset_redemption_for_coupon(self, info_log):
|
|
|
|
self.add_coupon(self.course_key, True, self.coupon_code)
|
|
reg_item = self.add_course_to_user_cart(self.course_key)
|
|
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code})
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
resp = self.client.post(reverse('shoppingcart.views.reset_code_redemption', args=[]))
|
|
|
|
self.assertEqual(resp.status_code, 200)
|
|
info_log.assert_called_with(
|
|
'Coupon redemption entry removed for user {0} for order {1}'.format(self.user, reg_item.id))
|
|
|
|
@patch('shoppingcart.views.log.info')
|
|
def test_reset_redemption_for_registration_code(self, info_log):
|
|
|
|
self.add_reg_code(self.course_key)
|
|
reg_item = self.add_course_to_user_cart(self.course_key)
|
|
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.reg_code})
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
resp = self.client.post(reverse('shoppingcart.views.reset_code_redemption', args=[]))
|
|
|
|
self.assertEqual(resp.status_code, 200)
|
|
info_log.assert_called_with(
|
|
'Registration code redemption entry removed for user {0} for order {1}'.format(self.user, reg_item.id))
|
|
|
|
@patch('shoppingcart.views.log.info')
|
|
def test_existing_reg_code_redemption_on_removing_item(self, info_log):
|
|
|
|
self.add_reg_code(self.course_key)
|
|
reg_item = self.add_course_to_user_cart(self.course_key)
|
|
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.reg_code})
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
resp = self.client.post(reverse('shoppingcart.views.remove_item', args=[]),
|
|
{'id': reg_item.id})
|
|
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertEquals(self.cart.orderitem_set.count(), 0)
|
|
info_log.assert_called_with(
|
|
'Registration code "{0}" redemption entry removed for user "{1}" for order item "{2}"'.format(self.reg_code, self.user, reg_item.id))
|
|
|
|
@patch('shoppingcart.views.log.info')
|
|
def test_coupon_discount_for_multiple_courses_in_cart(self, info_log):
|
|
|
|
reg_item = self.add_course_to_user_cart(self.course_key)
|
|
self.add_coupon(self.course_key, True, self.coupon_code)
|
|
cert_item = CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor')
|
|
self.assertEquals(self.cart.orderitem_set.count(), 2)
|
|
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code})
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
# unit_cost should be updated for that particular course for which coupon code is registered
|
|
items = self.cart.orderitem_set.all().select_subclasses()
|
|
for item in items:
|
|
if item.id == reg_item.id:
|
|
self.assertEquals(item.unit_cost, self.get_discount(self.cost))
|
|
elif item.id == cert_item.id:
|
|
self.assertEquals(item.list_price, None)
|
|
|
|
# Delete the discounted item, corresponding coupon redemption should be removed for that particular discounted item
|
|
resp = self.client.post(reverse('shoppingcart.views.remove_item', args=[]),
|
|
{'id': reg_item.id})
|
|
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertEquals(self.cart.orderitem_set.count(), 1)
|
|
info_log.assert_called_with(
|
|
'Coupon "{0}" redemption entry removed for user "{1}" for order item "{2}"'.format(self.coupon_code, self.user, reg_item.id))
|
|
|
|
@patch('shoppingcart.views.log.info')
|
|
def test_reg_code_free_discount_with_multiple_courses_in_cart(self, info_log):
|
|
|
|
reg_item = self.add_course_to_user_cart(self.course_key)
|
|
self.add_reg_code(self.course_key)
|
|
cert_item = CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor')
|
|
self.assertEquals(self.cart.orderitem_set.count(), 2)
|
|
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.reg_code})
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
# unit_cost should be 0 for that particular course for which registration code is registered
|
|
items = self.cart.orderitem_set.all().select_subclasses()
|
|
for item in items:
|
|
if item.id == reg_item.id:
|
|
self.assertEquals(item.unit_cost, 0)
|
|
elif item.id == cert_item.id:
|
|
self.assertEquals(item.list_price, None)
|
|
|
|
# Delete the discounted item, corresponding reg code redemption should be removed for that particular item
|
|
resp = self.client.post(reverse('shoppingcart.views.remove_item', args=[]),
|
|
{'id': reg_item.id})
|
|
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertEquals(self.cart.orderitem_set.count(), 1)
|
|
info_log.assert_called_with(
|
|
'Registration code "{0}" redemption entry removed for user "{1}" for order item "{2}"'.format(self.reg_code, self.user, reg_item.id))
|
|
|
|
@patch('shoppingcart.views.log.info')
|
|
def test_delete_certificate_item(self, info_log):
|
|
|
|
self.add_course_to_user_cart(self.course_key)
|
|
cert_item = CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor')
|
|
self.assertEquals(self.cart.orderitem_set.count(), 2)
|
|
|
|
# Delete the discounted item, corresponding coupon redemption should be removed for that particular discounted item
|
|
resp = self.client.post(reverse('shoppingcart.views.remove_item', args=[]),
|
|
{'id': cert_item.id})
|
|
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertEquals(self.cart.orderitem_set.count(), 1)
|
|
info_log.assert_called_with(
|
|
'order item {0} removed for user {1}'.format(cert_item.id, self.user))
|
|
|
|
@patch('shoppingcart.views.log.info')
|
|
def test_remove_coupon_redemption_on_clear_cart(self, info_log):
|
|
|
|
reg_item = self.add_course_to_user_cart(self.course_key)
|
|
CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor')
|
|
self.assertEquals(self.cart.orderitem_set.count(), 2)
|
|
|
|
self.add_coupon(self.course_key, True, self.coupon_code)
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code})
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
resp = self.client.post(reverse('shoppingcart.views.clear_cart', args=[]))
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertEquals(self.cart.orderitem_set.count(), 0)
|
|
|
|
info_log.assert_called_with(
|
|
'Coupon redemption entry removed for user {0} for order {1}'.format(self.user, reg_item.id))
|
|
|
|
@patch('shoppingcart.views.log.info')
|
|
def test_remove_registration_code_redemption_on_clear_cart(self, info_log):
|
|
|
|
reg_item = self.add_course_to_user_cart(self.course_key)
|
|
CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor')
|
|
self.assertEquals(self.cart.orderitem_set.count(), 2)
|
|
|
|
self.add_reg_code(self.course_key)
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.reg_code})
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
resp = self.client.post(reverse('shoppingcart.views.clear_cart', args=[]))
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertEquals(self.cart.orderitem_set.count(), 0)
|
|
|
|
info_log.assert_called_with(
|
|
'Registration code redemption entry removed for user {0} for order {1}'.format(self.user, reg_item.id))
|
|
|
|
def test_add_course_to_cart_already_registered(self):
|
|
CourseEnrollment.enroll(self.user, self.course_key)
|
|
self.login_user()
|
|
resp = self.client.post(reverse('shoppingcart.views.add_course_to_cart', args=[self.course_key.to_deprecated_string()]))
|
|
self.assertEqual(resp.status_code, 400)
|
|
self.assertIn('You are already registered in course {0}.'.format(self.course_key.to_deprecated_string()), resp.content)
|
|
|
|
def test_add_nonexistent_course_to_cart(self):
|
|
self.login_user()
|
|
resp = self.client.post(reverse('shoppingcart.views.add_course_to_cart', args=['non/existent/course']))
|
|
self.assertEqual(resp.status_code, 404)
|
|
self.assertIn(_("The course you requested does not exist."), resp.content)
|
|
|
|
def test_add_course_to_cart_success(self):
|
|
self.login_user()
|
|
reverse('shoppingcart.views.add_course_to_cart', args=[self.course_key.to_deprecated_string()])
|
|
resp = self.client.post(reverse('shoppingcart.views.add_course_to_cart', args=[self.course_key.to_deprecated_string()]))
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertTrue(PaidCourseRegistration.contained_in_order(self.cart, self.course_key))
|
|
|
|
|
|
@patch('shoppingcart.views.render_purchase_form_html', form_mock)
|
|
@patch('shoppingcart.views.render_to_response', render_mock)
|
|
def test_show_cart(self):
|
|
self.login_user()
|
|
reg_item = PaidCourseRegistration.add_to_order(self.cart, self.course_key)
|
|
cert_item = CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor')
|
|
resp = self.client.get(reverse('shoppingcart.views.show_cart', args=[]))
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
((purchase_form_arg_cart,), _) = form_mock.call_args # pylint: disable=W0621
|
|
purchase_form_arg_cart_items = purchase_form_arg_cart.orderitem_set.all().select_subclasses()
|
|
self.assertIn(reg_item, purchase_form_arg_cart_items)
|
|
self.assertIn(cert_item, purchase_form_arg_cart_items)
|
|
self.assertEqual(len(purchase_form_arg_cart_items), 2)
|
|
|
|
((template, context), _) = render_mock.call_args
|
|
self.assertEqual(template, 'shoppingcart/shopping_cart.html')
|
|
self.assertEqual(len(context['shoppingcart_items']), 2)
|
|
self.assertEqual(context['amount'], 80)
|
|
self.assertIn("80.00", context['form_html'])
|
|
|
|
def test_clear_cart(self):
|
|
self.login_user()
|
|
PaidCourseRegistration.add_to_order(self.cart, self.course_key)
|
|
CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor')
|
|
self.assertEquals(self.cart.orderitem_set.count(), 2)
|
|
resp = self.client.post(reverse('shoppingcart.views.clear_cart', args=[]))
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertEquals(self.cart.orderitem_set.count(), 0)
|
|
|
|
@patch('shoppingcart.views.log.exception')
|
|
def test_remove_item(self, exception_log):
|
|
self.login_user()
|
|
reg_item = PaidCourseRegistration.add_to_order(self.cart, self.course_key)
|
|
cert_item = CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor')
|
|
self.assertEquals(self.cart.orderitem_set.count(), 2)
|
|
resp = self.client.post(reverse('shoppingcart.views.remove_item', args=[]),
|
|
{'id': reg_item.id})
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertEquals(self.cart.orderitem_set.count(), 1)
|
|
self.assertNotIn(reg_item, self.cart.orderitem_set.all().select_subclasses())
|
|
|
|
self.cart.purchase()
|
|
resp2 = self.client.post(reverse('shoppingcart.views.remove_item', args=[]),
|
|
{'id': cert_item.id})
|
|
self.assertEqual(resp2.status_code, 200)
|
|
exception_log.assert_called_with(
|
|
'Cannot remove cart OrderItem id={0}. DoesNotExist or item is already purchased'.format(cert_item.id))
|
|
|
|
resp3 = self.client.post(reverse('shoppingcart.views.remove_item', args=[]),
|
|
{'id': -1})
|
|
self.assertEqual(resp3.status_code, 200)
|
|
exception_log.assert_called_with(
|
|
'Cannot remove cart OrderItem id={0}. DoesNotExist or item is already purchased'.format(-1))
|
|
|
|
@patch('shoppingcart.views.process_postpay_callback', postpay_mock)
|
|
def test_postpay_callback_success(self):
|
|
postpay_mock.return_value = {'success': True, 'order': self.cart}
|
|
self.login_user()
|
|
resp = self.client.post(reverse('shoppingcart.views.postpay_callback', args=[]))
|
|
self.assertEqual(resp.status_code, 302)
|
|
self.assertEqual(urlparse(resp.__getitem__('location')).path,
|
|
reverse('shoppingcart.views.show_receipt', args=[self.cart.id]))
|
|
|
|
@patch('shoppingcart.views.process_postpay_callback', postpay_mock)
|
|
@patch('shoppingcart.views.render_to_response', render_mock)
|
|
def test_postpay_callback_failure(self):
|
|
postpay_mock.return_value = {'success': False, 'order': self.cart, 'error_html': 'ERROR_TEST!!!'}
|
|
self.login_user()
|
|
resp = self.client.post(reverse('shoppingcart.views.postpay_callback', args=[]))
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertIn('ERROR_TEST!!!', resp.content)
|
|
|
|
((template, context), _) = render_mock.call_args
|
|
self.assertEqual(template, 'shoppingcart/error.html')
|
|
self.assertEqual(context['order'], self.cart)
|
|
self.assertEqual(context['error_html'], 'ERROR_TEST!!!')
|
|
|
|
def test_show_receipt_404s(self):
|
|
PaidCourseRegistration.add_to_order(self.cart, self.course_key)
|
|
CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor')
|
|
self.cart.purchase()
|
|
|
|
user2 = UserFactory.create()
|
|
cart2 = Order.get_cart_for_user(user2)
|
|
PaidCourseRegistration.add_to_order(cart2, self.course_key)
|
|
cart2.purchase()
|
|
|
|
self.login_user()
|
|
resp = self.client.get(reverse('shoppingcart.views.show_receipt', args=[cart2.id]))
|
|
self.assertEqual(resp.status_code, 404)
|
|
|
|
resp2 = self.client.get(reverse('shoppingcart.views.show_receipt', args=[1000]))
|
|
self.assertEqual(resp2.status_code, 404)
|
|
|
|
def test_total_amount_of_purchased_course(self):
|
|
self.add_course_to_user_cart(self.course_key)
|
|
self.assertEquals(self.cart.orderitem_set.count(), 1)
|
|
self.add_coupon(self.course_key, True, self.coupon_code)
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code})
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
self.cart.purchase(first='FirstNameTesting123', street1='StreetTesting123')
|
|
|
|
# Total amount of a particular course that is purchased by different users
|
|
total_amount = PaidCourseRegistration.get_total_amount_of_purchased_item(self.course_key)
|
|
self.assertEqual(total_amount, 36)
|
|
|
|
self.client.login(username=self.instructor.username, password="test")
|
|
cart = Order.get_cart_for_user(self.instructor)
|
|
PaidCourseRegistration.add_to_order(cart, self.course_key)
|
|
cart.purchase(first='FirstNameTesting123', street1='StreetTesting123')
|
|
|
|
total_amount = PaidCourseRegistration.get_total_amount_of_purchased_item(self.course_key)
|
|
self.assertEqual(total_amount, 76)
|
|
|
|
@patch('shoppingcart.views.render_to_response', render_mock)
|
|
def test_show_receipt_success_with_valid_coupon_code(self):
|
|
self.add_course_to_user_cart(self.course_key)
|
|
self.add_coupon(self.course_key, True, self.coupon_code)
|
|
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code})
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.cart.purchase(first='FirstNameTesting123', street1='StreetTesting123')
|
|
|
|
resp = self.client.get(reverse('shoppingcart.views.show_receipt', args=[self.cart.id]))
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertIn('FirstNameTesting123', resp.content)
|
|
self.assertIn(str(self.get_discount(self.cost)), resp.content)
|
|
|
|
@patch('shoppingcart.views.render_to_response', render_mock)
|
|
def test_reg_code_and_course_registration_scenario(self):
|
|
self.add_reg_code(self.course_key)
|
|
|
|
# One courses in user shopping cart
|
|
self.add_course_to_user_cart(self.course_key)
|
|
self.assertEquals(self.cart.orderitem_set.count(), 1)
|
|
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.reg_code})
|
|
self.assertEqual(resp.status_code, 200)
|
|
resp = self.client.get(reverse('shoppingcart.views.show_cart', args=[]))
|
|
self.assertIn('Register', resp.content)
|
|
|
|
# freely enroll the user into course
|
|
resp = self.client.get(reverse('shoppingcart.views.register_courses'))
|
|
self.assertIn('success', resp.content)
|
|
|
|
@patch('shoppingcart.views.render_to_response', render_mock)
|
|
def test_reg_code_with_multiple_courses_and_checkout_scenario(self):
|
|
self.add_reg_code(self.course_key)
|
|
|
|
# Two courses in user shopping cart
|
|
self.login_user()
|
|
PaidCourseRegistration.add_to_order(self.cart, self.course_key)
|
|
PaidCourseRegistration.add_to_order(self.cart, self.testing_course.id)
|
|
self.assertEquals(self.cart.orderitem_set.count(), 2)
|
|
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.reg_code})
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
resp = self.client.get(reverse('shoppingcart.views.show_cart', args=[]))
|
|
self.assertIn('Payment', resp.content)
|
|
self.cart.purchase(first='FirstNameTesting123', street1='StreetTesting123')
|
|
|
|
resp = self.client.get(reverse('shoppingcart.views.show_receipt', args=[self.cart.id]))
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
((template, context), _) = render_mock.call_args # pylint: disable=W0621
|
|
self.assertEqual(template, 'shoppingcart/receipt.html')
|
|
self.assertEqual(context['order'], self.cart)
|
|
self.assertEqual(context['order'].total_cost, self.testing_cost)
|
|
|
|
course_enrollment = CourseEnrollment.objects.filter(user=self.user)
|
|
self.assertEqual(course_enrollment.count(), 2)
|
|
|
|
@patch('shoppingcart.views.render_to_response', render_mock)
|
|
def test_show_receipt_success_with_valid_reg_code(self):
|
|
self.add_course_to_user_cart(self.course_key)
|
|
self.add_reg_code(self.course_key)
|
|
|
|
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.reg_code})
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.cart.purchase(first='FirstNameTesting123', street1='StreetTesting123')
|
|
|
|
resp = self.client.get(reverse('shoppingcart.views.show_receipt', args=[self.cart.id]))
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertIn('0.00', resp.content)
|
|
|
|
@patch('shoppingcart.views.render_to_response', render_mock)
|
|
def test_show_receipt_success(self):
|
|
reg_item = PaidCourseRegistration.add_to_order(self.cart, self.course_key)
|
|
cert_item = CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor')
|
|
self.cart.purchase(first='FirstNameTesting123', street1='StreetTesting123')
|
|
|
|
self.login_user()
|
|
resp = self.client.get(reverse('shoppingcart.views.show_receipt', args=[self.cart.id]))
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertIn('FirstNameTesting123', resp.content)
|
|
self.assertIn('80.00', resp.content)
|
|
|
|
((template, context), _) = render_mock.call_args # pylint: disable=W0621
|
|
self.assertEqual(template, 'shoppingcart/receipt.html')
|
|
self.assertEqual(context['order'], self.cart)
|
|
self.assertIn(reg_item, context['shoppingcart_items'][0])
|
|
self.assertIn(cert_item, context['shoppingcart_items'][1])
|
|
self.assertFalse(context['any_refunds'])
|
|
|
|
@patch('shoppingcart.views.render_to_response', render_mock)
|
|
def test_courseregcode_item_total_price(self):
|
|
self.cart.order_type = 'business'
|
|
self.cart.save()
|
|
CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2)
|
|
self.cart.purchase(first='FirstNameTesting123', street1='StreetTesting123')
|
|
self.assertEquals(CourseRegCodeItem.get_total_amount_of_purchased_item(self.course_key), 80)
|
|
|
|
@patch('shoppingcart.views.render_to_response', render_mock)
|
|
def test_show_receipt_success_with_order_type_business(self):
|
|
self.cart.order_type = 'business'
|
|
self.cart.save()
|
|
reg_item = CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2)
|
|
self.cart.add_billing_details(company_name='T1Omega', company_contact_name='C1',
|
|
company_contact_email='test@t1.com', recipient_email='test@t2.com')
|
|
self.cart.purchase(first='FirstNameTesting123', street1='StreetTesting123')
|
|
|
|
# mail is sent to these emails recipient_email, company_contact_email, order.user.email
|
|
self.assertEquals(len(mail.outbox), 3)
|
|
|
|
self.login_user()
|
|
resp = self.client.get(reverse('shoppingcart.views.show_receipt', args=[self.cart.id]))
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
# when order_type = 'business' the user is not enrolled in the
|
|
# course but presented with the enrollment links
|
|
self.assertFalse(CourseEnrollment.is_enrolled(self.cart.user, self.course_key))
|
|
self.assertIn('FirstNameTesting123', resp.content)
|
|
self.assertIn('80.00', resp.content)
|
|
# check for the enrollment codes content
|
|
self.assertIn('Please send each professional one of these unique registration codes to enroll into the course.', resp.content)
|
|
|
|
((template, context), _) = render_mock.call_args # pylint: disable=W0621
|
|
self.assertEqual(template, 'shoppingcart/receipt.html')
|
|
self.assertEqual(context['order'], self.cart)
|
|
self.assertIn(reg_item, context['shoppingcart_items'][0])
|
|
self.assertIn(self.cart.purchase_time.strftime("%B %d, %Y"), resp.content)
|
|
self.assertIn(self.cart.company_name, resp.content)
|
|
self.assertIn(self.cart.company_contact_name, resp.content)
|
|
self.assertIn(self.cart.company_contact_email, resp.content)
|
|
self.assertIn(self.cart.recipient_email, resp.content)
|
|
self.assertIn("Invoice #{order_id}".format(order_id=self.cart.id), resp.content)
|
|
self.assertIn('You have successfully purchased <b>{total_registration_codes} course registration codes'
|
|
.format(total_registration_codes=context['total_registration_codes']), resp.content)
|
|
|
|
@patch('shoppingcart.views.render_to_response', render_mock)
|
|
def test_show_receipt_success_with_upgrade(self):
|
|
|
|
reg_item = PaidCourseRegistration.add_to_order(self.cart, self.course_key)
|
|
cert_item = CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor')
|
|
self.cart.purchase(first='FirstNameTesting123', street1='StreetTesting123')
|
|
|
|
self.login_user()
|
|
|
|
# When we come from the upgrade flow, we'll have a session variable showing that
|
|
s = self.client.session
|
|
s['attempting_upgrade'] = True
|
|
s.save()
|
|
|
|
self.mock_tracker.emit.reset_mock() # pylint: disable=maybe-no-member
|
|
resp = self.client.get(reverse('shoppingcart.views.show_receipt', args=[self.cart.id]))
|
|
|
|
# Once they've upgraded, they're no longer *attempting* to upgrade
|
|
attempting_upgrade = self.client.session.get('attempting_upgrade', False)
|
|
self.assertFalse(attempting_upgrade)
|
|
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertIn('FirstNameTesting123', resp.content)
|
|
self.assertIn('80.00', resp.content)
|
|
|
|
|
|
((template, context), _) = render_mock.call_args
|
|
|
|
# When we come from the upgrade flow, we get these context variables
|
|
|
|
|
|
self.assertEqual(template, 'shoppingcart/receipt.html')
|
|
self.assertEqual(context['order'], self.cart)
|
|
self.assertIn(reg_item, context['shoppingcart_items'][0])
|
|
self.assertIn(cert_item, context['shoppingcart_items'][1])
|
|
self.assertFalse(context['any_refunds'])
|
|
|
|
course_enrollment = CourseEnrollment.get_or_create_enrollment(self.user, self.course_key)
|
|
course_enrollment.emit_event('edx.course.enrollment.upgrade.succeeded')
|
|
self.mock_tracker.emit.assert_any_call( # pylint: disable=maybe-no-member
|
|
'edx.course.enrollment.upgrade.succeeded',
|
|
{
|
|
'user_id': course_enrollment.user.id,
|
|
'course_id': course_enrollment.course_id.to_deprecated_string(),
|
|
'mode': course_enrollment.mode
|
|
}
|
|
)
|
|
|
|
@patch('shoppingcart.views.render_to_response', render_mock)
|
|
def test_show_receipt_success_refund(self):
|
|
reg_item = PaidCourseRegistration.add_to_order(self.cart, self.course_key)
|
|
cert_item = CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor')
|
|
self.cart.purchase(first='FirstNameTesting123', street1='StreetTesting123')
|
|
cert_item.status = "refunded"
|
|
cert_item.save()
|
|
self.assertEqual(self.cart.total_cost, 40)
|
|
self.login_user()
|
|
resp = self.client.get(reverse('shoppingcart.views.show_receipt', args=[self.cart.id]))
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertIn('40.00', resp.content)
|
|
|
|
((template, context), _tmp) = render_mock.call_args
|
|
self.assertEqual(template, 'shoppingcart/receipt.html')
|
|
self.assertEqual(context['order'], self.cart)
|
|
self.assertIn(reg_item, context['shoppingcart_items'][0])
|
|
self.assertIn(cert_item, context['shoppingcart_items'][1])
|
|
self.assertTrue(context['any_refunds'])
|
|
|
|
@patch('shoppingcart.views.render_to_response', render_mock)
|
|
def test_show_receipt_success_custom_receipt_page(self):
|
|
cert_item = CertificateItem.add_to_order(self.cart, self.course_key, self.cost, 'honor')
|
|
self.cart.purchase()
|
|
self.login_user()
|
|
receipt_url = reverse('shoppingcart.views.show_receipt', args=[self.cart.id])
|
|
resp = self.client.get(receipt_url)
|
|
self.assertEqual(resp.status_code, 200)
|
|
((template, _context), _tmp) = render_mock.call_args
|
|
self.assertEqual(template, cert_item.single_item_receipt_template)
|
|
|
|
|
|
@override_settings(MODULESTORE=MODULESTORE_CONFIG)
|
|
class RegistrationCodeRedemptionCourseEnrollment(ModuleStoreTestCase):
|
|
"""
|
|
Test suite for RegistrationCodeRedemption Course Enrollments
|
|
"""
|
|
def setUp(self, **kwargs):
|
|
self.user = UserFactory.create()
|
|
self.user.set_password('password')
|
|
self.user.save()
|
|
self.cost = 40
|
|
self.course = CourseFactory.create(org='MITx', number='999', display_name='Robot Super Course')
|
|
self.course_key = self.course.id
|
|
self.course_mode = CourseMode(course_id=self.course_key,
|
|
mode_slug="honor",
|
|
mode_display_name="honor cert",
|
|
min_price=self.cost)
|
|
self.course_mode.save()
|
|
|
|
def login_user(self):
|
|
"""
|
|
Helper fn to login self.user
|
|
"""
|
|
self.client.login(username=self.user.username, password="password")
|
|
|
|
def test_registration_redemption_post_request_ratelimited(self):
|
|
"""
|
|
Try (and fail) registration code redemption 30 times
|
|
in a row on an non-existing registration code post request
|
|
"""
|
|
cache.clear()
|
|
url = reverse('register_code_redemption', args=['asdasd'])
|
|
self.login_user()
|
|
for i in xrange(30): # pylint: disable=W0612
|
|
response = self.client.post(url, **{'HTTP_HOST': 'localhost'})
|
|
self.assertEquals(response.status_code, 404)
|
|
|
|
# then the rate limiter should kick in and give a HttpForbidden response
|
|
response = self.client.post(url)
|
|
self.assertEquals(response.status_code, 403)
|
|
|
|
# now reset the time to 5 mins from now in future in order to unblock
|
|
reset_time = datetime.now(UTC) + timedelta(seconds=300)
|
|
with freeze_time(reset_time):
|
|
response = self.client.post(url, **{'HTTP_HOST': 'localhost'})
|
|
self.assertEquals(response.status_code, 404)
|
|
|
|
cache.clear()
|
|
|
|
def test_registration_redemption_get_request_ratelimited(self):
|
|
"""
|
|
Try (and fail) registration code redemption 30 times
|
|
in a row on an non-existing registration code get request
|
|
"""
|
|
cache.clear()
|
|
url = reverse('register_code_redemption', args=['asdasd'])
|
|
self.login_user()
|
|
for i in xrange(30): # pylint: disable=W0612
|
|
response = self.client.get(url, **{'HTTP_HOST': 'localhost'})
|
|
self.assertEquals(response.status_code, 404)
|
|
|
|
# then the rate limiter should kick in and give a HttpForbidden response
|
|
response = self.client.get(url)
|
|
self.assertEquals(response.status_code, 403)
|
|
|
|
# now reset the time to 5 mins from now in future in order to unblock
|
|
reset_time = datetime.now(UTC) + timedelta(seconds=300)
|
|
with freeze_time(reset_time):
|
|
response = self.client.get(url, **{'HTTP_HOST': 'localhost'})
|
|
self.assertEquals(response.status_code, 404)
|
|
|
|
cache.clear()
|
|
|
|
def test_course_enrollment_active_registration_code_redemption(self):
|
|
"""
|
|
Test for active registration code course enrollment
|
|
"""
|
|
cache.clear()
|
|
instructor = InstructorFactory(course_key=self.course_key)
|
|
self.client.login(username=instructor.username, password='test')
|
|
url = reverse('generate_registration_codes',
|
|
kwargs={'course_id': self.course.id.to_deprecated_string()})
|
|
|
|
data = {
|
|
'total_registration_codes': 12, 'company_name': 'Test Group', 'company_contact_name': 'Test@company.com',
|
|
'company_contact_email': 'Test@company.com', 'sale_price': 122.45, 'recipient_name': 'Test123',
|
|
'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street',
|
|
'address_line_2': '', 'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '',
|
|
'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': ''
|
|
}
|
|
|
|
response = self.client.post(url, data, **{'HTTP_HOST': 'localhost'})
|
|
self.assertEquals(response.status_code, 200)
|
|
# get the first registration from the newly created registration codes
|
|
registration_code = CourseRegistrationCode.objects.all()[0].code
|
|
redeem_url = reverse('register_code_redemption', args=[registration_code])
|
|
self.login_user()
|
|
|
|
response = self.client.get(redeem_url, **{'HTTP_HOST': 'localhost'})
|
|
self.assertEquals(response.status_code, 200)
|
|
# check button text
|
|
self.assertTrue('Activate Course Enrollment' in response.content)
|
|
|
|
#now activate the user by enrolling him/her to the course
|
|
response = self.client.post(redeem_url, **{'HTTP_HOST': 'localhost'})
|
|
self.assertEquals(response.status_code, 200)
|
|
self.assertTrue('View Course' in response.content)
|
|
|
|
#now check that the registration code has already been redeemed and user is already registered in the course
|
|
RegistrationCodeRedemption.objects.filter(registration_code__code=registration_code)
|
|
response = self.client.get(redeem_url, **{'HTTP_HOST': 'localhost'})
|
|
self.assertEquals(len(RegistrationCodeRedemption.objects.filter(registration_code__code=registration_code)), 1)
|
|
self.assertTrue("You've clicked a link for an enrollment code that has already been used." in response.content)
|
|
|
|
#now check that the registration code has already been redeemed
|
|
response = self.client.post(redeem_url, **{'HTTP_HOST': 'localhost'})
|
|
self.assertTrue("You've clicked a link for an enrollment code that has already been used." in response.content)
|
|
|
|
#now check the response of the dashboard page
|
|
dashboard_url = reverse('dashboard')
|
|
response = self.client.get(dashboard_url)
|
|
self.assertEquals(response.status_code, 200)
|
|
self.assertTrue(self.course.display_name, response.content)
|
|
|
|
|
|
@override_settings(MODULESTORE=MODULESTORE_CONFIG)
|
|
@ddt.ddt
|
|
class DonationViewTest(ModuleStoreTestCase):
|
|
"""Tests for making a donation.
|
|
|
|
These tests cover both the single-item purchase flow,
|
|
as well as the receipt page for donation items.
|
|
"""
|
|
|
|
DONATION_AMOUNT = "23.45"
|
|
PASSWORD = "password"
|
|
|
|
def setUp(self):
|
|
"""Create a test user and order. """
|
|
super(DonationViewTest, self).setUp()
|
|
|
|
# Create and login a user
|
|
self.user = UserFactory.create()
|
|
self.user.set_password(self.PASSWORD)
|
|
self.user.save()
|
|
result = self.client.login(username=self.user.username, password=self.PASSWORD)
|
|
self.assertTrue(result)
|
|
|
|
# Enable donations
|
|
config = DonationConfiguration.current()
|
|
config.enabled = True
|
|
config.save()
|
|
|
|
def test_donation_for_org(self):
|
|
self._donate(self.DONATION_AMOUNT)
|
|
self._assert_receipt_contains("tax purposes")
|
|
|
|
def test_donation_for_course_receipt(self):
|
|
# Create a test course and donate to it
|
|
self.course = CourseFactory.create(display_name="Test Course")
|
|
self._donate(self.DONATION_AMOUNT, course_id=self.course.id)
|
|
|
|
# Verify the receipt page
|
|
self._assert_receipt_contains("tax purposes")
|
|
self._assert_receipt_contains(self.course.display_name)
|
|
|
|
def test_smallest_possible_donation(self):
|
|
self._donate("0.01")
|
|
self._assert_receipt_contains("0.01")
|
|
|
|
@ddt.data(
|
|
{},
|
|
{"amount": "abcd"},
|
|
{"amount": "-1.00"},
|
|
{"amount": "0.00"},
|
|
{"amount": "0.001"},
|
|
{"amount": "0"},
|
|
{"amount": "23.45", "course_id": "invalid"}
|
|
)
|
|
def test_donation_bad_request(self, bad_params):
|
|
response = self.client.post(reverse('donation'), bad_params)
|
|
self.assertEqual(response.status_code, 400)
|
|
|
|
def test_donation_requires_login(self):
|
|
self.client.logout()
|
|
response = self.client.post(reverse('donation'), {'amount': self.DONATION_AMOUNT})
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
def test_no_such_course(self):
|
|
response = self.client.post(
|
|
reverse("donation"),
|
|
{"amount": self.DONATION_AMOUNT, "course_id": "edx/DemoX/Demo"}
|
|
)
|
|
self.assertEqual(response.status_code, 400)
|
|
|
|
@ddt.data("get", "put", "head", "options", "delete")
|
|
def test_donation_requires_post(self, invalid_method):
|
|
response = getattr(self.client, invalid_method)(
|
|
reverse("donation"), {"amount": self.DONATION_AMOUNT}
|
|
)
|
|
self.assertEqual(response.status_code, 405)
|
|
|
|
|
|
def test_donations_disabled(self):
|
|
config = DonationConfiguration.current()
|
|
config.enabled = False
|
|
config.save()
|
|
|
|
# Logged in -- should be a 404
|
|
response = self.client.post(reverse('donation'))
|
|
self.assertEqual(response.status_code, 404)
|
|
|
|
# Logged out -- should still be a 404
|
|
self.client.logout()
|
|
response = self.client.post(reverse('donation'))
|
|
self.assertEqual(response.status_code, 404)
|
|
|
|
def _donate(self, donation_amount, course_id=None):
|
|
"""Simulate a donation to a course.
|
|
|
|
This covers the entire payment flow, except for the external
|
|
payment processor, which is simulated.
|
|
|
|
Arguments:
|
|
donation_amount (unicode): The amount the user is donating.
|
|
|
|
Keyword Arguments:
|
|
course_id (CourseKey): If provided, make a donation to the specific course.
|
|
|
|
Raises:
|
|
AssertionError
|
|
|
|
"""
|
|
# Purchase a single donation item
|
|
# Optionally specify a particular course for the donation
|
|
params = {'amount': donation_amount}
|
|
if course_id is not None:
|
|
params['course_id'] = course_id
|
|
|
|
url = reverse('donation')
|
|
response = self.client.post(url, params)
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
# Use the fake payment implementation to simulate the parameters
|
|
# we would receive from the payment processor.
|
|
payment_info = json.loads(response.content)
|
|
self.assertEqual(payment_info["payment_url"], "/shoppingcart/payment_fake")
|
|
|
|
# If this is a per-course donation, verify that we're sending
|
|
# the course ID to the payment processor.
|
|
if course_id is not None:
|
|
self.assertEqual(
|
|
payment_info["payment_params"]["merchant_defined_data1"],
|
|
unicode(course_id)
|
|
)
|
|
|
|
processor_response_params = PaymentFakeView.response_post_params(payment_info["payment_params"])
|
|
|
|
# Use the response parameters to simulate a successful payment
|
|
url = reverse('shoppingcart.views.postpay_callback')
|
|
response = self.client.post(url, processor_response_params)
|
|
self.assertRedirects(response, self._receipt_url)
|
|
|
|
def _assert_receipt_contains(self, expected_text):
|
|
"""Load the receipt page and verify that it contains the expected text."""
|
|
resp = self.client.get(self._receipt_url)
|
|
self.assertContains(resp, expected_text)
|
|
|
|
@property
|
|
def _receipt_url(self):
|
|
order_id = Order.objects.get(user=self.user, status="purchased").id
|
|
return reverse("shoppingcart.views.show_receipt", kwargs={"ordernum": order_id})
|
|
|
|
|
|
@override_settings(MODULESTORE=MODULESTORE_CONFIG)
|
|
class CSVReportViewsTest(ModuleStoreTestCase):
|
|
"""
|
|
Test suite for CSV Purchase Reporting
|
|
"""
|
|
def setUp(self):
|
|
self.user = UserFactory.create()
|
|
self.user.set_password('password')
|
|
self.user.save()
|
|
self.cost = 40
|
|
self.course = CourseFactory.create(org='MITx', number='999', display_name='Robot Super Course')
|
|
self.course_key = self.course.id
|
|
self.course_mode = CourseMode(course_id=self.course_key,
|
|
mode_slug="honor",
|
|
mode_display_name="honor cert",
|
|
min_price=self.cost)
|
|
self.course_mode.save()
|
|
self.course_mode2 = CourseMode(course_id=self.course_key,
|
|
mode_slug="verified",
|
|
mode_display_name="verified cert",
|
|
min_price=self.cost)
|
|
self.course_mode2.save()
|
|
verified_course = CourseFactory.create(org='org', number='test', display_name='Test Course')
|
|
|
|
self.verified_course_key = verified_course.id
|
|
self.cart = Order.get_cart_for_user(self.user)
|
|
self.dl_grp = Group(name=settings.PAYMENT_REPORT_GENERATOR_GROUP)
|
|
self.dl_grp.save()
|
|
|
|
def login_user(self):
|
|
"""
|
|
Helper fn to login self.user
|
|
"""
|
|
self.client.login(username=self.user.username, password="password")
|
|
|
|
def add_to_download_group(self, user):
|
|
"""
|
|
Helper fn to add self.user to group that's allowed to download report CSV
|
|
"""
|
|
user.groups.add(self.dl_grp)
|
|
|
|
def test_report_csv_no_access(self):
|
|
self.login_user()
|
|
response = self.client.get(reverse('payment_csv_report'))
|
|
self.assertEqual(response.status_code, 403)
|
|
|
|
def test_report_csv_bad_method(self):
|
|
self.login_user()
|
|
self.add_to_download_group(self.user)
|
|
response = self.client.put(reverse('payment_csv_report'))
|
|
self.assertEqual(response.status_code, 400)
|
|
|
|
@patch('shoppingcart.views.render_to_response', render_mock)
|
|
def test_report_csv_get(self):
|
|
self.login_user()
|
|
self.add_to_download_group(self.user)
|
|
response = self.client.get(reverse('payment_csv_report'))
|
|
|
|
((template, context), unused_kwargs) = render_mock.call_args
|
|
self.assertEqual(template, 'shoppingcart/download_report.html')
|
|
self.assertFalse(context['total_count_error'])
|
|
self.assertFalse(context['date_fmt_error'])
|
|
self.assertIn(_("Download CSV Reports"), response.content.decode('UTF-8'))
|
|
|
|
@patch('shoppingcart.views.render_to_response', render_mock)
|
|
def test_report_csv_bad_date(self):
|
|
self.login_user()
|
|
self.add_to_download_group(self.user)
|
|
response = self.client.post(reverse('payment_csv_report'), {'start_date': 'BAD', 'end_date': 'BAD', 'requested_report': 'itemized_purchase_report'})
|
|
|
|
((template, context), unused_kwargs) = render_mock.call_args
|
|
self.assertEqual(template, 'shoppingcart/download_report.html')
|
|
self.assertFalse(context['total_count_error'])
|
|
self.assertTrue(context['date_fmt_error'])
|
|
self.assertIn(_("There was an error in your date input. It should be formatted as YYYY-MM-DD"),
|
|
response.content.decode('UTF-8'))
|
|
|
|
CORRECT_CSV_NO_DATE_ITEMIZED_PURCHASE = ",1,purchased,1,40,40,usd,Registration for Course: Robot Super Course,"
|
|
|
|
def test_report_csv_itemized(self):
|
|
report_type = 'itemized_purchase_report'
|
|
start_date = '1970-01-01'
|
|
end_date = '2100-01-01'
|
|
PaidCourseRegistration.add_to_order(self.cart, self.course_key)
|
|
self.cart.purchase()
|
|
self.login_user()
|
|
self.add_to_download_group(self.user)
|
|
response = self.client.post(reverse('payment_csv_report'), {'start_date': start_date,
|
|
'end_date': end_date,
|
|
'requested_report': report_type})
|
|
self.assertEqual(response['Content-Type'], 'text/csv')
|
|
report = initialize_report(report_type, start_date, end_date)
|
|
self.assertIn(",".join(report.header()), response.content)
|
|
self.assertIn(self.CORRECT_CSV_NO_DATE_ITEMIZED_PURCHASE, response.content)
|
|
|
|
def test_report_csv_university_revenue_share(self):
|
|
report_type = 'university_revenue_share'
|
|
start_date = '1970-01-01'
|
|
end_date = '2100-01-01'
|
|
start_letter = 'A'
|
|
end_letter = 'Z'
|
|
self.login_user()
|
|
self.add_to_download_group(self.user)
|
|
response = self.client.post(reverse('payment_csv_report'), {'start_date': start_date,
|
|
'end_date': end_date,
|
|
'start_letter': start_letter,
|
|
'end_letter': end_letter,
|
|
'requested_report': report_type})
|
|
self.assertEqual(response['Content-Type'], 'text/csv')
|
|
report = initialize_report(report_type, start_date, end_date, start_letter, end_letter)
|
|
self.assertIn(",".join(report.header()), response.content)
|
|
|
|
|
|
class UtilFnsTest(TestCase):
|
|
"""
|
|
Tests for utility functions in views.py
|
|
"""
|
|
def setUp(self):
|
|
self.user = UserFactory.create()
|
|
|
|
def test_can_download_report_no_group(self):
|
|
"""
|
|
Group controlling perms is not present
|
|
"""
|
|
self.assertFalse(_can_download_report(self.user))
|
|
|
|
def test_can_download_report_not_member(self):
|
|
"""
|
|
User is not part of group controlling perms
|
|
"""
|
|
Group(name=settings.PAYMENT_REPORT_GENERATOR_GROUP).save()
|
|
self.assertFalse(_can_download_report(self.user))
|
|
|
|
def test_can_download_report(self):
|
|
"""
|
|
User is part of group controlling perms
|
|
"""
|
|
grp = Group(name=settings.PAYMENT_REPORT_GENERATOR_GROUP)
|
|
grp.save()
|
|
self.user.groups.add(grp)
|
|
self.assertTrue(_can_download_report(self.user))
|
|
|
|
def test_get_date_from_str(self):
|
|
test_str = "2013-10-01"
|
|
date = _get_date_from_str(test_str)
|
|
self.assertEqual(2013, date.year)
|
|
self.assertEqual(10, date.month)
|
|
self.assertEqual(1, date.day)
|