|
|
|
|
@@ -3,8 +3,9 @@
|
|
|
|
|
Tests of verify_student views.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import absolute_import
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
import urllib
|
|
|
|
|
from datetime import timedelta
|
|
|
|
|
from uuid import uuid4
|
|
|
|
|
|
|
|
|
|
@@ -14,18 +15,23 @@ import httpretty
|
|
|
|
|
import mock
|
|
|
|
|
import moto
|
|
|
|
|
import requests
|
|
|
|
|
import six
|
|
|
|
|
import six.moves.urllib.error # pylint: disable=import-error
|
|
|
|
|
import six.moves.urllib.parse # pylint: disable=import-error
|
|
|
|
|
import six.moves.urllib.request # pylint: disable=import-error
|
|
|
|
|
from bs4 import BeautifulSoup
|
|
|
|
|
from django.conf import settings
|
|
|
|
|
from django.core import mail
|
|
|
|
|
from django.urls import reverse
|
|
|
|
|
from django.test import TestCase
|
|
|
|
|
from django.test.client import Client, RequestFactory
|
|
|
|
|
from django.test.utils import override_settings
|
|
|
|
|
from django.urls import reverse
|
|
|
|
|
from django.utils.timezone import now
|
|
|
|
|
from django.utils.translation import ugettext as _
|
|
|
|
|
from mock import Mock, patch
|
|
|
|
|
from opaque_keys.edx.keys import CourseKey
|
|
|
|
|
from opaque_keys.edx.locator import CourseLocator
|
|
|
|
|
from six.moves import zip
|
|
|
|
|
from waffle.testutils import override_switch
|
|
|
|
|
|
|
|
|
|
from common.test.utils import XssTestMixin
|
|
|
|
|
@@ -35,11 +41,7 @@ from lms.djangoapps.commerce.models import CommerceConfiguration
|
|
|
|
|
from lms.djangoapps.commerce.tests import TEST_API_URL, TEST_PAYMENT_DATA, TEST_PUBLIC_URL_ROOT
|
|
|
|
|
from lms.djangoapps.commerce.utils import EcommerceService
|
|
|
|
|
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification, VerificationDeadline
|
|
|
|
|
from lms.djangoapps.verify_student.views import (
|
|
|
|
|
PayAndVerifyView,
|
|
|
|
|
checkout_with_ecommerce_service,
|
|
|
|
|
render_to_response
|
|
|
|
|
)
|
|
|
|
|
from lms.djangoapps.verify_student.views import PayAndVerifyView, checkout_with_ecommerce_service, render_to_response
|
|
|
|
|
from openedx.core.djangoapps.embargo.test_utils import restrict_course
|
|
|
|
|
from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme
|
|
|
|
|
from openedx.core.djangoapps.user_api.accounts.api import get_account_settings
|
|
|
|
|
@@ -67,7 +69,7 @@ class StartView(TestCase):
|
|
|
|
|
attempting a Photo Verification.
|
|
|
|
|
"""
|
|
|
|
|
def start_url(self, course_id=""):
|
|
|
|
|
return "/verify_student/{0}".format(urllib.quote(course_id))
|
|
|
|
|
return "/verify_student/{0}".format(six.moves.urllib.parse.quote(course_id))
|
|
|
|
|
|
|
|
|
|
def test_start_new_verification(self):
|
|
|
|
|
"""
|
|
|
|
|
@@ -506,12 +508,12 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
|
|
|
|
response = self._get_page('verify_student_payment_confirmation', course.id)
|
|
|
|
|
|
|
|
|
|
courseware_url = (
|
|
|
|
|
reverse("course_root", kwargs={'course_id': unicode(course.id)})
|
|
|
|
|
reverse("course_root", kwargs={'course_id': six.text_type(course.id)})
|
|
|
|
|
if show_courseware_url else ""
|
|
|
|
|
)
|
|
|
|
|
self._assert_course_details(
|
|
|
|
|
response,
|
|
|
|
|
unicode(course.id),
|
|
|
|
|
six.text_type(course.id),
|
|
|
|
|
course.display_name,
|
|
|
|
|
courseware_url
|
|
|
|
|
)
|
|
|
|
|
@@ -642,7 +644,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
|
|
|
|
course = self._create_course("verified")
|
|
|
|
|
response = self._get_page(url_name, course.id, expected_status_code=302)
|
|
|
|
|
|
|
|
|
|
original_url = reverse(url_name, kwargs={'course_id': unicode(course.id)})
|
|
|
|
|
original_url = reverse(url_name, kwargs={'course_id': six.text_type(course.id)})
|
|
|
|
|
login_url = u"{login_url}?next={original_url}".format(
|
|
|
|
|
login_url=reverse('signin_user'),
|
|
|
|
|
original_url=original_url
|
|
|
|
|
@@ -742,7 +744,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
|
|
|
|
# Expect that the expiration date is set
|
|
|
|
|
response = self._get_page(payment_flow, course.id)
|
|
|
|
|
data = self._get_page_data(response)
|
|
|
|
|
self.assertEqual(data['verification_deadline'], unicode(deadline))
|
|
|
|
|
self.assertEqual(data['verification_deadline'], six.text_type(deadline))
|
|
|
|
|
|
|
|
|
|
def test_course_mode_expired(self):
|
|
|
|
|
deadline = now() + timedelta(days=-360)
|
|
|
|
|
@@ -810,7 +812,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
|
|
|
|
|
|
|
|
|
# Check that the verification deadline (rather than the upgrade deadline) is displayed
|
|
|
|
|
if verification_deadline is not None:
|
|
|
|
|
self.assertEqual(data["verification_deadline"], unicode(verification_deadline))
|
|
|
|
|
self.assertEqual(data["verification_deadline"], six.text_type(verification_deadline))
|
|
|
|
|
else:
|
|
|
|
|
self.assertEqual(data["verification_deadline"], "")
|
|
|
|
|
|
|
|
|
|
@@ -941,13 +943,13 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
|
|
|
|
"""Set the contribution amount pre-filled in a session var. """
|
|
|
|
|
session = self.client.session
|
|
|
|
|
session["donation_for_course"] = {
|
|
|
|
|
unicode(course_id): amount
|
|
|
|
|
six.text_type(course_id): amount
|
|
|
|
|
}
|
|
|
|
|
session.save()
|
|
|
|
|
|
|
|
|
|
def _get_page(self, url_name, course_key, expected_status_code=200, skip_first_step=False):
|
|
|
|
|
"""Retrieve one of the verification pages. """
|
|
|
|
|
url = reverse(url_name, kwargs={"course_id": unicode(course_key)})
|
|
|
|
|
url = reverse(url_name, kwargs={"course_id": six.text_type(course_key)})
|
|
|
|
|
|
|
|
|
|
if skip_first_step:
|
|
|
|
|
url += "?skip-first-step=1"
|
|
|
|
|
@@ -978,7 +980,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
|
|
|
|
def _assert_requirements_displayed(self, response, requirements):
|
|
|
|
|
"""Check that requirements are displayed on the page. """
|
|
|
|
|
response_dict = self._get_page_data(response)
|
|
|
|
|
for req, displayed in response_dict['requirements'].iteritems():
|
|
|
|
|
for req, displayed in six.iteritems(response_dict['requirements']):
|
|
|
|
|
if req in requirements:
|
|
|
|
|
self.assertTrue(displayed, msg=u"Expected '{req}' requirement to be displayed".format(req=req))
|
|
|
|
|
else:
|
|
|
|
|
@@ -1039,17 +1041,17 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
|
|
|
|
|
|
|
|
|
|
def _assert_redirects_to_start_flow(self, response, course_id):
|
|
|
|
|
"""Check that the page redirects to the start of the payment/verification flow. """
|
|
|
|
|
url = reverse('verify_student_start_flow', kwargs={'course_id': unicode(course_id)})
|
|
|
|
|
url = reverse('verify_student_start_flow', kwargs={'course_id': six.text_type(course_id)})
|
|
|
|
|
self.assertRedirects(response, url)
|
|
|
|
|
|
|
|
|
|
def _assert_redirects_to_verify_start(self, response, course_id, status_code=302):
|
|
|
|
|
"""Check that the page redirects to the "verify later" part of the flow. """
|
|
|
|
|
url = reverse('verify_student_verify_now', kwargs={'course_id': unicode(course_id)})
|
|
|
|
|
url = reverse('verify_student_verify_now', kwargs={'course_id': six.text_type(course_id)})
|
|
|
|
|
self.assertRedirects(response, url, status_code)
|
|
|
|
|
|
|
|
|
|
def _assert_redirects_to_upgrade(self, response, course_id):
|
|
|
|
|
"""Check that the page redirects to the "upgrade" part of the flow. """
|
|
|
|
|
url = reverse('verify_student_upgrade_and_verify', kwargs={'course_id': unicode(course_id)})
|
|
|
|
|
url = reverse('verify_student_upgrade_and_verify', kwargs={'course_id': six.text_type(course_id)})
|
|
|
|
|
self.assertRedirects(response, url)
|
|
|
|
|
|
|
|
|
|
@ddt.data("verify_student_start_flow", "verify_student_begin_flow")
|
|
|
|
|
@@ -1158,7 +1160,7 @@ class CheckoutTestMixin(object):
|
|
|
|
|
def test_create_order(self, patched_create_order):
|
|
|
|
|
# Create an order
|
|
|
|
|
params = {
|
|
|
|
|
'course_id': unicode(self.course.id),
|
|
|
|
|
'course_id': six.text_type(self.course.id),
|
|
|
|
|
'contribution': 100,
|
|
|
|
|
}
|
|
|
|
|
self._assert_checked_out(params, patched_create_order, self.course.id, 'verified')
|
|
|
|
|
@@ -1168,7 +1170,7 @@ class CheckoutTestMixin(object):
|
|
|
|
|
course = CourseFactory.create()
|
|
|
|
|
CourseModeFactory.create(mode_slug="professional", course_id=course.id, min_price=10, sku=self.make_sku())
|
|
|
|
|
# Create an order for a prof ed course
|
|
|
|
|
params = {'course_id': unicode(course.id)}
|
|
|
|
|
params = {'course_id': six.text_type(course.id)}
|
|
|
|
|
self._assert_checked_out(params, patched_create_order, course.id, 'professional')
|
|
|
|
|
|
|
|
|
|
def test_create_order_no_id_professional(self, patched_create_order):
|
|
|
|
|
@@ -1176,7 +1178,7 @@ class CheckoutTestMixin(object):
|
|
|
|
|
course = CourseFactory.create()
|
|
|
|
|
CourseModeFactory.create(mode_slug="no-id-professional", course_id=course.id, min_price=10, sku=self.make_sku())
|
|
|
|
|
# Create an order for a prof ed course
|
|
|
|
|
params = {'course_id': unicode(course.id)}
|
|
|
|
|
params = {'course_id': six.text_type(course.id)}
|
|
|
|
|
self._assert_checked_out(params, patched_create_order, course.id, 'no-id-professional')
|
|
|
|
|
|
|
|
|
|
def test_create_order_for_multiple_paid_modes(self, patched_create_order):
|
|
|
|
|
@@ -1185,14 +1187,14 @@ class CheckoutTestMixin(object):
|
|
|
|
|
CourseModeFactory.create(mode_slug="no-id-professional", course_id=course.id, min_price=10, sku=self.make_sku())
|
|
|
|
|
CourseModeFactory.create(mode_slug="professional", course_id=course.id, min_price=10, sku=self.make_sku())
|
|
|
|
|
# Create an order for a prof ed course
|
|
|
|
|
params = {'course_id': unicode(course.id)}
|
|
|
|
|
params = {'course_id': six.text_type(course.id)}
|
|
|
|
|
# TODO jsa - is this the intended behavior?
|
|
|
|
|
self._assert_checked_out(params, patched_create_order, course.id, 'no-id-professional')
|
|
|
|
|
|
|
|
|
|
def test_create_order_bad_donation_amount(self, patched_create_order):
|
|
|
|
|
# Create an order
|
|
|
|
|
params = {
|
|
|
|
|
'course_id': unicode(self.course.id),
|
|
|
|
|
'course_id': six.text_type(self.course.id),
|
|
|
|
|
'contribution': '99.9'
|
|
|
|
|
}
|
|
|
|
|
self._assert_checked_out(params, patched_create_order, None, None, expected_status_code=400)
|
|
|
|
|
@@ -1200,7 +1202,7 @@ class CheckoutTestMixin(object):
|
|
|
|
|
def test_create_order_good_donation_amount(self, patched_create_order):
|
|
|
|
|
# Create an order
|
|
|
|
|
params = {
|
|
|
|
|
'course_id': unicode(self.course.id),
|
|
|
|
|
'course_id': six.text_type(self.course.id),
|
|
|
|
|
'contribution': '100.0'
|
|
|
|
|
}
|
|
|
|
|
self._assert_checked_out(params, patched_create_order, self.course.id, 'verified')
|
|
|
|
|
@@ -1213,7 +1215,7 @@ class CheckoutTestMixin(object):
|
|
|
|
|
expected_payment_data['payment_form_data'].update({'foo': 'bar'})
|
|
|
|
|
patched_create_order.return_value = expected_payment_data
|
|
|
|
|
# there is no 'processor' parameter in the post payload, so the response should only contain payment form data.
|
|
|
|
|
params = {'course_id': unicode(self.course.id), 'contribution': 100}
|
|
|
|
|
params = {'course_id': six.text_type(self.course.id), 'contribution': 100}
|
|
|
|
|
response = self.client.post(reverse('verify_student_create_order'), params)
|
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
self.assertTrue(patched_create_order.called)
|
|
|
|
|
@@ -1237,7 +1239,9 @@ class TestCreateOrderShoppingCart(CheckoutTestMixin, ModuleStoreTestCase):
|
|
|
|
|
|
|
|
|
|
def _get_checkout_args(self, patched_create_order):
|
|
|
|
|
""" Assuming patched_create_order was called, return a mapping containing the call arguments."""
|
|
|
|
|
return dict(zip(('request', 'user', 'course_key', 'course_mode', 'amount'), patched_create_order.call_args[0]))
|
|
|
|
|
return dict(
|
|
|
|
|
list(zip(('request', 'user', 'course_key', 'course_mode', 'amount'), patched_create_order.call_args[0]))
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@override_settings(ECOMMERCE_API_URL=TEST_API_URL)
|
|
|
|
|
@@ -1255,7 +1259,7 @@ class TestCreateOrderEcommerceService(CheckoutTestMixin, ModuleStoreTestCase):
|
|
|
|
|
|
|
|
|
|
def _get_checkout_args(self, patched_create_order):
|
|
|
|
|
""" Assuming patched_create_order was called, return a mapping containing the call arguments."""
|
|
|
|
|
return dict(zip(('user', 'course_key', 'course_mode', 'processor'), patched_create_order.call_args[0]))
|
|
|
|
|
return dict(list(zip(('user', 'course_key', 'course_mode', 'processor'), patched_create_order.call_args[0])))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestCheckoutWithEcommerceService(ModuleStoreTestCase):
|
|
|
|
|
|