Add upgrade section to program detail course cards.
Displays a message and link when a user is enrolled in a program's course run but must upgrade in order to be eligible for the program certificate. ECOM-4220.
This commit is contained in:
@@ -10,6 +10,7 @@ from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.utils import timezone
|
||||
import httpretty
|
||||
import mock
|
||||
@@ -18,6 +19,7 @@ from edx_oauth2_provider.tests.factories import ClientFactory
|
||||
from provider.constants import CONFIDENTIAL
|
||||
|
||||
from lms.djangoapps.certificates.api import MODES
|
||||
from lms.djangoapps.commerce.tests.test_utils import update_commerce_config
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.djangoapps.credentials.tests import factories as credentials_factories
|
||||
from openedx.core.djangoapps.credentials.tests.mixins import CredentialsApiConfigMixin, CredentialsDataMixin
|
||||
@@ -34,6 +36,7 @@ from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
UTILS_MODULE = 'openedx.core.djangoapps.programs.utils'
|
||||
CERTIFICATES_API_MODULE = 'lms.djangoapps.certificates.api'
|
||||
ECOMMERCE_URL_ROOT = 'http://example-ecommerce.com'
|
||||
|
||||
|
||||
@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
@@ -672,11 +675,14 @@ class TestProgramProgressMeter(ProgramsApiConfigMixin, TestCase):
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@override_settings(ECOMMERCE_PUBLIC_URL_ROOT=ECOMMERCE_URL_ROOT)
|
||||
@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
|
||||
"""Tests of the utility function used to supplement program data."""
|
||||
password = 'test'
|
||||
maxDiff = None
|
||||
sku = 'abc123'
|
||||
password = 'test'
|
||||
checkout_path = '/basket'
|
||||
|
||||
def setUp(self):
|
||||
super(TestSupplementProgramData, self).setUp()
|
||||
@@ -704,15 +710,18 @@ class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
|
||||
course_overview = CourseOverview.get_from_id(self.course.id) # pylint: disable=no-member
|
||||
run_mode = dict(
|
||||
factories.RunMode(
|
||||
certificate_url=None,
|
||||
course_image_url=course_overview.course_image_url,
|
||||
course_key=unicode(self.course.id), # pylint: disable=no-member
|
||||
course_url=reverse('course_root', args=[self.course.id]), # pylint: disable=no-member
|
||||
course_image_url=course_overview.course_image_url,
|
||||
start_date=strftime_localized(self.course.start, 'SHORT_DATE'),
|
||||
end_date=strftime_localized(self.course.end, 'SHORT_DATE'),
|
||||
enrollment_open_date=None,
|
||||
is_course_ended=self.course.end < timezone.now(),
|
||||
is_enrolled=False,
|
||||
is_enrollment_open=True,
|
||||
marketing_url='',
|
||||
marketing_url=None,
|
||||
start_date=strftime_localized(self.course.start, 'SHORT_DATE'),
|
||||
upgrade_url=None,
|
||||
),
|
||||
**kwargs
|
||||
)
|
||||
@@ -722,19 +731,72 @@ class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
|
||||
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test_student_enrollment_status(self, is_enrolled):
|
||||
"""Verify that program data is supplemented correctly."""
|
||||
@ddt.data(
|
||||
(False, None, False),
|
||||
(True, MODES.audit, True),
|
||||
(True, MODES.verified, False),
|
||||
)
|
||||
@ddt.unpack
|
||||
@mock.patch(UTILS_MODULE + '.CourseMode.mode_for_course')
|
||||
def test_student_enrollment_status(self, is_enrolled, enrolled_mode, is_upgrade_required, mock_get_mode):
|
||||
"""Verify that program data is supplemented with the student's enrollment status."""
|
||||
expected_upgrade_url = '{root}/{path}?sku={sku}'.format(
|
||||
root=ECOMMERCE_URL_ROOT,
|
||||
path=self.checkout_path.strip('/'),
|
||||
sku=self.sku,
|
||||
)
|
||||
|
||||
update_commerce_config(enabled=True, checkout_page=self.checkout_path)
|
||||
|
||||
mock_mode = mock.Mock()
|
||||
mock_mode.sku = self.sku
|
||||
mock_get_mode.return_value = mock_mode
|
||||
|
||||
if is_enrolled:
|
||||
CourseEnrollmentFactory(user=self.user, course_id=self.course.id) # pylint: disable=no-member
|
||||
CourseEnrollmentFactory(user=self.user, course_id=self.course.id, mode=enrolled_mode) # pylint: disable=no-member
|
||||
|
||||
data = utils.supplement_program_data(self.program, self.user)
|
||||
|
||||
self._assert_supplemented(data, is_enrolled=is_enrolled)
|
||||
self._assert_supplemented(
|
||||
data,
|
||||
is_enrolled=is_enrolled,
|
||||
upgrade_url=expected_upgrade_url if is_upgrade_required else None
|
||||
)
|
||||
|
||||
@ddt.data(MODES.audit, MODES.verified)
|
||||
def test_inactive_enrollment_no_upgrade(self, enrolled_mode):
|
||||
"""Verify that a student with an inactive enrollment isn't encouraged to upgrade."""
|
||||
update_commerce_config(enabled=True, checkout_page=self.checkout_path)
|
||||
|
||||
CourseEnrollmentFactory(
|
||||
user=self.user,
|
||||
course_id=self.course.id, # pylint: disable=no-member
|
||||
mode=enrolled_mode,
|
||||
is_active=False,
|
||||
)
|
||||
|
||||
data = utils.supplement_program_data(self.program, self.user)
|
||||
|
||||
self._assert_supplemented(data)
|
||||
|
||||
@mock.patch(UTILS_MODULE + '.CourseMode.mode_for_course')
|
||||
def test_ecommerce_disabled(self, mock_get_mode):
|
||||
"""Verify that the utility can operate when the ecommerce service is disabled."""
|
||||
update_commerce_config(enabled=False, checkout_page=self.checkout_path)
|
||||
|
||||
mock_mode = mock.Mock()
|
||||
mock_mode.sku = self.sku
|
||||
mock_get_mode.return_value = mock_mode
|
||||
|
||||
CourseEnrollmentFactory(user=self.user, course_id=self.course.id, mode=MODES.audit) # pylint: disable=no-member
|
||||
|
||||
data = utils.supplement_program_data(self.program, self.user)
|
||||
|
||||
self._assert_supplemented(data, is_enrolled=True, upgrade_url=None)
|
||||
|
||||
@ddt.data(
|
||||
[1, 1, False],
|
||||
[1, -1, True],
|
||||
(1, 1, False),
|
||||
(1, -1, True),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_course_enrollment_status(self, start_offset, end_offset, is_enrollment_open):
|
||||
@@ -746,13 +808,15 @@ class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
|
||||
data = utils.supplement_program_data(self.program, self.user)
|
||||
|
||||
if is_enrollment_open:
|
||||
self._assert_supplemented(data, is_enrollment_open=is_enrollment_open)
|
||||
enrollment_open_date = None
|
||||
else:
|
||||
self._assert_supplemented(
|
||||
data,
|
||||
is_enrollment_open=is_enrollment_open,
|
||||
enrollment_open_date=strftime_localized(self.course.enrollment_start, 'SHORT_DATE')
|
||||
)
|
||||
enrollment_open_date = strftime_localized(self.course.enrollment_start, 'SHORT_DATE')
|
||||
|
||||
self._assert_supplemented(
|
||||
data,
|
||||
is_enrollment_open=is_enrollment_open,
|
||||
enrollment_open_date=enrollment_open_date,
|
||||
)
|
||||
|
||||
@ddt.data(True, False)
|
||||
@mock.patch(UTILS_MODULE + '.certificate_api.certificate_downloadable_status')
|
||||
@@ -765,11 +829,21 @@ class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
|
||||
|
||||
data = utils.supplement_program_data(self.program, self.user)
|
||||
|
||||
if is_uuid_available:
|
||||
expected_url = reverse('certificates:render_cert_by_uuid', kwargs={'certificate_uuid': test_uuid})
|
||||
self._assert_supplemented(data, certificate_url=expected_url)
|
||||
else:
|
||||
self._assert_supplemented(data)
|
||||
expected_url = reverse(
|
||||
'certificates:render_cert_by_uuid',
|
||||
kwargs={'certificate_uuid': test_uuid}
|
||||
) if is_uuid_available else None
|
||||
|
||||
self._assert_supplemented(data, certificate_url=expected_url)
|
||||
|
||||
@ddt.data(-1, 0, 1)
|
||||
def test_course_course_ended(self, days_offset):
|
||||
self.course.end = timezone.now() + datetime.timedelta(days=days_offset)
|
||||
self.course = self.update_course(self.course, self.user.id) # pylint: disable=no-member
|
||||
|
||||
data = utils.supplement_program_data(self.program, self.user)
|
||||
|
||||
self._assert_supplemented(data)
|
||||
|
||||
@mock.patch(UTILS_MODULE + '.get_organization_by_short_name')
|
||||
def test_organization_logo_exists(self, mock_get_organization_by_short_name):
|
||||
@@ -780,6 +854,7 @@ class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
|
||||
mock_get_organization_by_short_name.return_value = {
|
||||
'logo': mock_image
|
||||
}
|
||||
|
||||
data = utils.supplement_program_data(self.program, self.user)
|
||||
self.assertEqual(data['organizations'][0].get('img'), mock_logo_url)
|
||||
|
||||
@@ -799,11 +874,3 @@ class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
|
||||
mock_get_organization_by_short_name.return_value = {'logo': None}
|
||||
data = utils.supplement_program_data(self.program, self.user)
|
||||
self.assertEqual(data['organizations'][0].get('img'), None)
|
||||
|
||||
@ddt.data(-1, 0, 1)
|
||||
def test_course_course_ended(self, days_offset):
|
||||
self.course.end = timezone.now() + datetime.timedelta(days=days_offset)
|
||||
self.course = self.update_course(self.course, self.user.id) # pylint: disable=no-member
|
||||
data = utils.supplement_program_data(self.program, self.user)
|
||||
|
||||
self._assert_supplemented(data)
|
||||
|
||||
Reference in New Issue
Block a user