Merge pull request #15400 from open-craft/haikuginger/enterprise_audit_enrollment
[ENT-457] Create EnterpriseCourseEnrollment when enrolling via Track Selection page
This commit is contained in:
@@ -236,6 +236,52 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
|
||||
self.assertContains(response, 'Pursue a Verified Certificate')
|
||||
self.assertContains(response, 'Audit This Course')
|
||||
|
||||
@httpretty.activate
|
||||
@patch('course_modes.views.get_enterprise_consent_url')
|
||||
@ddt.data(
|
||||
(True, True),
|
||||
(True, False),
|
||||
(False, True),
|
||||
(False, False),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_enterprise_course_enrollment_creation(
|
||||
self,
|
||||
enterprise_enrollment_exists,
|
||||
course_in_catalog,
|
||||
get_consent_url_mock,
|
||||
):
|
||||
for mode in ('audit', 'honor', 'verified'):
|
||||
CourseModeFactory.create(mode_slug=mode, course_id=self.course.id)
|
||||
|
||||
catalog_integration = self.create_catalog_integration()
|
||||
UserFactory(username=catalog_integration.service_username)
|
||||
|
||||
courses_in_catalog = [str(self.course.id)] if course_in_catalog else []
|
||||
enterprise_enrollment = {'course_id': str(self.course.id)} if enterprise_enrollment_exists else {}
|
||||
|
||||
self.mock_course_discovery_api_for_catalog_contains(
|
||||
catalog_id=1, course_run_ids=courses_in_catalog
|
||||
)
|
||||
self.mock_enterprise_course_enrollment_get_api(**enterprise_enrollment)
|
||||
self.mock_enterprise_course_enrollment_post_api()
|
||||
self.mock_enterprise_learner_api(enable_audit_enrollment=True)
|
||||
|
||||
get_consent_url_mock.return_value = 'http://appropriate-consent-url.com/'
|
||||
|
||||
url = reverse('course_modes_choose', args=[unicode(self.course.id)])
|
||||
|
||||
response = self.client.post(url, self.POST_PARAMS_FOR_COURSE_MODE['audit'])
|
||||
|
||||
final_url = reverse('dashboard') if not course_in_catalog else 'http://appropriate-consent-url.com/'
|
||||
|
||||
self.assertRedirects(response, final_url, fetch_redirect_response=False)
|
||||
if course_in_catalog:
|
||||
if enterprise_enrollment_exists:
|
||||
self.assertEquals(httpretty.last_request().method, 'GET')
|
||||
else:
|
||||
self.assertEquals(httpretty.last_request().method, 'POST')
|
||||
|
||||
@httpretty.activate
|
||||
@ddt.data(
|
||||
'',
|
||||
@@ -334,6 +380,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
|
||||
'unsupported': {'unsupported_mode': True},
|
||||
}
|
||||
|
||||
@httpretty.activate
|
||||
@ddt.data(
|
||||
('audit', 'dashboard'),
|
||||
('honor', 'dashboard'),
|
||||
@@ -341,6 +388,8 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_choose_mode_redirect(self, course_mode, expected_redirect):
|
||||
self.mock_enterprise_learner_api()
|
||||
self.mock_enterprise_course_enrollment_get_api()
|
||||
# Create the course modes
|
||||
for mode in ('audit', 'honor', 'verified'):
|
||||
min_price = 0 if mode in ["honor", "audit"] else 1
|
||||
@@ -363,7 +412,38 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
|
||||
|
||||
self.assertRedirects(response, redirect_url)
|
||||
|
||||
@httpretty.activate
|
||||
def test_choose_mode_audit_enroll_on_get(self):
|
||||
"""
|
||||
Confirms that the learner will be enrolled in Audit track if it is the only possible option
|
||||
"""
|
||||
self.mock_enterprise_learner_api()
|
||||
self.mock_enterprise_course_enrollment_get_api()
|
||||
# Create the course mode
|
||||
audit_mode = 'audit'
|
||||
CourseModeFactory.create(mode_slug=audit_mode, course_id=self.course.id, min_price=0)
|
||||
|
||||
# Assert learner is not enrolled in Audit track pre-POST
|
||||
mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)
|
||||
self.assertIsNone(mode)
|
||||
self.assertIsNone(is_active)
|
||||
|
||||
# Choose the audit mode (POST request)
|
||||
choose_track_url = reverse('course_modes_choose', args=[unicode(self.course.id)])
|
||||
response = self.client.get(choose_track_url)
|
||||
|
||||
# Assert learner is enrolled in Audit track and sent to the dashboard
|
||||
mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)
|
||||
self.assertEquals(mode, audit_mode)
|
||||
self.assertTrue(is_active)
|
||||
|
||||
redirect_url = reverse('dashboard')
|
||||
self.assertRedirects(response, redirect_url)
|
||||
|
||||
@httpretty.activate
|
||||
def test_choose_mode_audit_enroll_on_post(self):
|
||||
self.mock_enterprise_learner_api()
|
||||
self.mock_enterprise_course_enrollment_get_api()
|
||||
audit_mode = 'audit'
|
||||
# Create the course modes
|
||||
for mode in (audit_mode, 'verified'):
|
||||
@@ -398,7 +478,10 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
|
||||
self.assertEqual(mode, audit_mode)
|
||||
self.assertTrue(is_active)
|
||||
|
||||
@httpretty.activate
|
||||
def test_remember_donation_for_course(self):
|
||||
self.mock_enterprise_learner_api()
|
||||
self.mock_enterprise_course_enrollment_get_api()
|
||||
# Create the course modes
|
||||
CourseModeFactory.create(mode_slug='honor', course_id=self.course.id)
|
||||
CourseModeFactory.create(mode_slug='verified', course_id=self.course.id, min_price=1)
|
||||
@@ -415,7 +498,10 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
|
||||
expected_amount = decimal.Decimal(self.POST_PARAMS_FOR_COURSE_MODE['verified']['contribution'])
|
||||
self.assertEqual(actual_amount, expected_amount)
|
||||
|
||||
@httpretty.activate
|
||||
def test_successful_default_enrollment(self):
|
||||
self.mock_enterprise_learner_api()
|
||||
self.mock_enterprise_course_enrollment_get_api()
|
||||
# Create the course modes
|
||||
for mode in (CourseMode.DEFAULT_MODE_SLUG, 'verified'):
|
||||
CourseModeFactory.create(mode_slug=mode, course_id=self.course.id)
|
||||
@@ -437,7 +523,10 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
|
||||
self.assertEqual(mode, CourseMode.DEFAULT_MODE_SLUG)
|
||||
self.assertEqual(is_active, True)
|
||||
|
||||
@httpretty.activate
|
||||
def test_unsupported_enrollment_mode_failure(self):
|
||||
self.mock_enterprise_learner_api()
|
||||
self.mock_enterprise_course_enrollment_get_api()
|
||||
# Create the supported course modes
|
||||
for mode in ('honor', 'verified'):
|
||||
CourseModeFactory.create(mode_slug=mode, course_id=self.course.id)
|
||||
|
||||
@@ -25,6 +25,7 @@ from edxmako.shortcuts import render_to_response
|
||||
from lms.djangoapps.commerce.utils import EcommerceService
|
||||
from openedx.core.djangoapps.embargo import api as embargo_api
|
||||
from openedx.features.enterprise_support import api as enterprise_api
|
||||
from openedx.features.enterprise_support.api import get_enterprise_consent_url
|
||||
from student.models import CourseEnrollment
|
||||
from third_party_auth.decorators import tpa_hint_ends_existing_session
|
||||
from util import organizations_helpers as organization_api
|
||||
@@ -107,6 +108,16 @@ class ChooseModeView(View):
|
||||
# If there isn't a verified mode available, then there's nothing
|
||||
# to do on this page. Send the user to the dashboard.
|
||||
if not CourseMode.has_verified_mode(modes):
|
||||
# If the learner has arrived at this screen via the traditional enrollment workflow,
|
||||
# then they should already be enrolled in an audit mode for the course, assuming one has
|
||||
# been configured. However, alternative enrollment workflows have been introduced into the
|
||||
# system, such as third-party discovery. These workflows result in learners arriving
|
||||
# directly at this screen, and they will not necessarily be pre-enrolled in the audit mode.
|
||||
# In this particular case, Audit is the ONLY option available, and thus we need to ensure
|
||||
# that the learner is truly enrolled before we redirect them away to the dashboard.
|
||||
if len(modes) == 1 and modes.get(CourseMode.AUDIT):
|
||||
CourseEnrollment.enroll(request.user, course_key, CourseMode.AUDIT)
|
||||
return redirect(self._get_redirect_url_for_audit_enrollment(request, course_id))
|
||||
return redirect(reverse('dashboard'))
|
||||
|
||||
# If a user has already paid, redirect them to the dashboard.
|
||||
@@ -241,19 +252,14 @@ class ChooseModeView(View):
|
||||
allowed_modes = CourseMode.modes_for_course_dict(course_key)
|
||||
if requested_mode not in allowed_modes:
|
||||
return HttpResponseBadRequest(_("Enrollment mode not supported"))
|
||||
|
||||
if requested_mode == 'audit':
|
||||
if requested_mode in CourseMode.AUDIT_MODES:
|
||||
# If the learner has arrived at this screen via the traditional enrollment workflow,
|
||||
# then they should already be enrolled in an audit mode for the course, assuming one has
|
||||
# been configured. However, alternative enrollment workflows have been introduced into the
|
||||
# system, such as third-party discovery. These workflows result in learners arriving
|
||||
# directly at this screen, and they will not necessarily be pre-enrolled in the audit mode.
|
||||
CourseEnrollment.enroll(request.user, course_key, CourseMode.AUDIT)
|
||||
return redirect(reverse('dashboard'))
|
||||
|
||||
if requested_mode == 'honor':
|
||||
CourseEnrollment.enroll(user, course_key, mode=requested_mode)
|
||||
return redirect(reverse('dashboard'))
|
||||
return redirect(self._get_redirect_url_for_audit_enrollment(request, course_id))
|
||||
|
||||
mode_info = allowed_modes[requested_mode]
|
||||
|
||||
@@ -284,6 +290,44 @@ class ChooseModeView(View):
|
||||
)
|
||||
)
|
||||
|
||||
def _get_redirect_url_for_audit_enrollment(self, request, course_id):
|
||||
"""
|
||||
After a user has been enrolled in a course in an audit mode, determine the appropriate location
|
||||
to which they ought to be redirected, bearing in mind enterprise data sharing consent considerations.
|
||||
"""
|
||||
enterprise_learner_data = enterprise_api.get_enterprise_learner_data(site=request.site, user=request.user)
|
||||
|
||||
if enterprise_learner_data:
|
||||
enterprise_learner = enterprise_learner_data[0]
|
||||
# If we have an enterprise learner, check to see if the current course is in the enterprise's catalog.
|
||||
is_course_in_enterprise_catalog = enterprise_api.is_course_in_enterprise_catalog(
|
||||
site=request.site,
|
||||
course_id=course_id,
|
||||
enterprise_catalog_id=enterprise_learner['enterprise_customer']['catalog']
|
||||
)
|
||||
# If the course is in the catalog, check for an existing Enterprise enrollment
|
||||
if is_course_in_enterprise_catalog:
|
||||
client = enterprise_api.EnterpriseApiClient()
|
||||
if not client.get_enterprise_course_enrollment(enterprise_learner['id'], course_id):
|
||||
# If there's no existing Enterprise enrollment, create one.
|
||||
client.post_enterprise_course_enrollment(request.user.username, course_id, None)
|
||||
# Check if consent is required, and generate a redirect URL to the
|
||||
# consent service if so; this function returns None if consent
|
||||
# is not required or has already been granted.
|
||||
consent_url = get_enterprise_consent_url(
|
||||
request,
|
||||
course_id,
|
||||
user=request.user,
|
||||
return_to='dashboard',
|
||||
course_specific_return=False,
|
||||
)
|
||||
# If we got a redirect URL for consent, go there.
|
||||
if consent_url:
|
||||
return consent_url
|
||||
|
||||
# If the enrollment isn't Enterprise-linked, or if consent isn't necessary, go to the Dashboard.
|
||||
return reverse('dashboard')
|
||||
|
||||
def _get_requested_mode(self, request_dict):
|
||||
"""Get the user's requested mode
|
||||
|
||||
|
||||
Reference in New Issue
Block a user