Move to new consent API
This commit is contained in:
@@ -21,7 +21,6 @@ from lms.djangoapps.commerce.tests import test_utils as ecomm_test_utils
|
||||
from openedx.core.djangoapps.catalog.tests.mixins import CatalogIntegrationMixin
|
||||
from openedx.core.djangoapps.embargo.test_utils import restrict_course
|
||||
from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme
|
||||
from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseServiceMockMixin
|
||||
from student.models import CourseEnrollment
|
||||
from student.tests.factories import CourseEnrollmentFactory, UserFactory
|
||||
from util import organizations_helpers as organizations_api
|
||||
@@ -35,7 +34,7 @@ from xmodule.modulestore.tests.factories import CourseFactory
|
||||
@attr(shard=3)
|
||||
@ddt.ddt
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTestCase, EnterpriseServiceMockMixin, CourseCatalogServiceMockMixin):
|
||||
class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTestCase, CourseCatalogServiceMockMixin):
|
||||
"""
|
||||
Course Mode View tests
|
||||
"""
|
||||
@@ -48,13 +47,6 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
|
||||
self.user = UserFactory.create(username="Bob", email="bob@example.com", password="edx")
|
||||
self.client.login(username=self.user.username, password="edx")
|
||||
|
||||
# Create a service user, because the track selection page depends on it
|
||||
UserFactory.create(
|
||||
username='enterprise_worker',
|
||||
email="enterprise_worker@example.com",
|
||||
password="edx",
|
||||
)
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
@httpretty.activate
|
||||
@ddt.data(
|
||||
@@ -82,8 +74,6 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
|
||||
user=self.user
|
||||
)
|
||||
|
||||
self.mock_enterprise_learner_api()
|
||||
|
||||
# Configure whether we're upgrading or not
|
||||
url = reverse('course_modes_choose', args=[unicode(self.course.id)])
|
||||
response = self.client.get(url)
|
||||
@@ -133,109 +123,6 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
|
||||
self.assertRedirects(response, 'http://testserver/basket/add/?sku=TEST', fetch_redirect_response=False)
|
||||
ecomm_test_utils.update_commerce_config(enabled=False)
|
||||
|
||||
def _generate_enterprise_learner_context(self, enable_audit_enrollment=False):
|
||||
"""
|
||||
Internal helper to support common pieces of test case variations
|
||||
"""
|
||||
# Create the course modes
|
||||
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)
|
||||
|
||||
self.mock_course_discovery_api_for_catalog_contains(
|
||||
catalog_id=1, course_run_ids=[str(self.course.id)]
|
||||
)
|
||||
self.mock_enterprise_learner_api(enable_audit_enrollment=enable_audit_enrollment)
|
||||
|
||||
return reverse('course_modes_choose', args=[unicode(self.course.id)])
|
||||
|
||||
@httpretty.activate
|
||||
def test_no_enrollment(self):
|
||||
url = self._generate_enterprise_learner_context()
|
||||
response = self.client.get(url)
|
||||
self.assertEquals(response.status_code, 200)
|
||||
|
||||
@httpretty.activate
|
||||
@waffle.testutils.override_switch("populate-multitenant-programs", True)
|
||||
def test_enterprise_learner_context(self):
|
||||
"""
|
||||
Test: Track selection page should show the enterprise context message if user belongs to the Enterprise.
|
||||
"""
|
||||
url = self._generate_enterprise_learner_context()
|
||||
|
||||
# User visits the track selection page directly without ever enrolling
|
||||
response = self.client.get(url)
|
||||
self.assertEquals(response.status_code, 200)
|
||||
self.assertContains(
|
||||
response,
|
||||
'Welcome, {username}! You are about to enroll in {course_name}, from {partner_names}, '
|
||||
'sponsored by TestShib. Please select your enrollment information below.'.format(
|
||||
username=self.user.username,
|
||||
course_name=self.course.display_name_with_default_escaped,
|
||||
partner_names=self.course.org
|
||||
)
|
||||
)
|
||||
|
||||
@httpretty.activate
|
||||
@waffle.testutils.override_switch("populate-multitenant-programs", True)
|
||||
def test_enterprise_learner_context_with_multiple_organizations(self):
|
||||
"""
|
||||
Test: Track selection page should show the enterprise context message with multiple organization names
|
||||
if user belongs to the Enterprise.
|
||||
"""
|
||||
url = self._generate_enterprise_learner_context()
|
||||
|
||||
# Creating organization
|
||||
for i in xrange(2):
|
||||
test_organization_data = {
|
||||
'name': 'test organization ' + str(i),
|
||||
'short_name': 'test_organization_' + str(i),
|
||||
'description': 'Test Organization Description',
|
||||
'active': True,
|
||||
'logo': '/logo_test1.png/'
|
||||
}
|
||||
test_org = organizations_api.add_organization(organization_data=test_organization_data)
|
||||
organizations_api.add_organization_course(organization_data=test_org, course_id=unicode(self.course.id))
|
||||
|
||||
# User visits the track selection page directly without ever enrolling
|
||||
response = self.client.get(url)
|
||||
self.assertEquals(response.status_code, 200)
|
||||
self.assertContains(
|
||||
response,
|
||||
'Welcome, {username}! You are about to enroll in {course_name}, from test organization 0 and '
|
||||
'test organization 1, sponsored by TestShib. Please select your enrollment information below.'.format(
|
||||
username=self.user.username,
|
||||
course_name=self.course.display_name_with_default_escaped
|
||||
)
|
||||
)
|
||||
|
||||
@httpretty.activate
|
||||
@waffle.testutils.override_switch("populate-multitenant-programs", True)
|
||||
def test_enterprise_learner_context_audit_disabled(self):
|
||||
"""
|
||||
Track selection page should hide the audit choice by default in an Enterprise Customer/Learner context
|
||||
"""
|
||||
|
||||
# User visits the track selection page directly without ever enrolling, sees only Verified track choice
|
||||
url = self._generate_enterprise_learner_context()
|
||||
response = self.client.get(url)
|
||||
self.assertContains(response, 'Pursue a Verified Certificate')
|
||||
self.assertNotContains(response, 'Audit This Course')
|
||||
|
||||
@httpretty.activate
|
||||
def test_enterprise_learner_context_audit_enabled(self):
|
||||
"""
|
||||
Track selection page should display Audit choice when specified for an Enterprise Customer
|
||||
"""
|
||||
|
||||
# User visits the track selection page directly without ever enrolling, sees both Verified and Audit choices
|
||||
url = self._generate_enterprise_learner_context(enable_audit_enrollment=True)
|
||||
response = self.client.get(url)
|
||||
self.assertContains(response, 'Pursue a Verified Certificate')
|
||||
self.assertContains(response, 'Audit This Course')
|
||||
|
||||
@httpretty.activate
|
||||
@ddt.data(
|
||||
'',
|
||||
@@ -263,8 +150,6 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
|
||||
user=self.user
|
||||
)
|
||||
|
||||
self.mock_enterprise_learner_api()
|
||||
|
||||
# Verify that the prices render correctly
|
||||
response = self.client.get(
|
||||
reverse('course_modes_choose', args=[unicode(self.course.id)]),
|
||||
@@ -286,8 +171,6 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
|
||||
for mode in available_modes:
|
||||
CourseModeFactory.create(mode_slug=mode, course_id=self.course.id)
|
||||
|
||||
self.mock_enterprise_learner_api()
|
||||
|
||||
# Check whether credit upsell is shown on the page
|
||||
# This should *only* be shown when a credit mode is available
|
||||
url = reverse('course_modes_choose', args=[unicode(self.course.id)])
|
||||
@@ -530,8 +413,6 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
|
||||
for mode in ["honor", "verified"]:
|
||||
CourseModeFactory.create(mode_slug=mode, course_id=self.course.id)
|
||||
|
||||
self.mock_enterprise_learner_api()
|
||||
|
||||
# Load the track selection page
|
||||
url = reverse('course_modes_choose', args=[unicode(self.course.id)])
|
||||
response = self.client.get(url)
|
||||
@@ -558,7 +439,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
|
||||
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
class TrackSelectionEmbargoTest(UrlResetMixin, ModuleStoreTestCase, EnterpriseServiceMockMixin):
|
||||
class TrackSelectionEmbargoTest(UrlResetMixin, ModuleStoreTestCase):
|
||||
"""Test embargo restrictions on the track selection page. """
|
||||
|
||||
URLCONF_MODULES = ['openedx.core.djangoapps.embargo']
|
||||
@@ -576,13 +457,6 @@ class TrackSelectionEmbargoTest(UrlResetMixin, ModuleStoreTestCase, EnterpriseSe
|
||||
self.user = UserFactory.create(username="Bob", email="bob@example.com", password="edx")
|
||||
self.client.login(username=self.user.username, password="edx")
|
||||
|
||||
# Create a service user
|
||||
UserFactory.create(
|
||||
username='enterprise_worker',
|
||||
email="enterprise_worker@example.com",
|
||||
password="edx",
|
||||
)
|
||||
|
||||
# Construct the URL for the track selection page
|
||||
self.url = reverse('course_modes_choose', args=[unicode(self.course.id)])
|
||||
|
||||
@@ -595,6 +469,5 @@ class TrackSelectionEmbargoTest(UrlResetMixin, ModuleStoreTestCase, EnterpriseSe
|
||||
@httpretty.activate
|
||||
def test_embargo_allow(self):
|
||||
|
||||
self.mock_enterprise_learner_api()
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@@ -24,7 +24,6 @@ from edxmako.shortcuts import render_to_response
|
||||
from lms.djangoapps.commerce.utils import EcommerceService
|
||||
from lms.djangoapps.experiments.utils import get_experiment_user_metadata_context
|
||||
from openedx.core.djangoapps.embargo import api as embargo_api
|
||||
from openedx.features.enterprise_support import api as enterprise_api
|
||||
from student.models import CourseEnrollment
|
||||
from third_party_auth.decorators import tpa_hint_ends_existing_session
|
||||
from util import organizations_helpers as organization_api
|
||||
@@ -162,36 +161,6 @@ class ChooseModeView(View):
|
||||
title_content = _("Congratulations! You are now enrolled in {course_name}").format(
|
||||
course_name=course.display_name_with_default_escaped
|
||||
)
|
||||
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]
|
||||
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 is_course_in_enterprise_catalog:
|
||||
partner_names = partner_name = course.display_organization \
|
||||
if course.display_organization else course.org
|
||||
enterprise_name = enterprise_learner['enterprise_customer']['name']
|
||||
organizations = organization_api.get_course_organizations(course_id=course.id)
|
||||
if organizations:
|
||||
partner_names = ' and '.join([org.get('name', partner_name) for org in organizations])
|
||||
|
||||
title_content = _("Welcome, {username}! You are about to enroll in {course_name},"
|
||||
" from {partner_names}, sponsored by {enterprise_name}. Please select your enrollment"
|
||||
" information below.").format(
|
||||
username=request.user.username,
|
||||
course_name=course.display_name_with_default_escaped,
|
||||
partner_names=partner_names,
|
||||
enterprise_name=enterprise_name
|
||||
)
|
||||
|
||||
# Hide the audit modes for this enterprise customer, if necessary
|
||||
if not enterprise_learner['enterprise_customer'].get('enable_audit_enrollment'):
|
||||
for audit_mode in CourseMode.AUDIT_MODES:
|
||||
modes.pop(audit_mode, None)
|
||||
|
||||
context["title_content"] = title_content
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ class EnrollmentTestMixin(object):
|
||||
min_mongo_calls=0,
|
||||
max_mongo_calls=0,
|
||||
enterprise_course_consent=None,
|
||||
linked_enterprise_customer=None,
|
||||
):
|
||||
"""
|
||||
Enroll in the course and verify the response's status code. If the expected status is 200, also validates
|
||||
@@ -85,6 +86,9 @@ class EnrollmentTestMixin(object):
|
||||
if enterprise_course_consent is not None:
|
||||
data['enterprise_course_consent'] = enterprise_course_consent
|
||||
|
||||
if linked_enterprise_customer is not None:
|
||||
data['linked_enterprise_customer'] = linked_enterprise_customer
|
||||
|
||||
extra = {}
|
||||
if as_server:
|
||||
extra['HTTP_X_EDX_API_KEY'] = self.API_KEY
|
||||
@@ -961,6 +965,7 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase, Ente
|
||||
self.assertTrue(is_active)
|
||||
self.assertEqual(course_mode, updated_mode)
|
||||
|
||||
@override_settings(ENABLE_ENTERPRISE_INTEGRATION=True)
|
||||
def test_enterprise_course_enrollment_invalid_consent(self):
|
||||
"""Verify that the enterprise_course_consent must be a boolean. """
|
||||
CourseModeFactory.create(
|
||||
@@ -976,6 +981,7 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase, Ente
|
||||
|
||||
@httpretty.activate
|
||||
@override_settings(ENTERPRISE_SERVICE_WORKER_USERNAME='enterprise_worker')
|
||||
@override_settings(ENABLE_ENTERPRISE_INTEGRATION=True)
|
||||
def test_enterprise_course_enrollment_api_error(self):
|
||||
"""Verify that enterprise service errors are handled properly. """
|
||||
UserFactory.create(
|
||||
@@ -1003,6 +1009,7 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase, Ente
|
||||
|
||||
@httpretty.activate
|
||||
@override_settings(ENTERPRISE_SERVICE_WORKER_USERNAME='enterprise_worker')
|
||||
@override_settings(ENABLE_ENTERPRISE_INTEGRATION=True)
|
||||
def test_enterprise_course_enrollment_successful(self):
|
||||
"""Verify that the enrollment completes when the EnterpriseCourseEnrollment creation succeeds. """
|
||||
UserFactory.create(
|
||||
@@ -1028,6 +1035,43 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase, Ente
|
||||
'No request was made to the mocked enterprise-course-enrollment API'
|
||||
)
|
||||
|
||||
@httpretty.activate
|
||||
@override_settings(ENTERPRISE_SERVICE_WORKER_USERNAME='enterprise_worker')
|
||||
@override_settings(ENABLE_ENTERPRISE_INTEGRATION=True)
|
||||
def test_enterprise_course_enrollment_with_ec_uuid(self):
|
||||
"""Verify that the enrollment completes when the EnterpriseCourseEnrollment creation succeeds. """
|
||||
UserFactory.create(
|
||||
username='enterprise_worker',
|
||||
email=self.EMAIL,
|
||||
password=self.PASSWORD,
|
||||
)
|
||||
CourseModeFactory.create(
|
||||
course_id=self.course.id,
|
||||
mode_slug=CourseMode.DEFAULT_MODE_SLUG,
|
||||
mode_display_name=CourseMode.DEFAULT_MODE_SLUG,
|
||||
)
|
||||
consent_kwargs = {
|
||||
'username': self.user.username,
|
||||
'course_id': unicode(self.course.id),
|
||||
'ec_uuid': 'this-is-a-real-uuid'
|
||||
}
|
||||
self.mock_consent_missing(**consent_kwargs)
|
||||
self.mock_consent_post(**consent_kwargs)
|
||||
self.assert_enrollment_status(
|
||||
expected_status=status.HTTP_200_OK,
|
||||
as_server=True,
|
||||
username='enterprise_worker',
|
||||
linked_enterprise_customer='this-is-a-real-uuid',
|
||||
)
|
||||
self.assertEqual(
|
||||
httpretty.last_request().path,
|
||||
'/consent/api/v1/data_sharing_consent',
|
||||
)
|
||||
self.assertEqual(
|
||||
httpretty.last_request().method,
|
||||
httpretty.POST
|
||||
)
|
||||
|
||||
def test_enrollment_attributes_always_written(self):
|
||||
""" Enrollment attributes should always be written, regardless of whether
|
||||
the enrollment is being created or updated.
|
||||
|
||||
@@ -29,7 +29,12 @@ from openedx.core.lib.api.authentication import (
|
||||
from openedx.core.lib.api.permissions import ApiKeyHeaderPermission, ApiKeyHeaderPermissionIsAuthenticated
|
||||
from openedx.core.lib.exceptions import CourseNotFoundError
|
||||
from openedx.core.lib.log_utils import audit_log
|
||||
from openedx.features.enterprise_support.api import EnterpriseApiClient, EnterpriseApiException, enterprise_enabled
|
||||
from openedx.features.enterprise_support.api import (
|
||||
ConsentApiClient,
|
||||
EnterpriseApiClient,
|
||||
EnterpriseApiException,
|
||||
enterprise_enabled
|
||||
)
|
||||
from student.auth import user_has_role
|
||||
from student.models import User
|
||||
from student.roles import CourseStaffRole, GlobalStaff
|
||||
@@ -591,27 +596,42 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn):
|
||||
)
|
||||
|
||||
enterprise_course_consent = request.data.get('enterprise_course_consent')
|
||||
# Check if the enterprise_course_enrollment is a boolean
|
||||
if has_api_key_permissions and enterprise_enabled() and enterprise_course_consent is not None:
|
||||
if not isinstance(enterprise_course_consent, bool):
|
||||
return Response(
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
data={
|
||||
'message': (u"'{value}' is an invalid enterprise course consent value.").format(
|
||||
value=enterprise_course_consent
|
||||
)
|
||||
}
|
||||
)
|
||||
try:
|
||||
EnterpriseApiClient().post_enterprise_course_enrollment(
|
||||
username,
|
||||
unicode(course_id),
|
||||
enterprise_course_consent
|
||||
)
|
||||
except EnterpriseApiException as error:
|
||||
log.exception("An unexpected error occurred while creating the new EnterpriseCourseEnrollment "
|
||||
"for user [%s] in course run [%s]", username, course_id)
|
||||
raise CourseEnrollmentError(error.message)
|
||||
explicit_linked_enterprise = request.data.get('linked_enterprise_customer')
|
||||
if has_api_key_permissions and enterprise_enabled():
|
||||
# We received an explicitly-linked EnterpriseCustomer for the enrollment
|
||||
if explicit_linked_enterprise is not None:
|
||||
kwargs = {
|
||||
'username': username,
|
||||
'course_id': unicode(course_id),
|
||||
'enterprise_customer_uuid': explicit_linked_enterprise,
|
||||
}
|
||||
consent_client = ConsentApiClient()
|
||||
consent_client.provide_consent(**kwargs)
|
||||
|
||||
# We received an implicit "consent granted" parameter from ecommerce
|
||||
# TODO: Once ecommerce has been deployed with explicit enterprise support, remove this
|
||||
# entire chunk of logic, related tests, and any supporting methods no longer required.
|
||||
elif enterprise_course_consent is not None:
|
||||
# Check if the enterprise_course_enrollment is a boolean
|
||||
if not isinstance(enterprise_course_consent, bool):
|
||||
return Response(
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
data={
|
||||
'message': (u"'{value}' is an invalid enterprise course consent value.").format(
|
||||
value=enterprise_course_consent
|
||||
)
|
||||
}
|
||||
)
|
||||
try:
|
||||
EnterpriseApiClient().post_enterprise_course_enrollment(
|
||||
username,
|
||||
unicode(course_id),
|
||||
enterprise_course_consent
|
||||
)
|
||||
except EnterpriseApiException as error:
|
||||
log.exception("An unexpected error occurred while creating the new EnterpriseCourseEnrollment "
|
||||
"for user [%s] in course run [%s]", username, course_id)
|
||||
raise CourseEnrollmentError(error.message)
|
||||
|
||||
enrollment_attributes = request.data.get('enrollment_attributes')
|
||||
enrollment = api.get_enrollment(username, unicode(course_id))
|
||||
|
||||
Reference in New Issue
Block a user