Files
edx-platform/lms/djangoapps/shoppingcart/tests/test_views.py
Adeel Khan 298d3554fe Fix templates for XSS code injection via translations
This would patch all templates (django/mako)
for a possible XSS code injection via
translation files by html escaping them..

LEARNER-4632
2019-06-25 14:22:42 +05:00

2206 lines
97 KiB
Python

"""
Tests for Shopping Cart views
"""
from __future__ import absolute_import
import json
from collections import OrderedDict
from datetime import datetime, timedelta
from decimal import Decimal
import ddt
import pytz
import six
from django.conf import settings
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 django.http import HttpRequest
from django.test import TestCase
from django.test.utils import override_settings
from django.urls import reverse
from freezegun import freeze_time
from mock import Mock, patch
from pytz import UTC
from six import text_type
from six.moves import range
from six.moves.urllib.parse import urlparse # pylint: disable=import-error
from common.test.utils import XssTestMixin
from course_modes.models import CourseMode
from course_modes.tests.factories import CourseModeFactory
from courseware.tests.factories import InstructorFactory
from edxmako.shortcuts import render_to_response
from openedx.core.djangoapps.embargo.test_utils import restrict_course
from shoppingcart.admin import SoftDeleteCouponAdmin
from shoppingcart.models import (
CertificateItem,
Coupon,
CouponRedemption,
CourseRegCodeItem,
CourseRegistrationCode,
DonationConfiguration,
Order,
PaidCourseRegistration,
RegistrationCodeRedemption
)
from shoppingcart.processors import render_purchase_form_html
from shoppingcart.processors.CyberSource2 import sign
from shoppingcart.tests.payment_fake import PaymentFakeView
from shoppingcart.views import _can_download_report, _get_date_from_str, initialize_report
from student.models import CourseEnrollment
from student.roles import CourseSalesAdminRole
from student.tests.factories import AdminFactory, UserFactory
from util.date_utils import get_default_time_display
from util.testing import UrlResetMixin
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
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()
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_PAID_COURSE_REGISTRATION': True})
@ddt.ddt
class ShoppingCartViewsTests(SharedModuleStoreTestCase, XssTestMixin):
"""
Test shopping cart view under various states
"""
@classmethod
def setUpClass(cls):
super(ShoppingCartViewsTests, cls).setUpClass()
cls.course = CourseFactory.create(org='MITx', number='999', display_name='Robot Super Course')
cls.course_key = cls.course.id
verified_course = CourseFactory.create(org='org', number='test', display_name='Test Course')
cls.verified_course_key = verified_course.id
xss_course = CourseFactory.create(org='xssorg', number='test', display_name='<script>alert("XSS")</script>')
cls.xss_course_key = xss_course.id
cls.testing_course = CourseFactory.create(org='edX', number='888', display_name='Testing Super Course')
def setUp(self):
super(ShoppingCartViewsTests, self).setUp()
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_mode = CourseMode(
course_id=self.course_key,
mode_slug=CourseMode.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_mode = CourseMode(
course_id=self.testing_course.id,
mode_slug=CourseMode.HONOR,
mode_display_name="testing honor cert",
min_price=self.testing_cost
)
self.testing_course_mode.save()
# And for the XSS course
CourseMode(
course_id=self.xss_course_key,
mode_slug=CourseMode.HONOR,
mode_display_name="honor cert",
min_price=self.cost
).save()
# And the verified course
self.verified_course_mode = CourseMode(
course_id=self.verified_course_key,
mode_slug=CourseMode.HONOR,
mode_display_name="honor cert",
min_price=self.cost
)
self.verified_course_mode.save()
self.cart = Order.get_cart_for_user(self.user)
self.addCleanup(patcher.stop)
self.now = datetime.now(pytz.UTC)
self.yesterday = self.now - timedelta(days=1)
self.tomorrow = self.now + timedelta(days=1)
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, mode_slug=None, is_valid=True):
"""
add dummy registration code into models
"""
if mode_slug is None:
mode_slug = self.course_mode.mode_slug
course_reg_code = CourseRegistrationCode(
code=self.reg_code, course_id=course_key,
created_by=self.user, mode_slug=mode_slug,
is_valid=is_valid
)
course_reg_code.save()
def _add_course_mode(self, min_price=50, mode_slug='honor', expiration_date=None):
"""
Adds a course mode to the test course.
"""
return CourseModeFactory(
course_id=self.course.id,
min_price=min_price,
mode_slug=mode_slug,
expiration_date=expiration_date,
)
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, mode_slug=self.course_mode.mode_slug)
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('add_course_to_cart', args=[text_type(self.course_key)]))
self.assertEqual(resp.status_code, 403)
@patch('shoppingcart.views.render_to_response', render_mock)
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)
((template, context), _) = render_mock.call_args # pylint: disable=unpacking-non-sequence
self.assertEqual(template, 'shoppingcart/billing_details.html')
# check for the default currency in the context
self.assertEqual(context['currency'], 'usd')
self.assertEqual(context['currency_symbol'], '$')
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)
@patch('shoppingcart.views.render_to_response', render_mock)
@override_settings(PAID_COURSE_REGISTRATION_CURRENCY=['PKR', 'Rs'])
def test_billing_details_with_override_currency_settings(self):
billing_url = reverse('billing_details')
self.login_user()
#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)
((template, context), __) = render_mock.call_args # pylint: disable=unpacking-non-sequence
self.assertEqual(template, 'shoppingcart/billing_details.html')
# check for the override currency settings in the context
self.assertEqual(context['currency'], 'PKR')
self.assertEqual(context['currency_symbol'], 'Rs')
def test_same_coupon_code_applied_on_multiple_items_in_the_cart(self):
"""
test to check that that the same coupon code applied on multiple
items in the cart.
"""
self.login_user()
# add first course to user cart
resp = self.client.post(
reverse('add_course_to_cart', args=[text_type(self.course_key)])
)
self.assertEqual(resp.status_code, 200)
# add and apply the coupon code to course in the cart
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)
# now add the same coupon code to the second course(testing_course)
self.add_coupon(self.testing_course.id, True, self.coupon_code)
#now add the second course to cart, the coupon code should be
# applied when adding the second course to the cart
resp = self.client.post(
reverse('add_course_to_cart', args=[text_type(self.testing_course.id)])
)
self.assertEqual(resp.status_code, 200)
#now check the user cart and see that the discount has been applied on both the courses
resp = self.client.get(reverse('shoppingcart.views.show_cart', args=[]))
self.assertEqual(resp.status_code, 200)
#first course price is 40$ and the second course price is 20$
# after 10% discount on both the courses the total price will be 18+36 = 54
self.assertIn('54.00', resp.content)
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('add_course_to_cart', args=[text_type(self.course_key)]))
self.assertEqual(resp.status_code, 400)
self.assertIn(u'The course {0} is already in your cart.'.format(text_type(self.course_key)), 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(u"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(u"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(u"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 = text_type(self.course_key) + '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(u"Discount does not exist against code '{0}'.".format(self.coupon_code), resp.content)
def test_inactive_registration_code_returns_error(self):
"""
test to redeem inactive registration code and
it returns an error.
"""
course_key = text_type(self.course_key)
self.add_reg_code(course_key, is_valid=False)
self.add_course_to_user_cart(self.course_key)
# now apply the inactive registration code
# it will raise an exception
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.reg_code})
self.assertEqual(resp.status_code, 400)
self.assertIn(
u"This enrollment code ({enrollment_code}) is no longer valid.".format(
enrollment_code=self.reg_code), resp.content)
def test_course_does_not_exist_in_cart_against_valid_reg_code(self):
course_key = text_type(self.course_key) + '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(u"Code '{0}' is not valid for any course in the shopping cart.".format(self.reg_code),
resp.content)
def test_cart_item_qty_greater_than_1_against_valid_reg_code(self):
course_key = text_type(self.course_key)
self.add_reg_code(course_key)
item = self.add_course_to_user_cart(self.course_key)
resp = self.client.post(reverse('shoppingcart.views.update_user_cart'), {'ItemId': item.id, 'qty': 4})
self.assertEqual(resp.status_code, 200)
# now update the cart item quantity and then apply the registration code
# it will raise an exception
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.reg_code})
self.assertEqual(resp.status_code, 404)
self.assertIn("Cart item quantity should not be greater than 1 when applying activation code", resp.content)
@ddt.data(True, False)
def test_reg_code_uses_associated_mode(self, expired_mode):
"""Tests the use of reg codes on verified courses, expired or active. """
course_key = text_type(self.course_key)
expiration_date = self.yesterday if expired_mode else self.tomorrow
self._add_course_mode(mode_slug='verified', expiration_date=expiration_date)
self.add_reg_code(course_key, mode_slug='verified')
self.add_course_to_user_cart(self.course_key)
resp = self.client.post(reverse('register_code_redemption', args=[self.reg_code]), HTTP_HOST='localhost')
self.assertEqual(resp.status_code, 200)
self.assertIn(self.course.display_name.encode('utf-8'), resp.content)
@ddt.data(True, False)
def test_reg_code_uses_unknown_mode(self, expired_mode):
"""Tests the use of reg codes on verified courses, expired or active. """
course_key = text_type(self.course_key)
expiration_date = self.yesterday if expired_mode else self.tomorrow
self._add_course_mode(mode_slug='verified', expiration_date=expiration_date)
self.add_reg_code(course_key, mode_slug='bananas')
self.add_course_to_user_cart(self.course_key)
resp = self.client.post(reverse('register_code_redemption', args=[self.reg_code]), HTTP_HOST='localhost')
self.assertEqual(resp.status_code, 200)
self.assertIn(self.course.display_name.encode('utf-8'), resp.content)
self.assertIn("error processing your redeem 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):
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
request.session = 'session'
messages = FallbackStorage(request)
request._messages = messages # pylint: disable=protected-access
coupon_admin = SoftDeleteCouponAdmin(Coupon, AdminSite())
test_query_set = coupon_admin.get_queryset(request)
test_actions = coupon_admin.get_actions(request)
self.assertIn('really_delete_selected', test_actions['really_delete_selected'])
self.assertEqual(get_coupon.is_active, True)
coupon_admin.really_delete_selected(request, test_query_set)
for coupon in test_query_set:
self.assertEqual(coupon.is_active, False)
coupon_admin.delete_model(request, get_coupon)
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.get_queryset(request)
coupon_admin.really_delete_selected(request, test_query_set)
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)
redeem_url = reverse('register_code_redemption', args=[self.reg_code])
response = self.client.get(redeem_url)
self.assertEquals(response.status_code, 200)
# check button text
self.assertIn('Activate Course Enrollment', response.content)
#now activate the user by enrolling him/her to the course
response = self.client.post(redeem_url)
self.assertEquals(response.status_code, 200)
# now testing registration code already used scenario, reusing the same code
# the item has been removed when using the registration code for the first time
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.reg_code})
self.assertEqual(resp.status_code, 400)
self.assertIn(u"This enrollment code ({enrollment_code}) is not valid.".format(
enrollment_code=self.reg_code
), resp.content)
def test_upgrade_from_valid_reg_code(self):
"""Use a valid registration code to upgrade from honor to verified mode. """
# Ensure the course has a verified mode
course_key = text_type(self.course_key)
self._add_course_mode(mode_slug='verified')
self.add_reg_code(course_key, mode_slug='verified')
# Enroll as honor in the course with the current user.
CourseEnrollment.enroll(self.user, self.course_key, mode=CourseMode.HONOR)
self.login_user()
current_enrollment, __ = CourseEnrollment.enrollment_mode_for_user(self.user, self.course_key)
self.assertEquals('honor', current_enrollment)
redeem_url = reverse('register_code_redemption', args=[self.reg_code])
response = self.client.get(redeem_url)
self.assertEquals(response.status_code, 200)
# check button text
self.assertIn('Activate Course Enrollment', response.content)
#now activate the user by enrolling him/her to the course
response = self.client.post(redeem_url)
self.assertEquals(response.status_code, 200)
# Once upgraded, should be "verified"
current_enrollment, __ = CourseEnrollment.enrollment_mode_for_user(self.user, self.course_key)
self.assertEquals('verified', current_enrollment)
@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(
u'Code redemption does not exist for order item id=%s.',
str(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(
u'Coupon "%s" redemption entry removed for user "%s" for order item "%s"',
self.coupon_code,
self.user,
str(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(
u'Coupon redemption entry removed for user %s for order %s',
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))
self.assertEquals(item.list_price, self.cost)
elif item.id == cert_item.id:
self.assertEquals(item.list_price, self.cost)
self.assertEquals(item.unit_cost, self.cost)
# 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 "%s" redemption entry removed for user "%s" for order item "%s"', # pylint: disable=unicode-format-string
self.coupon_code,
self.user,
str(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(u"order item %s removed for user %s", str(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(
u'Coupon redemption entry removed for user %s for order %s',
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('add_course_to_cart', args=[text_type(self.course_key)]))
self.assertEqual(resp.status_code, 400)
self.assertIn(u'You are already registered in course {0}.'.format(text_type(self.course_key)), resp.content)
def test_add_nonexistent_course_to_cart(self):
self.login_user()
resp = self.client.post(reverse('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('add_course_to_cart', args=[text_type(self.course_key)])
resp = self.client.post(reverse('add_course_to_cart', args=[text_type(self.course_key)]))
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,
mode_slug=self.course_mode.mode_slug
)
cert_item = CertificateItem.add_to_order(
self.cart,
self.verified_course_key,
self.cost,
self.course_mode.mode_slug
)
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=unpacking-non-sequence
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'])
# check for the default currency in the context
self.assertEqual(context['currency'], 'usd')
self.assertEqual(context['currency_symbol'], '$')
@patch('shoppingcart.views.render_purchase_form_html', form_mock)
@patch('shoppingcart.views.render_to_response', render_mock)
@override_settings(PAID_COURSE_REGISTRATION_CURRENCY=['PKR', 'Rs'])
def test_show_cart_with_override_currency_settings(self):
self.login_user()
reg_item = PaidCourseRegistration.add_to_order(self.cart, self.course_key)
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=unpacking-non-sequence
purchase_form_arg_cart_items = purchase_form_arg_cart.orderitem_set.all().select_subclasses()
self.assertIn(reg_item, purchase_form_arg_cart_items)
((template, context), _) = render_mock.call_args
self.assertEqual(template, 'shoppingcart/shopping_cart.html')
# check for the override currency settings in the context
self.assertEqual(context['currency'], 'PKR')
self.assertEqual(context['currency_symbol'], 'Rs')
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(
u'Cannot remove cart OrderItem id=%s. DoesNotExist or item is already purchased', str(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(
u'Cannot remove cart OrderItem id=%s. DoesNotExist or item is already purchased',
'-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!!!')
@ddt.data(0, 1)
def test_show_receipt_json(self, num_items):
# Create the correct number of items in the order
for __ in range(num_items):
CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor')
self.cart.purchase()
self.login_user()
url = reverse('shoppingcart.views.show_receipt', args=[self.cart.id])
resp = self.client.get(url, HTTP_ACCEPT="application/json")
# Should have gotten a successful response
self.assertEqual(resp.status_code, 200)
# Parse the response as JSON and check the contents
json_resp = json.loads(resp.content)
self.assertEqual(json_resp.get('currency'), self.cart.currency)
self.assertEqual(json_resp.get('purchase_datetime'), get_default_time_display(self.cart.purchase_time))
self.assertEqual(json_resp.get('total_cost'), self.cart.total_cost)
self.assertEqual(json_resp.get('status'), "purchased")
self.assertEqual(json_resp.get('billed_to'), {
'first_name': self.cart.bill_to_first,
'last_name': self.cart.bill_to_last,
'street1': self.cart.bill_to_street1,
'street2': self.cart.bill_to_street2,
'city': self.cart.bill_to_city,
'state': self.cart.bill_to_state,
'postal_code': self.cart.bill_to_postalcode,
'country': self.cart.bill_to_country
})
self.assertEqual(len(json_resp.get('items')), num_items)
for item in json_resp.get('items'):
self.assertEqual(item, {
'unit_cost': 40,
'quantity': 1,
'line_cost': 40,
'line_desc': u'{} for course Test Course'.format(self.verified_course_mode.mode_display_name),
'course_key': six.text_type(self.verified_course_key)
})
def test_show_receipt_xss(self):
CertificateItem.add_to_order(self.cart, self.xss_course_key, self.cost, 'honor')
self.cart.purchase()
self.login_user()
url = reverse('shoppingcart.views.show_receipt', args=[self.cart.id])
resp = self.client.get(url)
self.assert_no_xss(resp, '<script>alert("XSS")</script>')
@patch('shoppingcart.views.render_to_response', render_mock)
def test_reg_code_xss(self):
self.add_reg_code(self.xss_course_key)
# One courses in user shopping cart
self.add_course_to_user_cart(self.xss_course_key)
self.assertEquals(self.cart.orderitem_set.count(), 1)
post_response = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.reg_code})
self.assertEqual(post_response.status_code, 200)
redeem_url = reverse('register_code_redemption', args=[self.reg_code])
redeem_response = self.client.get(redeem_url)
self.assert_no_xss(redeem_response, '<script>alert("XSS")</script>')
def test_show_receipt_json_multiple_items(self):
# Two different item types
PaidCourseRegistration.add_to_order(
self.cart,
self.course_key,
mode_slug=self.course_mode.mode_slug
)
CertificateItem.add_to_order(
self.cart,
self.verified_course_key,
self.cost,
self.verified_course_mode.mode_slug
)
self.cart.purchase()
self.login_user()
url = reverse('shoppingcart.views.show_receipt', args=[self.cart.id])
resp = self.client.get(url, HTTP_ACCEPT="application/json")
# Should have gotten a successful response
self.assertEqual(resp.status_code, 200)
# Parse the response as JSON and check the contents
json_resp = json.loads(resp.content)
self.assertEqual(json_resp.get('total_cost'), self.cart.total_cost)
items = json_resp.get('items')
self.assertEqual(len(items), 2)
self.assertEqual(items[0], {
'unit_cost': 40,
'quantity': 1,
'line_cost': 40,
'line_desc': 'Registration for Course: Robot Super Course',
'course_key': six.text_type(self.course_key)
})
self.assertEqual(items[1], {
'unit_cost': 40,
'quantity': 1,
'line_cost': 40,
'line_desc': u'{} for course Test Course'.format(self.verified_course_mode.mode_display_name),
'course_key': six.text_type(self.verified_course_key)
})
def test_receipt_json_refunded(self):
mock_enrollment = Mock()
mock_enrollment.refundable.side_effect = lambda: True
mock_enrollment.course_id = self.verified_course_key
mock_enrollment.user = self.user
CourseMode.objects.create(
course_id=self.verified_course_key,
mode_slug="verified",
mode_display_name="verified cert",
min_price=self.cost
)
cert = CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'verified')
self.cart.purchase()
cert.refund_cert_callback(course_enrollment=mock_enrollment)
self.login_user()
url = reverse('shoppingcart.views.show_receipt', args=[self.cart.id])
resp = self.client.get(url, HTTP_ACCEPT="application/json")
self.assertEqual(resp.status_code, 200)
json_resp = json.loads(resp.content)
self.assertEqual(json_resp.get('status'), 'refunded')
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, mode_slug=self.course_mode.mode_slug)
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)
redeem_url = reverse('register_code_redemption', args=[self.reg_code])
response = self.client.get(redeem_url)
self.assertEquals(response.status_code, 200)
# check button text
self.assertIn('Activate Course Enrollment', response.content)
#now activate the user by enrolling him/her to the course
response = self.client.post(redeem_url)
self.assertEquals(response.status_code, 200)
@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, mode_slug=self.course_mode.mode_slug)
item2 = PaidCourseRegistration.add_to_order(
self.cart,
self.testing_course.id,
mode_slug=self.course_mode.mode_slug
)
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)
redeem_url = reverse('register_code_redemption', args=[self.reg_code])
resp = self.client.get(redeem_url)
self.assertEquals(resp.status_code, 200)
# check button text
self.assertIn('Activate Course Enrollment', resp.content)
#now activate the user by enrolling him/her to the course
resp = self.client.post(redeem_url)
self.assertEquals(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=unpacking-non-sequence
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)
# make sure the enrollment_ids were stored in the PaidCourseRegistration items
# refetch them first since they are updated
# item1 has been deleted from the the cart.
# User has been enrolled for the item1
item2 = PaidCourseRegistration.objects.get(id=item2.id)
self.assertIsNotNone(item2.course_enrollment)
self.assertEqual(item2.course_enrollment.course_id, self.testing_course.id)
@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,
mode_slug=self.course_mode.mode_slug
)
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=unpacking-non-sequence
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'])
# check for the default currency settings in the context
self.assertEqual(context['currency_symbol'], '$')
self.assertEqual(context['currency'], 'usd')
@override_settings(PAID_COURSE_REGISTRATION_CURRENCY=['PKR', 'Rs'])
@patch('shoppingcart.views.render_to_response', render_mock)
def test_show_receipt_success_with_override_currency_settings(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)
((template, context), _) = render_mock.call_args # pylint: disable=unpacking-non-sequence
self.assertEqual(template, 'shoppingcart/receipt.html')
self.assertIn(reg_item, context['shoppingcart_items'][0])
self.assertIn(cert_item, context['shoppingcart_items'][1])
# check for the override currency settings in the context
self.assertEqual(context['currency_symbol'], 'Rs')
self.assertEqual(context['currency'], 'PKR')
@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, mode_slug=self.course_mode.mode_slug)
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,
mode_slug=self.course_mode.mode_slug
)
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)
# fetch the newly generated registration codes
course_registration_codes = CourseRegistrationCode.objects.filter(order=self.cart)
((template, context), _) = render_mock.call_args # pylint: disable=unpacking-non-sequence
self.assertEqual(template, 'shoppingcart/receipt.html')
self.assertEqual(context['order'], self.cart)
self.assertIn(reg_item, context['shoppingcart_items'][0])
# now check for all the registration codes in the receipt
# and all the codes should be unused at this point
self.assertIn(course_registration_codes[0].code, context['reg_code_info_list'][0]['code'])
self.assertIn(course_registration_codes[1].code, context['reg_code_info_list'][1]['code'])
self.assertFalse(context['reg_code_info_list'][0]['is_redeemed'])
self.assertFalse(context['reg_code_info_list'][1]['is_redeemed'])
self.assertIn(self.cart.purchase_time.strftime(u"%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(u"Invoice #{order_id}".format(order_id=self.cart.id), resp.content.decode(resp.charset))
codes_string = u'You have successfully purchased <b>{total_registration_codes} course registration codes'
self.assertIn(codes_string.format(
total_registration_codes=context['total_registration_codes']),
resp.content.decode(resp.charset)
)
# now redeem one of registration code from the previous order
redeem_url = reverse('register_code_redemption', args=[context['reg_code_info_list'][0]['code']])
#now activate the user by enrolling him/her to the course
response = self.client.post(redeem_url)
self.assertEquals(response.status_code, 200)
self.assertIn('View Dashboard', response.content)
# now view the receipt page again to see if any registration codes
# has been expired or not
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=unpacking-non-sequence
self.assertEqual(template, 'shoppingcart/receipt.html')
# now check for all the registration codes in the receipt
# and one of code should be used at this point
self.assertTrue(context['reg_code_info_list'][0]['is_redeemed'])
self.assertFalse(context['reg_code_info_list'][1]['is_redeemed'])
@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,
mode_slug=self.course_mode.mode_slug
)
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()
self.mock_tracker.emit.reset_mock()
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
# 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'])
@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,
mode_slug=self.course_mode.mode_slug
)
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)
def _assert_404(self, url, use_post=False):
"""
Helper method to assert that a given url will return a 404 status code
"""
if use_post:
response = self.client.post(url)
else:
response = self.client.get(url)
self.assertEquals(response.status_code, 404)
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_PAID_COURSE_REGISTRATION': False})
def test_disabled_paid_courses(self):
"""
Assert that the pages that require ENABLE_PAID_COURSE_REGISTRATION=True return a
HTTP 404 status code when we have this flag turned off
"""
self.login_user()
self._assert_404(reverse('shoppingcart.views.show_cart', args=[]))
self._assert_404(reverse('shoppingcart.views.clear_cart', args=[]))
self._assert_404(reverse('shoppingcart.views.remove_item', args=[]), use_post=True)
self._assert_404(reverse('register_code_redemption', args=["testing"]))
self._assert_404(reverse('shoppingcart.views.use_code', args=[]), use_post=True)
self._assert_404(reverse('shoppingcart.views.update_user_cart', args=[]))
self._assert_404(reverse('shoppingcart.views.reset_code_redemption', args=[]), use_post=True)
self._assert_404(reverse('billing_details', args=[]))
def test_upgrade_postpay_callback_emits_ga_event(self):
# Enroll as honor in the course with the current user.
CourseEnrollment.enroll(self.user, self.course_key)
# add verified mode
CourseMode.objects.create(
course_id=self.verified_course_key,
mode_slug="verified",
mode_display_name="verified cert",
min_price=self.cost
)
# Purchase a verified certificate
self.cart = Order.get_cart_for_user(self.user)
CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'verified')
self.cart.start_purchase()
self.login_user()
# setting the attempting upgrade session value.
session = self.client.session
session['attempting_upgrade'] = True
session.save()
ordered_params = OrderedDict([
('amount', self.cost),
('currency', 'usd'),
('transaction_type', 'sale'),
('orderNumber', str(self.cart.id)),
('access_key', '123456789'),
('merchantID', 'edx'),
('djch', '012345678912'),
('orderPage_version', 2),
('orderPage_serialNumber', '1234567890'),
('profile_id', "00000001"),
('reference_number', str(self.cart.id)),
('locale', 'en'),
('signed_date_time', '2014-08-18T13:59:31Z'),
])
resp_params = PaymentFakeView.response_post_params(sign(ordered_params))
self.assertTrue(self.client.session.get('attempting_upgrade'))
url = reverse('shoppingcart.views.postpay_callback')
self.client.post(url, resp_params, follow=True)
self.assertFalse(self.client.session.get('attempting_upgrade'))
self.mock_tracker.emit.assert_any_call(
'edx.course.enrollment.upgrade.succeeded',
{
'user_id': self.user.id,
'course_id': text_type(self.verified_course_key),
'mode': 'verified'
}
)
def test_shopping_cart_navigation_link(self):
"""
Tests shopping cart link is available in navigation header.
"""
CourseEnrollment.enroll(self.user, self.course_key)
self.add_course_to_user_cart(self.testing_course.id)
resp = self.client.get(reverse('courseware', kwargs={'course_id': text_type(self.course.id)}))
self.assertEqual(resp.status_code, 200)
self.assertIn('<a class="shopping-cart"', resp.content)
def test_shopping_cart_navigation_link_and_not_on_courseware(self):
"""
Tests shopping cart link is available in navigation header
and requested page is not courseware too.
"""
CourseEnrollment.enroll(self.user, self.course_key)
self.add_course_to_user_cart(self.testing_course.id)
resp = self.client.get(reverse('dashboard'))
self.assertEqual(resp.status_code, 200)
self.assertIn('<a class="shopping-cart"', resp.content)
class ReceiptRedirectTest(SharedModuleStoreTestCase):
"""Test special-case redirect from the receipt page. """
COST = 40
PASSWORD = 'password'
@classmethod
def setUpClass(cls):
super(ReceiptRedirectTest, cls).setUpClass()
cls.course = CourseFactory.create()
cls.course_key = cls.course.id
def setUp(self):
super(ReceiptRedirectTest, self).setUp()
self.user = UserFactory.create()
self.user.set_password(self.PASSWORD)
self.user.save()
self.course_mode = CourseMode(
course_id=self.course_key,
mode_slug="verified",
mode_display_name="verified cert",
min_price=self.COST
)
self.course_mode.save()
self.cart = Order.get_cart_for_user(self.user)
self.client.login(
username=self.user.username,
password=self.PASSWORD
)
def test_postpay_callback_redirect_to_verify_student(self):
# Create other carts first
# This ensures that the order ID and order item IDs do not match
Order.get_cart_for_user(self.user).start_purchase()
Order.get_cart_for_user(self.user).start_purchase()
Order.get_cart_for_user(self.user).start_purchase()
# Purchase a verified certificate
self.cart = Order.get_cart_for_user(self.user)
CertificateItem.add_to_order(
self.cart,
self.course_key,
self.COST,
'verified'
)
self.cart.start_purchase()
# Simulate hitting the post-pay callback
with patch('shoppingcart.views.process_postpay_callback') as mock_process:
mock_process.return_value = {'success': True, 'order': self.cart}
url = reverse('shoppingcart.views.postpay_callback')
resp = self.client.post(url, follow=True)
# Expect to be redirected to the payment confirmation
# page in the verify_student app
redirect_url = reverse(
'verify_student_payment_confirmation',
kwargs={'course_id': six.text_type(self.course_key)}
)
redirect_url += '?payment-order-num={order_num}'.format(
order_num=self.cart.id
)
self.assertIn(redirect_url, resp.redirect_chain[0][0])
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_PAID_COURSE_REGISTRATION': True})
class ShoppingcartViewsClosedEnrollment(ModuleStoreTestCase):
"""
Test suite for ShoppingcartViews Course Enrollments Closed or not
"""
def setUp(self):
super(ShoppingcartViewsClosedEnrollment, self).setUp()
self.user = UserFactory.create()
self.user.set_password('password')
self.user.save()
self.instructor = AdminFactory.create()
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=CourseMode.HONOR,
mode_display_name="honor cert",
min_price=self.cost
)
self.course_mode.save()
self.testing_course = CourseFactory.create(
org='Edx',
number='999',
display_name='Testing Super Course',
metadata={"invitation_only": False}
)
self.testing_course_mode = CourseMode(
course_id=self.testing_course.id,
mode_slug=CourseMode.HONOR,
mode_display_name="honor cert",
min_price=self.cost
)
self.course_mode.save()
self.percentage_discount = 20.0
self.coupon_code = 'asdsad'
self.course_mode = CourseMode(course_id=self.testing_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)
self.now = datetime.now(pytz.UTC)
self.tomorrow = self.now + timedelta(days=1)
self.nextday = self.tomorrow + timedelta(days=1)
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 login_user(self):
"""
Helper fn to login self.user
"""
self.client.login(username=self.user.username, password="password")
@patch('shoppingcart.views.render_to_response', render_mock)
def test_to_check_that_cart_item_enrollment_is_closed(self):
self.login_user()
reg_item1 = PaidCourseRegistration.add_to_order(self.cart, self.course_key)
expired_course_item = PaidCourseRegistration.add_to_order(self.cart, self.testing_course.id)
# update the testing_course enrollment dates
self.testing_course.enrollment_start = self.tomorrow
self.testing_course.enrollment_end = self.nextday
self.testing_course = self.update_course(self.testing_course, self.user.id)
# now add the same coupon code to the second course(testing_course)
self.add_coupon(self.testing_course.id, True, self.coupon_code)
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code})
self.assertEqual(resp.status_code, 200)
coupon_redemption = CouponRedemption.objects.filter(coupon__course_id=expired_course_item.course_id,
order=expired_course_item.order_id)
self.assertEqual(coupon_redemption.count(), 1)
# testing_course enrollment is closed but the course is in the cart
# so we delete that item from the cart and display the message in the cart
# coupon redemption entry should also be deleted when the item is expired.
resp = self.client.get(reverse('shoppingcart.views.show_cart', args=[]))
self.assertEqual(resp.status_code, 200)
self.assertIn(u"{course_name} has been removed because the enrollment period has closed.".format(
course_name=self.testing_course.display_name), resp.content.decode(resp.charset)
)
# now the redemption entry should be deleted from the table.
coupon_redemption = CouponRedemption.objects.filter(coupon__course_id=expired_course_item.course_id,
order=expired_course_item.order_id)
self.assertEqual(coupon_redemption.count(), 0)
((template, context), _tmp) = render_mock.call_args
self.assertEqual(template, 'shoppingcart/shopping_cart.html')
self.assertEqual(context['order'], self.cart)
self.assertIn(reg_item1, context['shoppingcart_items'][0])
self.assertEqual(1, len(context['shoppingcart_items']))
self.assertEqual(True, context['is_course_enrollment_closed'])
self.assertIn(self.testing_course.display_name, context['expired_course_names'])
def test_to_check_that_cart_item_enrollment_is_closed_when_clicking_the_payment_button(self):
self.login_user()
PaidCourseRegistration.add_to_order(
self.cart,
self.course_key,
mode_slug=self.course_mode.mode_slug
)
PaidCourseRegistration.add_to_order(
self.cart,
self.testing_course.id,
mode_slug=self.testing_course_mode.mode_slug
)
# update the testing_course enrollment dates
self.testing_course.enrollment_start = self.tomorrow
self.testing_course.enrollment_end = self.nextday
self.testing_course = self.update_course(self.testing_course, self.user.id)
# testing_course enrollment is closed but the course is in the cart
# so we delete that item from the cart and display the message in the cart
resp = self.client.get(reverse('shoppingcart.views.verify_cart'))
self.assertEqual(resp.status_code, 200)
self.assertTrue(json.loads(resp.content)['is_course_enrollment_closed'])
resp = self.client.get(reverse('shoppingcart.views.show_cart', args=[]))
self.assertEqual(resp.status_code, 200)
self.assertIn(
u"{course_name} has been removed because the enrollment period has closed.".format(
course_name=self.testing_course.display_name
),
resp.content.decode(resp.charset)
)
self.assertIn('40.00', resp.content)
def test_is_enrollment_closed_when_order_type_is_business(self):
self.login_user()
self.cart.order_type = 'business'
self.cart.save()
PaidCourseRegistration.add_to_order(self.cart, self.course_key, mode_slug=self.course_mode.mode_slug)
CourseRegCodeItem.add_to_order(self.cart, self.testing_course.id, 2, mode_slug=self.course_mode.mode_slug)
# update the testing_course enrollment dates
self.testing_course.enrollment_start = self.tomorrow
self.testing_course.enrollment_end = self.nextday
self.testing_course = self.update_course(self.testing_course, self.user.id)
resp = self.client.post(reverse('billing_details'))
self.assertEqual(resp.status_code, 200)
self.assertTrue(json.loads(resp.content)['is_course_enrollment_closed'])
# testing_course enrollment is closed but the course is in the cart
# so we delete that item from the cart and display the message in the cart
resp = self.client.get(reverse('shoppingcart.views.show_cart', args=[]))
self.assertEqual(resp.status_code, 200)
self.assertIn(
u"{course_name} has been removed because the enrollment period has closed.".format(
course_name=self.testing_course.display_name
),
resp.content.decode(resp.charset)
)
self.assertIn('40.00', resp.content)
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_PAID_COURSE_REGISTRATION': True})
class RegistrationCodeRedemptionCourseEnrollment(SharedModuleStoreTestCase):
"""
Test suite for RegistrationCodeRedemption Course Enrollments
"""
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
@classmethod
def setUpClass(cls):
super(RegistrationCodeRedemptionCourseEnrollment, cls).setUpClass()
cls.course = CourseFactory.create(org='MITx', number='999', display_name='Robot Super Course')
cls.course_key = cls.course.id
def setUp(self):
super(RegistrationCodeRedemptionCourseEnrollment, self).setUp()
self.user = UserFactory.create()
self.user.set_password('password')
self.user.save()
self.cost = 40
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 range(30): # pylint: disable=unused-variable
response = self.client.post(url)
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)
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 range(30): # pylint: disable=unused-variable
response = self.client.get(url)
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)
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')
# Registration Code Generation only available to Sales Admins.
CourseSalesAdminRole(self.course.id).add_users(instructor)
url = reverse('generate_registration_codes',
kwargs={'course_id': text_type(self.course.id)})
data = {
'total_registration_codes': 12, 'company_name': 'Test Group', 'company_contact_name': 'Test@company.com',
'company_contact_email': 'Test@company.com', 'unit_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)
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)
self.assertEquals(response.status_code, 200)
# check button text
self.assertIn('Activate Course Enrollment', response.content)
#now activate the user by enrolling him/her to the course
response = self.client.post(redeem_url)
self.assertEquals(response.status_code, 200)
self.assertIn('View Dashboard', 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)
self.assertEquals(len(RegistrationCodeRedemption.objects.filter(registration_code__code=registration_code)), 1)
self.assertIn("You&#39;ve clicked a link for an enrollment code that has already been used.", response.content)
#now check that the registration code has already been redeemed
response = self.client.post(redeem_url)
self.assertIn("You&#39;ve clicked a link for an enrollment code that has already been used.", 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.assertIn(self.course.display_name.encode('utf-8'), response.content)
@ddt.ddt
class RedeemCodeEmbargoTests(UrlResetMixin, ModuleStoreTestCase):
"""Test blocking redeem code redemption based on country access rules. """
USERNAME = 'bob'
PASSWORD = 'test'
URLCONF_MODULES = ['openedx.core.djangoapps.embargo']
@patch.dict(settings.FEATURES, {'EMBARGO': True})
def setUp(self):
super(RedeemCodeEmbargoTests, self).setUp()
self.course = CourseFactory.create()
self.user = UserFactory.create(username=self.USERNAME, password=self.PASSWORD)
result = self.client.login(username=self.user.username, password=self.PASSWORD)
self.assertTrue(result, msg="Could not log in")
@ddt.data('get', 'post')
@patch.dict(settings.FEATURES, {'EMBARGO': True})
def test_registration_code_redemption_embargo(self, method):
# Create a valid registration code
reg_code = CourseRegistrationCode.objects.create(
code="abcd1234",
course_id=self.course.id,
created_by=self.user
)
# Try to redeem the code from a restricted country
with restrict_course(self.course.id) as redirect_url:
url = reverse(
'register_code_redemption',
kwargs={'registration_code': 'abcd1234'}
)
response = getattr(self.client, method)(url)
self.assertRedirects(response, redirect_url)
# The registration code should NOT be redeemed
is_redeemed = RegistrationCodeRedemption.objects.filter(
registration_code=reg_code
).exists()
self.assertFalse(is_redeemed)
# The user should NOT be enrolled
is_enrolled = CourseEnrollment.is_enrolled(self.user, self.course.id)
self.assertFalse(is_enrolled)
@ddt.ddt
class DonationViewTest(SharedModuleStoreTestCase):
"""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"
@classmethod
def setUpClass(cls):
super(DonationViewTest, cls).setUpClass()
cls.course = CourseFactory.create(display_name="Test Course")
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):
# Donate to our 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"],
six.text_type(course_id)
)
self.assertEqual(
payment_info["payment_params"]["merchant_defined_data2"],
"donation_course"
)
else:
self.assertEqual(payment_info["payment_params"]["merchant_defined_data1"], "")
self.assertEqual(
payment_info["payment_params"]["merchant_defined_data2"],
"donation_general"
)
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})
class CSVReportViewsTest(SharedModuleStoreTestCase):
"""
Test suite for CSV Purchase Reporting
"""
@classmethod
def setUpClass(cls):
super(CSVReportViewsTest, cls).setUpClass()
cls.course = CourseFactory.create(org='MITx', number='999', display_name='Robot Super Course')
cls.course_key = cls.course.id
verified_course = CourseFactory.create(org='org', number='test', display_name='Test Course')
cls.verified_course_key = verified_course.id
def setUp(self):
super(CSVReportViewsTest, self).setUp()
self.user = UserFactory.create()
self.user.set_password('password')
self.user.save()
self.cost = 40
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()
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'))
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, mode_slug=self.course_mode.mode_slug)
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(
",1,purchased,1,40.00,40.00,usd,Registration for Course: Robot Super Course,",
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):
super(UtilFnsTest, self).setUp()
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)