Commit 198d33dc by Awais Committed by Awais Qureshi

New AB Testing URL for checkout page.

ECOM-2866
parent 068b439a
...@@ -102,11 +102,17 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): ...@@ -102,11 +102,17 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
result = self.client.login(username=self.USERNAME, password=self.PASSWORD) result = self.client.login(username=self.USERNAME, password=self.PASSWORD)
self.assertTrue(result, msg="Could not log in") self.assertTrue(result, msg="Could not log in")
@ddt.data("verified", "professional") @ddt.data(
def test_start_flow_not_verified(self, course_mode): ("verified", "verify_student_start_flow"),
("professional", "verify_student_start_flow"),
("verified", "verify_student_begin_flow"),
("professional", "verify_student_begin_flow")
)
@ddt.unpack
def test_start_flow_not_verified(self, course_mode, payment_flow):
course = self._create_course(course_mode) course = self._create_course(course_mode)
self._enroll(course.id) self._enroll(course.id)
response = self._get_page('verify_student_start_flow', course.id) response = self._get_page(payment_flow, course.id)
self._assert_displayed_mode(response, course_mode) self._assert_displayed_mode(response, course_mode)
self._assert_steps_displayed( self._assert_steps_displayed(
response, response,
...@@ -120,11 +126,15 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): ...@@ -120,11 +126,15 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
]) ])
self._assert_upgrade_session_flag(False) self._assert_upgrade_session_flag(False)
@ddt.data("no-id-professional") @ddt.data(
def test_start_flow_with_no_id_professional(self, course_mode): ("no-id-professional", "verify_student_start_flow"),
("no-id-professional", "verify_student_begin_flow")
)
@ddt.unpack
def test_start_flow_with_no_id_professional(self, course_mode, payment_flow):
course = self._create_course(course_mode) course = self._create_course(course_mode)
self._enroll(course.id) self._enroll(course.id)
response = self._get_page('verify_student_start_flow', course.id) response = self._get_page(payment_flow, course.id)
self._assert_displayed_mode(response, course_mode) self._assert_displayed_mode(response, course_mode)
self._assert_steps_displayed( self._assert_steps_displayed(
response, response,
...@@ -134,12 +144,26 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): ...@@ -134,12 +144,26 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
self._assert_messaging(response, PayAndVerifyView.FIRST_TIME_VERIFY_MSG) self._assert_messaging(response, PayAndVerifyView.FIRST_TIME_VERIFY_MSG)
self._assert_requirements_displayed(response, []) self._assert_requirements_displayed(response, [])
@ddt.data("expired", "denied") def test_ab_testing_page(self):
def test_start_flow_expired_or_denied_verification(self, verification_status): course = self._create_course("verified")
self._enroll(course.id, "verified")
response = self._get_page("verify_student_begin_flow", course.id)
self._assert_displayed_mode(response, "verified")
self.assertContains(response, "Upgrade to a Verified Certificate")
self.assertContains(response, "Before you upgrade to a certificate track,")
self.assertContains(response, "To receive a certificate, you must also verify your identity")
self.assertContains(response, "You will use your webcam to take a picture of")
@ddt.data(
("expired", "verify_student_start_flow"),
("denied", "verify_student_begin_flow")
)
@ddt.unpack
def test_start_flow_expired_or_denied_verification(self, verification_status, payment_flow):
course = self._create_course("verified") course = self._create_course("verified")
self._enroll(course.id, "verified") self._enroll(course.id, "verified")
self._set_verification_status(verification_status) self._set_verification_status(verification_status)
response = self._get_page('verify_student_start_flow', course.id) response = self._get_page(payment_flow, course.id)
# Expect the same content as when the user has not verified # Expect the same content as when the user has not verified
self._assert_steps_displayed( self._assert_steps_displayed(
...@@ -154,18 +178,24 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): ...@@ -154,18 +178,24 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
]) ])
@ddt.data( @ddt.data(
("verified", "submitted"), ("verified", "submitted", "verify_student_start_flow"),
("verified", "approved"), ("verified", "approved", "verify_student_start_flow"),
("verified", "error"), ("verified", "error", "verify_student_start_flow"),
("professional", "submitted"), ("professional", "submitted", "verify_student_start_flow"),
("no-id-professional", None), ("no-id-professional", None, "verify_student_start_flow"),
("verified", "submitted", "verify_student_begin_flow"),
("verified", "approved", "verify_student_begin_flow"),
("verified", "error", "verify_student_begin_flow"),
("professional", "submitted", "verify_student_begin_flow"),
("no-id-professional", None, "verify_student_begin_flow"),
) )
@ddt.unpack @ddt.unpack
def test_start_flow_already_verified(self, course_mode, verification_status): def test_start_flow_already_verified(self, course_mode, verification_status, payment_flow):
course = self._create_course(course_mode) course = self._create_course(course_mode)
self._enroll(course.id) self._enroll(course.id)
self._set_verification_status(verification_status) self._set_verification_status(verification_status)
response = self._get_page('verify_student_start_flow', course.id) response = self._get_page(payment_flow, course.id)
self._assert_displayed_mode(response, course_mode) self._assert_displayed_mode(response, course_mode)
self._assert_steps_displayed( self._assert_steps_displayed(
response, response,
...@@ -175,11 +205,17 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): ...@@ -175,11 +205,17 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
self._assert_messaging(response, PayAndVerifyView.FIRST_TIME_VERIFY_MSG) self._assert_messaging(response, PayAndVerifyView.FIRST_TIME_VERIFY_MSG)
self._assert_requirements_displayed(response, []) self._assert_requirements_displayed(response, [])
@ddt.data("verified", "professional") @ddt.data(
def test_start_flow_already_paid(self, course_mode): ("verified", "verify_student_start_flow"),
("professional", "verify_student_start_flow"),
("verified", "verify_student_begin_flow"),
("professional", "verify_student_begin_flow")
)
@ddt.unpack
def test_start_flow_already_paid(self, course_mode, payment_flow):
course = self._create_course(course_mode) course = self._create_course(course_mode)
self._enroll(course.id, course_mode) self._enroll(course.id, course_mode)
response = self._get_page('verify_student_start_flow', course.id) response = self._get_page(payment_flow, course.id)
self._assert_displayed_mode(response, course_mode) self._assert_displayed_mode(response, course_mode)
self._assert_steps_displayed( self._assert_steps_displayed(
response, response,
...@@ -192,15 +228,16 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): ...@@ -192,15 +228,16 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
PayAndVerifyView.WEBCAM_REQ, PayAndVerifyView.WEBCAM_REQ,
]) ])
def test_start_flow_not_enrolled(self): @ddt.data("verify_student_start_flow", "verify_student_begin_flow")
def test_start_flow_not_enrolled(self, payment_flow):
course = self._create_course("verified") course = self._create_course("verified")
self._set_verification_status("submitted") self._set_verification_status("submitted")
response = self._get_page('verify_student_start_flow', course.id) response = self._get_page(payment_flow, course.id)
# This shouldn't happen if the student has been auto-enrolled, # This shouldn't happen if the student has been auto-enrolled,
# but if they somehow end up on this page without enrolling, # but if they somehow end up on this page without enrolling,
# treat them as if they need to pay # treat them as if they need to pay
response = self._get_page('verify_student_start_flow', course.id) response = self._get_page(payment_flow, course.id)
self._assert_steps_displayed( self._assert_steps_displayed(
response, response,
PayAndVerifyView.PAYMENT_STEPS, PayAndVerifyView.PAYMENT_STEPS,
...@@ -208,7 +245,8 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): ...@@ -208,7 +245,8 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
) )
self._assert_requirements_displayed(response, []) self._assert_requirements_displayed(response, [])
def test_start_flow_unenrolled(self): @ddt.data("verify_student_start_flow", "verify_student_begin_flow")
def test_start_flow_unenrolled(self, payment_flow):
course = self._create_course("verified") course = self._create_course("verified")
self._set_verification_status("submitted") self._set_verification_status("submitted")
self._enroll(course.id, "verified") self._enroll(course.id, "verified")
...@@ -216,7 +254,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): ...@@ -216,7 +254,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
# If unenrolled, treat them like they haven't paid at all # If unenrolled, treat them like they haven't paid at all
# (we assume that they've gotten a refund or didn't pay initially) # (we assume that they've gotten a refund or didn't pay initially)
response = self._get_page('verify_student_start_flow', course.id) response = self._get_page(payment_flow, course.id)
self._assert_steps_displayed( self._assert_steps_displayed(
response, response,
PayAndVerifyView.PAYMENT_STEPS, PayAndVerifyView.PAYMENT_STEPS,
...@@ -225,27 +263,31 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): ...@@ -225,27 +263,31 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
self._assert_requirements_displayed(response, []) self._assert_requirements_displayed(response, [])
@ddt.data( @ddt.data(
("verified", "submitted"), ("verified", "submitted", "verify_student_start_flow"),
("verified", "approved"), ("verified", "approved", "verify_student_start_flow"),
("professional", "submitted") ("professional", "submitted", "verify_student_start_flow"),
("verified", "submitted", "verify_student_begin_flow"),
("verified", "approved", "verify_student_begin_flow"),
("professional", "submitted", "verify_student_begin_flow")
) )
@ddt.unpack @ddt.unpack
def test_start_flow_already_verified_and_paid(self, course_mode, verification_status): def test_start_flow_already_verified_and_paid(self, course_mode, verification_status, payment_flow):
course = self._create_course(course_mode) course = self._create_course(course_mode)
self._enroll(course.id, course_mode) self._enroll(course.id, course_mode)
self._set_verification_status(verification_status) self._set_verification_status(verification_status)
response = self._get_page( response = self._get_page(
'verify_student_start_flow', payment_flow,
course.id, course.id,
expected_status_code=302 expected_status_code=302
) )
self._assert_redirects_to_dashboard(response) self._assert_redirects_to_dashboard(response)
@patch.dict(settings.FEATURES, {"IS_EDX_DOMAIN": True}) @patch.dict(settings.FEATURES, {"IS_EDX_DOMAIN": True})
def test_pay_and_verify_hides_header_nav(self): @ddt.data("verify_student_start_flow", "verify_student_begin_flow")
def test_pay_and_verify_hides_header_nav(self, payment_flow):
course = self._create_course("verified") course = self._create_course("verified")
self._enroll(course.id, "verified") self._enroll(course.id, "verified")
response = self._get_page('verify_student_start_flow', course.id) response = self._get_page(payment_flow, course.id)
# Verify that the header navigation links are hidden for the edx.org version # Verify that the header navigation links are hidden for the edx.org version
self.assertNotContains(response, "How it Works") self.assertNotContains(response, "How it Works")
...@@ -351,7 +393,8 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): ...@@ -351,7 +393,8 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
PayAndVerifyView.WEBCAM_REQ, PayAndVerifyView.WEBCAM_REQ,
]) ])
def test_payment_cannot_skip(self): @ddt.data("verify_student_start_flow", "verify_student_begin_flow")
def test_payment_cannot_skip(self, payment_flow):
""" """
Simple test to verify that certain steps cannot be skipped. This test sets up Simple test to verify that certain steps cannot be skipped. This test sets up
a scenario where the user should be on the MAKE_PAYMENT_STEP, but is trying to a scenario where the user should be on the MAKE_PAYMENT_STEP, but is trying to
...@@ -360,7 +403,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): ...@@ -360,7 +403,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
""" """
course = self._create_course("verified") course = self._create_course("verified")
response = self._get_page( response = self._get_page(
'verify_student_start_flow', payment_flow,
course.id, course.id,
skip_first_step=True skip_first_step=True
) )
...@@ -523,6 +566,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): ...@@ -523,6 +566,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
pages = [ pages = [
'verify_student_start_flow', 'verify_student_start_flow',
'verify_student_begin_flow',
'verify_student_verify_now', 'verify_student_verify_now',
'verify_student_upgrade_and_verify', 'verify_student_upgrade_and_verify',
] ]
...@@ -534,16 +578,25 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): ...@@ -534,16 +578,25 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
expected_status_code=404 expected_status_code=404
) )
@ddt.data([], ["no-id-professional", "professional"], ["honor", "audit"]) @ddt.data(
def test_no_id_professional_entry_point(self, modes_available): ([], "verify_student_start_flow"),
(["no-id-professional", "professional"], "verify_student_start_flow"),
(["honor", "audit"], "verify_student_start_flow"),
([], "verify_student_begin_flow"),
(["no-id-professional", "professional"], "verify_student_begin_flow"),
(["honor", "audit"], "verify_student_begin_flow"),
)
@ddt.unpack
def test_no_id_professional_entry_point(self, modes_available, payment_flow):
course = self._create_course(*modes_available) course = self._create_course(*modes_available)
if "no-id-professional" in modes_available or "professional" in modes_available: if "no-id-professional" in modes_available or "professional" in modes_available:
self._get_page("verify_student_start_flow", course.id, expected_status_code=200) self._get_page(payment_flow, course.id, expected_status_code=200)
else: else:
self._get_page("verify_student_start_flow", course.id, expected_status_code=404) self._get_page(payment_flow, course.id, expected_status_code=404)
@ddt.data( @ddt.data(
"verify_student_start_flow", "verify_student_start_flow",
"verify_student_begin_flow",
"verify_student_verify_now", "verify_student_verify_now",
"verify_student_upgrade_and_verify", "verify_student_upgrade_and_verify",
) )
...@@ -561,6 +614,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): ...@@ -561,6 +614,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
@ddt.data( @ddt.data(
"verify_student_start_flow", "verify_student_start_flow",
"verify_student_begin_flow",
"verify_student_verify_now", "verify_student_verify_now",
"verify_student_upgrade_and_verify", "verify_student_upgrade_and_verify",
) )
...@@ -572,11 +626,12 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): ...@@ -572,11 +626,12 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
expected_status_code=404 expected_status_code=404
) )
def test_account_not_active(self): @ddt.data("verify_student_start_flow", "verify_student_begin_flow")
def test_account_not_active(self, payment_flow):
self.user.is_active = False self.user.is_active = False
self.user.save() self.user.save()
course = self._create_course("verified") course = self._create_course("verified")
response = self._get_page('verify_student_start_flow', course.id) response = self._get_page(payment_flow, course.id)
self._assert_steps_displayed( self._assert_steps_displayed(
response, response,
PayAndVerifyView.PAYMENT_STEPS + PayAndVerifyView.VERIFICATION_STEPS, PayAndVerifyView.PAYMENT_STEPS + PayAndVerifyView.VERIFICATION_STEPS,
...@@ -588,32 +643,36 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): ...@@ -588,32 +643,36 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
PayAndVerifyView.WEBCAM_REQ, PayAndVerifyView.WEBCAM_REQ,
]) ])
def test_no_contribution(self): @ddt.data("verify_student_start_flow", "verify_student_begin_flow")
def test_no_contribution(self, payment_flow):
# Do NOT specify a contribution for the course in a session var. # Do NOT specify a contribution for the course in a session var.
course = self._create_course("verified") course = self._create_course("verified")
response = self._get_page("verify_student_start_flow", course.id) response = self._get_page(payment_flow, course.id)
self._assert_contribution_amount(response, "") self._assert_contribution_amount(response, "")
def test_contribution_other_course(self): @ddt.data("verify_student_start_flow", "verify_student_begin_flow")
def test_contribution_other_course(self, payment_flow):
# Specify a contribution amount for another course in the session # Specify a contribution amount for another course in the session
course = self._create_course("verified") course = self._create_course("verified")
other_course_id = CourseLocator(org="other", run="test", course="test") other_course_id = CourseLocator(org="other", run="test", course="test")
self._set_contribution("12.34", other_course_id) self._set_contribution("12.34", other_course_id)
# Expect that the contribution amount is NOT pre-filled, # Expect that the contribution amount is NOT pre-filled,
response = self._get_page("verify_student_start_flow", course.id) response = self._get_page(payment_flow, course.id)
self._assert_contribution_amount(response, "") self._assert_contribution_amount(response, "")
def test_contribution(self): @ddt.data("verify_student_start_flow", "verify_student_begin_flow")
def test_contribution(self, payment_flow):
# Specify a contribution amount for this course in the session # Specify a contribution amount for this course in the session
course = self._create_course("verified") course = self._create_course("verified")
self._set_contribution("12.34", course.id) self._set_contribution("12.34", course.id)
# Expect that the contribution amount is pre-filled, # Expect that the contribution amount is pre-filled,
response = self._get_page("verify_student_start_flow", course.id) response = self._get_page(payment_flow, course.id)
self._assert_contribution_amount(response, "12.34") self._assert_contribution_amount(response, "12.34")
def test_verification_deadline(self): @ddt.data("verify_student_start_flow", "verify_student_begin_flow")
def test_verification_deadline(self, payment_flow):
deadline = datetime(2999, 1, 2, tzinfo=pytz.UTC) deadline = datetime(2999, 1, 2, tzinfo=pytz.UTC)
course = self._create_course("verified") course = self._create_course("verified")
...@@ -625,7 +684,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): ...@@ -625,7 +684,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
self._set_deadlines(course.id, upgrade_deadline=deadline, verification_deadline=deadline) self._set_deadlines(course.id, upgrade_deadline=deadline, verification_deadline=deadline)
# Expect that the expiration date is set # Expect that the expiration date is set
response = self._get_page("verify_student_start_flow", course.id) response = self._get_page(payment_flow, course.id)
data = self._get_page_data(response) data = self._get_page_data(response)
self.assertEqual(data['verification_deadline'], "Jan 02, 2999 at 00:00 UTC") self.assertEqual(data['verification_deadline'], "Jan 02, 2999 at 00:00 UTC")
...@@ -661,7 +720,9 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): ...@@ -661,7 +720,9 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
# Try to pay or upgrade. # Try to pay or upgrade.
# We should get an error message since the deadline has passed. # We should get an error message since the deadline has passed.
for page_name in ["verify_student_start_flow", "verify_student_upgrade_and_verify"]: for page_name in ["verify_student_start_flow",
"verify_student_begin_flow",
"verify_student_upgrade_and_verify"]:
response = self._get_page(page_name, course.id) response = self._get_page(page_name, course.id)
self.assertContains(response, "Upgrade Deadline Has Passed") self.assertContains(response, "Upgrade Deadline Has Passed")
...@@ -708,18 +769,20 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): ...@@ -708,18 +769,20 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
self.assertContains(response, "Jan 02, 1999 at 00:00 UTC") self.assertContains(response, "Jan 02, 1999 at 00:00 UTC")
@mock.patch.dict(settings.FEATURES, {'EMBARGO': True}) @mock.patch.dict(settings.FEATURES, {'EMBARGO': True})
def test_embargo_restrict(self): @ddt.data("verify_student_start_flow", "verify_student_begin_flow")
def test_embargo_restrict(self, payment_flow):
course = self._create_course("verified") course = self._create_course("verified")
with restrict_course(course.id) as redirect_url: with restrict_course(course.id) as redirect_url:
# Simulate that we're embargoed from accessing this # Simulate that we're embargoed from accessing this
# course based on our IP address. # course based on our IP address.
response = self._get_page('verify_student_start_flow', course.id, expected_status_code=302) response = self._get_page(payment_flow, course.id, expected_status_code=302)
self.assertRedirects(response, redirect_url) self.assertRedirects(response, redirect_url)
@mock.patch.dict(settings.FEATURES, {'EMBARGO': True}) @mock.patch.dict(settings.FEATURES, {'EMBARGO': True})
def test_embargo_allow(self): @ddt.data("verify_student_start_flow", "verify_student_begin_flow")
def test_embargo_allow(self, payment_flow):
course = self._create_course("verified") course = self._create_course("verified")
self._get_page('verify_student_start_flow', course.id) self._get_page(payment_flow, course.id)
def _create_course(self, *course_modes, **kwargs): def _create_course(self, *course_modes, **kwargs):
"""Create a new course with the specified course modes. """ """Create a new course with the specified course modes. """
...@@ -918,7 +981,8 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): ...@@ -918,7 +981,8 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
url = reverse('verify_student_upgrade_and_verify', kwargs={'course_id': unicode(course_id)}) url = reverse('verify_student_upgrade_and_verify', kwargs={'course_id': unicode(course_id)})
self.assertRedirects(response, url) self.assertRedirects(response, url)
def test_course_upgrade_page_with_unicode_and_special_values_in_display_name(self): @ddt.data("verify_student_start_flow", "verify_student_begin_flow")
def test_course_upgrade_page_with_unicode_and_special_values_in_display_name(self, payment_flow):
"""Check the course information on the page. """ """Check the course information on the page. """
mode_display_name = u"Introduction à l'astrophysique" mode_display_name = u"Introduction à l'astrophysique"
course = CourseFactory.create(display_name=mode_display_name) course = CourseFactory.create(display_name=mode_display_name)
...@@ -932,13 +996,14 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): ...@@ -932,13 +996,14 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
) )
self._enroll(course.id) self._enroll(course.id)
response_dict = self._get_page_data(self._get_page('verify_student_start_flow', course.id)) response_dict = self._get_page_data(self._get_page(payment_flow, course.id))
self.assertEqual(response_dict['course_name'], mode_display_name) self.assertEqual(response_dict['course_name'], mode_display_name)
@httpretty.activate @httpretty.activate
@override_settings(ECOMMERCE_API_URL=TEST_API_URL, ECOMMERCE_API_SIGNING_KEY=TEST_API_SIGNING_KEY) @override_settings(ECOMMERCE_API_URL=TEST_API_URL, ECOMMERCE_API_SIGNING_KEY=TEST_API_SIGNING_KEY)
def test_processors_api(self): @ddt.data("verify_student_start_flow", "verify_student_begin_flow")
def test_processors_api(self, payment_flow):
""" """
Check that when working with a product being processed by the Check that when working with a product being processed by the
ecommerce api, we correctly call to that api for the list of ecommerce api, we correctly call to that api for the list of
...@@ -957,7 +1022,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin): ...@@ -957,7 +1022,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
content_type="application/json", content_type="application/json",
) )
# make the server request # make the server request
response = self._get_page('verify_student_start_flow', course.id) response = self._get_page(payment_flow, course.id)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
# ensure the mock api call was made. NOTE: the following line # ensure the mock api call was made. NOTE: the following line
......
...@@ -23,6 +23,16 @@ urlpatterns = patterns( ...@@ -23,6 +23,16 @@ urlpatterns = patterns(
} }
), ),
# This is for A/B testing.
url(
r'^begin-flow/{course}/$'.format(course=settings.COURSE_ID_PATTERN),
views.PayAndVerifyView.as_view(),
name="verify_student_begin_flow",
kwargs={
'message': views.PayAndVerifyView.FIRST_TIME_VERIFY_MSG
}
),
# The user is enrolled in a non-paid mode and wants to upgrade. # The user is enrolled in a non-paid mode and wants to upgrade.
# This is the same as the "start verification" flow, # This is the same as the "start verification" flow,
# except with slight messaging changes. # except with slight messaging changes.
......
...@@ -423,7 +423,9 @@ class PayAndVerifyView(View): ...@@ -423,7 +423,9 @@ class PayAndVerifyView(View):
'verification_good_until': verification_good_until, 'verification_good_until': verification_good_until,
'capture_sound': staticfiles_storage.url("audio/camera_capture.wav"), 'capture_sound': staticfiles_storage.url("audio/camera_capture.wav"),
'nav_hidden': True, 'nav_hidden': True,
'is_ab_testing': 'begin-flow' in request.path,
} }
return render_to_response("verify_student/pay_and_verify.html", context) return render_to_response("verify_student/pay_and_verify.html", context)
def _redirect_if_necessary( def _redirect_if_necessary(
......
...@@ -674,6 +674,7 @@ ...@@ -674,6 +674,7 @@
'lms/include/js/spec/verify_student/image_input_spec.js', 'lms/include/js/spec/verify_student/image_input_spec.js',
'lms/include/js/spec/verify_student/review_photos_step_view_spec.js', 'lms/include/js/spec/verify_student/review_photos_step_view_spec.js',
'lms/include/js/spec/verify_student/make_payment_step_view_spec.js', 'lms/include/js/spec/verify_student/make_payment_step_view_spec.js',
'lms/include/js/spec/verify_student/make_payment_step_view_ab_testing_spec.js',
'lms/include/js/spec/edxnotes/utils/logger_spec.js', 'lms/include/js/spec/edxnotes/utils/logger_spec.js',
'lms/include/js/spec/edxnotes/views/notes_factory_spec.js', 'lms/include/js/spec/edxnotes/views/notes_factory_spec.js',
'lms/include/js/spec/edxnotes/views/shim_spec.js', 'lms/include/js/spec/edxnotes/views/shim_spec.js',
......
define([
'jquery',
'underscore',
'backbone',
'common/js/spec_helpers/ajax_helpers',
'common/js/spec_helpers/template_helpers',
'js/verify_student/views/make_payment_step_view'
],
function( $, _, Backbone, AjaxHelpers, TemplateHelpers, MakePaymentStepView ) {
'use strict';
var checkPaymentButtons,
expectPaymentSubmitted,
goToPayment,
expectPaymentDisabledBecauseInactive,
expectPaymentButtonEnabled,
expectPriceSelected,
createView,
SERVER_ERROR_MSG = 'An error occurred!';
describe( 'edx.verify_student.MakePaymentStepView', function() {
var STEP_DATA = {
minPrice: '12',
currency: 'usd',
processors: ['test-payment-processor'],
courseKey: 'edx/test/test',
courseModeSlug: 'verified',
isABTesting: true
};
createView = function( stepDataOverrides ) {
var view = new MakePaymentStepView({
el: $( '#current-step-container' ),
stepData: _.extend( _.clone( STEP_DATA ), stepDataOverrides ),
errorModel: new ( Backbone.Model.extend({}) )()
}).render();
// Stub the payment form submission
spyOn( view, 'submitForm' ).andCallFake( function() {} );
return view;
};
expectPriceSelected = function( price ) {
var sel = $( 'input[name="contribution"]' );
// check that contribution value is same as price given
expect( sel.length ).toEqual(1);
expect( sel.val() ).toEqual(price);
};
expectPaymentButtonEnabled = function( isEnabled ) {
var el = $( '.payment-button'),
appearsDisabled = el.hasClass( 'is-disabled' ),
isDisabled = el.prop( 'disabled' );
expect( appearsDisabled ).not.toEqual( isEnabled );
expect( isDisabled ).not.toEqual( isEnabled );
};
expectPaymentDisabledBecauseInactive = function() {
var payButton = $( '.payment-button' );
// Payment button should be hidden
expect( payButton.length ).toEqual(0);
};
goToPayment = function( requests, kwargs ) {
var params = {
contribution: kwargs.amount || '',
course_id: kwargs.courseId || '',
processor: kwargs.processor || '',
sku: kwargs.sku || ''
};
// Click the "go to payment" button
$( '.payment-button' ).click();
// Verify that the request was made to the server
AjaxHelpers.expectPostRequest(
requests, '/verify_student/create_order/', $.param( params )
);
// Simulate the server response
if ( kwargs.succeeds ) {
// TODO put fixture responses in the right place
AjaxHelpers.respondWithJson(
requests, {payment_page_url: 'http://payment-page-url/', payment_form_data: {foo: 'bar'}}
);
} else {
AjaxHelpers.respondWithTextError( requests, 400, SERVER_ERROR_MSG);
}
};
expectPaymentSubmitted = function( view, params ) {
var form;
expect(view.submitForm).toHaveBeenCalled();
form = view.submitForm.mostRecentCall.args[0];
expect(form.serialize()).toEqual($.param(params));
expect(form.attr('method')).toEqual('POST');
expect(form.attr('action')).toEqual('http://payment-page-url/');
};
checkPaymentButtons = function( requests, buttons ) {
var $el = $( '.payment-button' );
expect($el.length).toEqual(_.size(buttons));
_.each(buttons, function( expectedText, expectedId ) {
var buttonEl = $( '#' + expectedId),
request;
buttonEl.removeAttr('disabled');
expect( buttonEl.length ).toEqual( 1 );
expect( buttonEl[0] ).toHaveClass( 'payment-button' );
expect( buttonEl[0] ).toHaveText( expectedText );
expect( buttonEl[0] ).toHaveClass( 'action-primary-blue' );
buttonEl[0].click();
expect( buttonEl[0] ).toHaveClass( 'is-selected' );
expectPaymentButtonEnabled( false );
request = AjaxHelpers.currentRequest(requests);
expect(request.requestBody.split('&')).toContain('processor=' + expectedId);
AjaxHelpers.respondWithJson(requests, {});
});
};
beforeEach(function() {
window.analytics = jasmine.createSpyObj('analytics', ['track', 'page', 'trackLink']);
setFixtures( '<div id="current-step-container"></div>' );
TemplateHelpers.installTemplate( 'templates/verify_student/make_payment_step_ab_testing' );
});
it( 'A/B Testing: check Initialize method with AB testing enable ', function() {
var view = createView();
expect( view.templateName ).toEqual('make_payment_step_ab_testing');
expect( view.btnClass ).toEqual('action-primary-blue');
});
it( 'shows users only minimum price', function() {
var view = createView(),
requests = AjaxHelpers.requests(this);
expectPriceSelected( STEP_DATA.minPrice );
expectPaymentButtonEnabled( true );
goToPayment( requests, {
amount: STEP_DATA.minPrice,
courseId: STEP_DATA.courseKey,
processor: STEP_DATA.processors[0],
succeeds: true
});
expectPaymentSubmitted( view, {foo: 'bar'} );
});
it( 'A/B Testing: provides working payment buttons for a single processor', function() {
createView({processors: ['cybersource']});
checkPaymentButtons( AjaxHelpers.requests(this), {cybersource: 'Checkout'});
});
it( 'A/B Testing: provides working payment buttons for multiple processors', function() {
createView({processors: ['cybersource', 'paypal', 'other']});
checkPaymentButtons( AjaxHelpers.requests(this), {
cybersource: 'Checkout',
paypal: 'Checkout with PayPal',
other: 'Checkout with other'
});
});
it( 'A/B Testing: by default minimum price is selected if no suggested prices are given', function() {
var view = createView(),
requests = AjaxHelpers.requests( this );
expectPriceSelected( STEP_DATA.minPrice);
expectPaymentButtonEnabled( true );
goToPayment( requests, {
amount: STEP_DATA.minPrice,
courseId: STEP_DATA.courseKey,
processor: STEP_DATA.processors[0],
succeeds: true
});
expectPaymentSubmitted( view, {foo: 'bar'} );
});
it( 'A/B Testing: min price is always selected even if contribution amount is provided', function() {
// Pre-select a price NOT in the suggestions
createView({
contributionAmount: '99.99'
});
// Expect that the price is filled in
expectPriceSelected( STEP_DATA.minPrice );
});
it( 'A/B Testing: disables payment for inactive users', function() {
createView({ isActive: false });
expectPaymentDisabledBecauseInactive();
});
it( 'A/B Testing: displays an error if the order could not be created', function() {
var requests = AjaxHelpers.requests( this ),
view = createView();
goToPayment( requests, {
amount: STEP_DATA.minPrice,
courseId: STEP_DATA.courseKey,
processor: STEP_DATA.processors[0],
succeeds: false
});
// Expect that an error is displayed
expect( view.errorModel.get('shown') ).toBe( true );
expect( view.errorModel.get('errorTitle') ).toEqual( 'Could not submit order' );
expect( view.errorModel.get('errorMsg') ).toEqual( SERVER_ERROR_MSG );
// Expect that the payment button is re-enabled
expectPaymentButtonEnabled( true );
});
});
}
);
...@@ -108,6 +108,7 @@ define([ ...@@ -108,6 +108,7 @@ define([
buttonEl.removeAttr('disabled'); buttonEl.removeAttr('disabled');
expect( buttonEl.length ).toEqual( 1 ); expect( buttonEl.length ).toEqual( 1 );
expect( buttonEl[0] ).toHaveClass( 'payment-button' ); expect( buttonEl[0] ).toHaveClass( 'payment-button' );
expect( buttonEl[0] ).toHaveClass( 'action-primary' );
expect( buttonEl[0] ).toHaveText( expectedText ); expect( buttonEl[0] ).toHaveText( expectedText );
buttonEl[0].click(); buttonEl[0].click();
...@@ -216,6 +217,12 @@ define([ ...@@ -216,6 +217,12 @@ define([
'Try the transaction again in a few minutes.' 'Try the transaction again in a few minutes.'
); );
}); });
it( 'check Initialize method without AB testing ', function() {
var view = createView();
expect( view.templateName ).toEqual('make_payment_step');
expect( view.btnClass ).toEqual('action-primary');
});
}); });
} }
); );
...@@ -66,7 +66,8 @@ var edx = edx || {}; ...@@ -66,7 +66,8 @@ var edx = edx || {};
verificationDeadline: el.data('verification-deadline'), verificationDeadline: el.data('verification-deadline'),
courseModeSlug: el.data('course-mode-slug'), courseModeSlug: el.data('course-mode-slug'),
alreadyVerified: el.data('already-verified'), alreadyVerified: el.data('already-verified'),
verificationGoodUntil: el.data('verification-good-until') verificationGoodUntil: el.data('verification-good-until'),
isABTesting: el.data('is-ab-testing')
}, },
'payment-confirmation-step': { 'payment-confirmation-step': {
courseKey: el.data('course-key'), courseKey: el.data('course-key'),
......
...@@ -11,6 +11,15 @@ var edx = edx || {}; ...@@ -11,6 +11,15 @@ var edx = edx || {};
edx.verify_student.MakePaymentStepView = edx.verify_student.StepView.extend({ edx.verify_student.MakePaymentStepView = edx.verify_student.StepView.extend({
templateName: "make_payment_step", templateName: "make_payment_step",
btnClass: 'action-primary',
initialize: function( obj ) {
_.extend( this, obj );
if (this.templateContext().isABTesting) {
this.templateName = 'make_payment_step_ab_testing';
this.btnClass = 'action-primary-blue';
}
},
defaultContext: function() { defaultContext: function() {
return { return {
...@@ -27,7 +36,8 @@ var edx = edx || {}; ...@@ -27,7 +36,8 @@ var edx = edx || {};
platformName: '', platformName: '',
alreadyVerified: false, alreadyVerified: false,
courseModeSlug: 'audit', courseModeSlug: 'audit',
verificationGoodUntil: '' verificationGoodUntil: '',
isABTesting: false
}; };
}, },
...@@ -61,8 +71,8 @@ var edx = edx || {}; ...@@ -61,8 +71,8 @@ var edx = edx || {};
_getPaymentButtonHtml: function(processorName) { _getPaymentButtonHtml: function(processorName) {
var self = this; var self = this;
return _.template( return _.template(
'<button class="next action-primary payment-button" id="<%- name %>" ><%- text %></button> ' '<button class="next <%- btnClass %> payment-button" id="<%- name %>" ><%- text %></button> '
)({name: processorName, text: self._getPaymentButtonText(processorName)}); )({name: processorName, text: self._getPaymentButtonText(processorName), btnClass: this.btnClass});
}, },
postRender: function() { postRender: function() {
......
...@@ -175,6 +175,14 @@ ...@@ -175,6 +175,14 @@
color: $white !important; color: $white !important;
} }
// elements - controls
.action-primary-blue {
@extend %btn-primary-blue;
// needed for override due to .register a:link styling
border: 0 !important;
color: $white !important;
}
.action-confirm { .action-confirm {
@extend %btn-verify-primary; @extend %btn-verify-primary;
// needed for override due to .register a:link styling // needed for override due to .register a:link styling
...@@ -821,6 +829,109 @@ ...@@ -821,6 +829,109 @@
// indiv slides - review // indiv slides - review
#wrapper-review { #wrapper-review {
color: $black;
.page-title {
@extend %t-strong;
border-bottom: 2px solid $m-gray-d3;
padding-bottom: ($baseline*0.75);
margin-bottom: $baseline;
text-transform: inherit;
}
.review {
.certificate {
@include font-size(18);
background-repeat: no-repeat;
padding-left: ($baseline*2.5);
overflow: hidden;
min-height: 32px;
p {
@include line-height(22);
@extend %t-strong;
margin-top: 0;
color: $black;
}
.purchase {
@include float(right);
@include margin-left($baseline*0.75);
text-align: right;
.product-info {
@include font-size(22);
@extend %t-strong;
color: $blue;
}
}
&.verified_icon {
background-image: url('#{$static-path}/images/icon-sm-verified.png');
}
&.no-id-professional_icon,
&.professional_icon {
background-image: url('#{$static-path}/images/icon-sm-professional.png');
}
}
.payment-buttons {
overflow: auto;
padding-bottom: ($baseline/4);
margin: {
top: ($baseline / 2);
bottom: ($baseline * 0.75);
};
.payment-button {
padding: ($baseline*0.4) $baseline;
min-width: 200px;
}
.action-primary-blue {
&.is-selected {
background: $blue !important;
}
}
}
.border-gray {
border-bottom: 2px solid $gray;
margin: ($baseline*1.12) 0;
}
}
.container {
padding: ($baseline*0.75) 0;
p {
@include line-height(22);
color: $black;
}
.photo-requirement {
@include font-size(12);
position: relative;
padding-left: ($baseline*2);
margin-top: ($baseline*0.75);
background-repeat: no-repeat;
background-position: left top;
.fa {
position: absolute;
left:0;
color: $mediumGrey;
}
h6 {
font-weight: bold;
color: $extraDarkGrey;
}
}
}
.review-task { .review-task {
margin-bottom: ($baseline*1.5); margin-bottom: ($baseline*1.5);
......
<div id="wrapper-review" tab-index="0" class="wrapper-view make-payment-step">
<div class="review view">
<% if (!isActive ) { %>
<h2 class="page-title">
<%- gettext("Account Not Activated")%>
</h2>
<% } else if ( !upgrade ) { %>
<h2 class="page-title">
<%= _.sprintf(
gettext( "You are enrolling in %(courseName)s"),
{ courseName: '<span class="course-title">' + courseName + '</span>' }
) %>
</h2>
<% } else { %>
<h2 class="page-title">
<%= _.sprintf(
gettext( "Upgrade to a Verified Certificate for %(courseName)s"),
{ courseName: '<span class="course-title">' + courseName + '</span>' }
) %>
</h2>
<% } %>
<% if ( !isActive ) { %>
<p>
<%- gettext("Before you upgrade to a certificate track, you must activate your account.") %>
<%- gettext("Check your email for an activation message.") %>
</p>
<% } else { %>
<div class="certificate <%- courseModeSlug %>_icon">
<div class="purchase">
<p class="product-info"><span class="product-name"></span> <%- gettext( "Total" ) %>: <span class="price">$<%- minPrice %> USD</span></p>
</div>
<p>
<% if ( courseModeSlug === 'no-id-professional' || courseModeSlug === 'professional') { %>
<%= _.sprintf(
gettext( "Professional Certificate for %(courseName)s"),{ courseName: courseName }
)%>
<% } else { %>
<%= _.sprintf(
gettext( "Verified Certificate for %(courseName)s"),{ courseName: courseName }
)%>
<% } %>
</p>
</div>
<% } %>
<% if ( isActive ) { %>
<div class="payment-buttons is-ready center">
<input type="hidden" name="contribution" value="<%- minPrice %>" />
<input type="hidden" name="sku" value="<%- sku %>" />
<div class="pay-options">
<%
// payment buttons will go here
%>
</div>
</div>
<div class="border-gray"></div>
<% } %>
</div>
<% if ( isActive ) { %>
<div class="container">
<% if ( _.some( requirements, function( isVisible ) { return isVisible; } ) ) { %>
<p>
<% if ( verificationDeadline ) { %>
<%- _.sprintf(
gettext( "To receive a certificate, you must also verify your identity before %(date)s." ),
{ date: verificationDeadline }
) %>
<% } else { %>
<%- gettext( "To receive a certificate, you must also verify your identity." ) %>
<% } %>
<%- gettext("To verify your identity, you need a webcam and a government-issued photo ID.") %>
</p>
<% if ( requirements['photo-id-required'] ) { %>
<div class="photo-requirement user_icon">
<i class="fa fa-user fa-2x" aria-hidden="true"></i>
<h6>
<%- gettext("Photo ID") %>
</h6>
<p>
<%- gettext("Your ID must be a government-issued photo ID that clearly shows your face.") %>
</p>
</div>
<% } %>
<% if ( requirements['webcam-required'] ) { %>
<div class="photo-requirement cam_icon">
<i class="fa fa-video-camera fa-2x" aria-hidden="true"></i>
<h6>
<%- gettext("Webcam") %>
</h6>
<p>
<%- gettext("You will use your webcam to take a picture of your face and of your government-issued photo ID.") %>
</p>
</div>
<% } %>
<% } %>
</div>
<% } %>
<form id="payment-processor-form"></form>
</div>
...@@ -24,9 +24,15 @@ from lms.djangoapps.verify_student.views import PayAndVerifyView ...@@ -24,9 +24,15 @@ from lms.djangoapps.verify_student.views import PayAndVerifyView
<% <%
template_names = ( template_names = (
["webcam_photo", "image_input", "error"] + ["webcam_photo", "image_input", "error"] +
["intro_step", "make_payment_step", "payment_confirmation_step"] + ["intro_step", "payment_confirmation_step"] +
["face_photo_step", "id_photo_step", "review_photos_step", "enrollment_confirmation_step"] ["face_photo_step", "id_photo_step", "review_photos_step", "enrollment_confirmation_step"]
) )
if not is_ab_testing:
template_names.append("make_payment_step")
else:
template_names.append("make_payment_step_ab_testing")
%> %>
% for template_name in template_names: % for template_name in template_names:
<script type="text/template" id="${template_name}-tpl"> <script type="text/template" id="${template_name}-tpl">
...@@ -76,6 +82,7 @@ from lms.djangoapps.verify_student.views import PayAndVerifyView ...@@ -76,6 +82,7 @@ from lms.djangoapps.verify_student.views import PayAndVerifyView
data-already-verified='${already_verified}' data-already-verified='${already_verified}'
data-verification-good-until='${verification_good_until}' data-verification-good-until='${verification_good_until}'
data-capture-sound='${capture_sound}' data-capture-sound='${capture_sound}'
data-is-ab-testing='${json.dumps(is_ab_testing)}'
## If we reached the verification flow from an in-course checkpoint, ## If we reached the verification flow from an in-course checkpoint,
## then pass the checkpoint location in so that we can associate ## then pass the checkpoint location in so that we can associate
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment