diff --git a/common/djangoapps/enrollment/tests/test_views.py b/common/djangoapps/enrollment/tests/test_views.py index a74dea421faee4dfedfef45d35f8a073b0f8cfc5..580cbd8002221abf2fffcee1c1b3978c01a3126f 100644 --- a/common/djangoapps/enrollment/tests/test_views.py +++ b/common/djangoapps/enrollment/tests/test_views.py @@ -33,6 +33,7 @@ from openedx.core.djangoapps.embargo.test_utils import restrict_course from openedx.core.djangoapps.user_api.models import UserOrgTag from openedx.core.lib.django_test_client_utils import get_absolute_url from openedx.core.lib.token_utils import JwtBuilder +from openedx.features.enterprise_support.tests import FAKE_ENTERPRISE_CUSTOMER from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseServiceMockMixin from student.models import CourseEnrollment from student.roles import CourseStaffRole @@ -932,7 +933,8 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase, Ente @httpretty.activate @override_settings(ENTERPRISE_SERVICE_WORKER_USERNAME='enterprise_worker', FEATURES=dict(ENABLE_ENTERPRISE_INTEGRATION=True)) - def test_enterprise_course_enrollment_with_ec_uuid(self): + @patch('openedx.features.enterprise_support.api.enterprise_customer_from_api') + def test_enterprise_course_enrollment_with_ec_uuid(self, mock_enterprise_customer_from_api): """Verify that the enrollment completes when the EnterpriseCourseEnrollment creation succeeds. """ UserFactory.create( username='enterprise_worker', @@ -949,6 +951,7 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase, Ente 'course_id': unicode(self.course.id), 'ec_uuid': 'this-is-a-real-uuid' } + mock_enterprise_customer_from_api.return_value = FAKE_ENTERPRISE_CUSTOMER self.mock_enterprise_course_enrollment_post_api() self.mock_consent_missing(**consent_kwargs) self.mock_consent_post(**consent_kwargs) diff --git a/common/djangoapps/util/views.py b/common/djangoapps/util/views.py index cdf5f860a355e31adc5d0ff285c97e6fe4cfa559..52a262eb33e5485033cba8ef1fc9a5b60ac4ea40 100644 --- a/common/djangoapps/util/views.py +++ b/common/djangoapps/util/views.py @@ -454,8 +454,8 @@ def submit_feedback(request): success = False context = get_feedback_form_context(request) - #Update the tag info with 'enterprise_learner' if the user belongs to an enterprise customer. - enterprise_learner_data = enterprise_api.get_enterprise_learner_data(site=request.site, user=request.user) + # Update the tag info with 'enterprise_learner' if the user belongs to an enterprise customer. + enterprise_learner_data = enterprise_api.get_enterprise_learner_data(user=request.user) if enterprise_learner_data: context["tags"]["learner_type"] = "enterprise_learner" diff --git a/lms/djangoapps/support/views/contact_us.py b/lms/djangoapps/support/views/contact_us.py index b8753e3448ec1640e71329870078ab2a88dcccb3..ff74e10c008e370e6f958150c9c0fb6f7ed54fef 100644 --- a/lms/djangoapps/support/views/contact_us.py +++ b/lms/djangoapps/support/views/contact_us.py @@ -33,7 +33,7 @@ class ContactUsView(View): if request.user.is_authenticated(): context['user_enrollments'] = CourseEnrollment.enrollments_for_user_with_overviews_preload(request.user) - enterprise_learner_data = enterprise_api.get_enterprise_learner_data(site=request.site, user=request.user) + enterprise_learner_data = enterprise_api.get_enterprise_learner_data(user=request.user) if enterprise_learner_data: tags.append('enterprise_learner') diff --git a/lms/envs/common.py b/lms/envs/common.py index 8a1b384f85ff066fa6737348f0b5123b08c8233e..8cb39b82e9e1b58459f75f5d709aaf2270bbefbc 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -1275,8 +1275,6 @@ MIDDLEWARE_CLASSES = [ # Must be after DarkLangMiddleware. 'django.middleware.locale.LocaleMiddleware', - # 'debug_toolbar.middleware.DebugToolbarMiddleware', - 'django_comment_client.utils.ViewNameMiddleware', 'codejail.django_integration.ConfigureCodeJailMiddleware', @@ -1298,6 +1296,9 @@ MIDDLEWARE_CLASSES = [ 'waffle.middleware.WaffleMiddleware', + # Inserts Enterprise content. + 'openedx.features.enterprise_support.middleware.EnterpriseMiddleware', + # This must be last 'openedx.core.djangoapps.site_configuration.middleware.SessionCookieDomainOverrideMiddleware', ] diff --git a/lms/urls.py b/lms/urls.py index 3cb63c264ccee2474a8231c48a59d0d3ff2525bb..ff6a8be4756e1cccf157a6cf7bf5b3a65bfc8427 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -143,7 +143,7 @@ urlpatterns = [ # TODO: This needs to move to a separate urls.py once the student_account and # student views below find a home together -if settings.FEATURES['ENABLE_COMBINED_LOGIN_REGISTRATION']: +if settings.FEATURES.get('ENABLE_COMBINED_LOGIN_REGISTRATION'): # Backwards compatibility with old URL structure, but serve the new views urlpatterns += [ url(r'^login$', student_account_views.login_and_registration_form, @@ -158,12 +158,12 @@ else: url(r'^register$', student_views.register_user, name='register_user'), ] -if settings.FEATURES['ENABLE_MOBILE_REST_API']: +if settings.FEATURES.get('ENABLE_MOBILE_REST_API'): urlpatterns += [ url(r'^api/mobile/v0.5/', include('mobile_api.urls')), ] -if settings.FEATURES['ENABLE_OPENBADGES']: +if settings.FEATURES.get('ENABLE_OPENBADGES'): urlpatterns += [ url(r'^api/badges/v1/', include('badges.api.urls', app_name='badges', namespace='badges_api')), ] @@ -174,7 +174,7 @@ urlpatterns += [ # sysadmin dashboard, to see what courses are loaded, to delete & load courses -if settings.FEATURES['ENABLE_SYSADMIN_DASHBOARD']: +if settings.FEATURES.get('ENABLE_SYSADMIN_DASHBOARD'): urlpatterns += [ url(r'^sysadmin/', include('dashboard.sysadmin_urls')), ] @@ -675,7 +675,7 @@ urlpatterns += [ ), ] -if settings.FEATURES['ENABLE_TEAMS']: +if settings.FEATURES.get('ENABLE_TEAMS'): # Teams endpoints urlpatterns += [ url( @@ -831,7 +831,7 @@ if settings.FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD'): external_auth_views.course_specific_register, name='course-specific-register'), ] -if configuration_helpers.get_value('ENABLE_BULK_ENROLLMENT_VIEW', settings.FEATURES['ENABLE_BULK_ENROLLMENT_VIEW']): +if configuration_helpers.get_value('ENABLE_BULK_ENROLLMENT_VIEW', settings.FEATURES.get('ENABLE_BULK_ENROLLMENT_VIEW')): urlpatterns += [ url(r'^api/bulk_enroll/v1/', include('bulk_enroll.urls')), ] @@ -994,7 +994,7 @@ urlpatterns += [ ] # Custom courses on edX (CCX) URLs -if settings.FEATURES['CUSTOM_COURSES_EDX']: +if settings.FEATURES.get('CUSTOM_COURSES_EDX'): urlpatterns += [ url(r'^courses/{}/'.format(settings.COURSE_ID_PATTERN), include('ccx.urls')), diff --git a/openedx/features/enterprise_support/api.py b/openedx/features/enterprise_support/api.py index afa044adaf07881a3766387c1ed4cfb67be47e73..d5487a0d5d6b4ee48920454850596685c7caffb2 100644 --- a/openedx/features/enterprise_support/api.py +++ b/openedx/features/enterprise_support/api.py @@ -161,12 +161,12 @@ class EnterpriseApiClient(object): LOGGER.exception(message) raise EnterpriseApiException(message) - def fetch_enterprise_learner_data(self, site, user): # pylint: disable=unused-argument + def fetch_enterprise_learner_data(self, user): """ Fetch information related to enterprise from the Enterprise Service. Example: - fetch_enterprise_learner_data(site, user) + fetch_enterprise_learner_data(user) Argument: site: (Site) site instance @@ -269,17 +269,12 @@ class EnterpriseApiServiceClient(EnterpriseServiceClientMixin, EnterpriseApiClie Fetch enterprise customer with enterprise service user and cache the API response`. """ - cache_key = get_cache_key( - resource='enterprise-customer', - resource_id=uuid, - username=settings.ENTERPRISE_SERVICE_WORKER_USERNAME, - ) - enterprise_customer = cache.get(cache_key) + enterprise_customer = enterprise_customer_from_cache(uuid=uuid) if not enterprise_customer: endpoint = getattr(self.client, 'enterprise-customer') enterprise_customer = endpoint(uuid).get() if enterprise_customer: - cache.set(cache_key, enterprise_customer, settings.ENTERPRISE_API_CACHE_TIMEOUT) + cache_enterprise(enterprise_customer) return enterprise_customer @@ -328,14 +323,79 @@ def enterprise_enabled(): return 'enterprise' in settings.INSTALLED_APPS and settings.FEATURES.get('ENABLE_ENTERPRISE_INTEGRATION', False) +def enterprise_is_enabled(otherwise=None): + """Decorator which requires that the Enterprise feature be enabled before the function can run.""" + def decorator(func): + """Decorator for ensuring the Enterprise feature is enabled.""" + def wrapper(*args, **kwargs): + if enterprise_enabled(): + return func(*args, **kwargs) + return otherwise + return wrapper + return decorator + + +@enterprise_is_enabled() +def get_enterprise_customer_cache_key(uuid, username=settings.ENTERPRISE_SERVICE_WORKER_USERNAME): + """The cache key used to get cached Enterprise Customer data.""" + return get_cache_key( + resource='enterprise-customer', + resource_id=uuid, + username=username, + ) + + +@enterprise_is_enabled() +def cache_enterprise(enterprise_customer): + """Cache this customer's data.""" + cache_key = get_enterprise_customer_cache_key(enterprise_customer['uuid']) + cache.set(cache_key, enterprise_customer, settings.ENTERPRISE_API_CACHE_TIMEOUT) + + +@enterprise_is_enabled() +def enterprise_customer_from_cache(request=None, uuid=None): + """Check all available caches for Enterprise Customer data.""" + enterprise_customer = None + + # Check if it's cached in the general cache storage. + if uuid: + cache_key = get_enterprise_customer_cache_key(uuid) + enterprise_customer = cache.get(cache_key) + + # Check if it's cached in the session. + if not enterprise_customer and request and request.user.is_authenticated(): + enterprise_customer = request.session.get('enterprise_customer') + + return enterprise_customer + + +@enterprise_is_enabled() +def enterprise_customer_from_api(request): + """Use an API to get Enterprise Customer data from request context clues.""" + enterprise_customer = None + enterprise_customer_uuid = enterprise_customer_uuid_for_request(request) + if enterprise_customer_uuid: + # If we were able to obtain an EnterpriseCustomer UUID, go ahead + # and use it to attempt to retrieve EnterpriseCustomer details + # from the EnterpriseCustomer API. + enterprise_api_client = ( + EnterpriseApiClient(user=request.user) + if request.user.is_authenticated() + else EnterpriseApiServiceClient() + ) + + try: + enterprise_customer = enterprise_api_client.get_enterprise_customer(enterprise_customer_uuid) + except HttpNotFoundError: + enterprise_customer = None + return enterprise_customer + + +@enterprise_is_enabled() def enterprise_customer_uuid_for_request(request): """ Check all the context clues of the request to gather a particular EnterpriseCustomer's UUID. """ - if not enterprise_enabled(): - return None - - enterprise_customer_uuid = None sso_provider_id = request.GET.get('tpa_hint') running_pipeline = get_partial_pipeline(request) if running_pipeline: @@ -355,7 +415,7 @@ def enterprise_customer_uuid_for_request(request): enterprise_customer_identity_provider__provider_id=sso_provider_id ).uuid except EnterpriseCustomer.DoesNotExist: - pass + enterprise_customer_uuid = None else: # Check if we got an Enterprise UUID passed directly as either a query # parameter, or as a value in the Enterprise cookie. @@ -366,50 +426,37 @@ def enterprise_customer_uuid_for_request(request): if not enterprise_customer_uuid and request.user.is_authenticated(): # If there's no way to get an Enterprise UUID for the request, check to see # if there's already an Enterprise attached to the requesting user on the backend. - learner_data = get_enterprise_learner_data(request.site, request.user) + learner_data = get_enterprise_learner_data(request.user) if learner_data: enterprise_customer_uuid = learner_data[0]['enterprise_customer']['uuid'] return enterprise_customer_uuid +@enterprise_is_enabled() def enterprise_customer_for_request(request): """ Check all the context clues of the request to determine if the request being made is tied to a particular EnterpriseCustomer. """ - enterprise_customer = None - enterprise_customer_uuid = enterprise_customer_uuid_for_request(request) - if enterprise_customer_uuid: - # If we were able to obtain an EnterpriseCustomer UUID, go ahead - # and use it to attempt to retrieve EnterpriseCustomer details - # from the EnterpriseCustomer API. - enterprise_api_client = EnterpriseApiServiceClient() - if request.user.is_authenticated(): - enterprise_api_client = EnterpriseApiClient(user=request.user) - - try: - enterprise_customer = enterprise_api_client.get_enterprise_customer(enterprise_customer_uuid) - except HttpNotFoundError: - enterprise_customer = None - - return enterprise_customer + if 'enterprise_customer' in request.session: + return enterprise_customer_from_cache(request=request) + else: + return enterprise_customer_from_api(request) +@enterprise_is_enabled(otherwise=False) def consent_needed_for_course(request, user, course_id, enrollment_exists=False): """ Wrap the enterprise app check to determine if the user needs to grant data sharing permissions before accessing a course. """ - if not enterprise_enabled(): - return False - consent_key = ('data_sharing_consent_needed', course_id) if request.session.get(consent_key) is False: return False - enterprise_learner_details = get_enterprise_learner_data(request.site, user) + enterprise_learner_details = get_enterprise_learner_data(user) if not enterprise_learner_details: consent_needed = False else: @@ -431,15 +478,13 @@ def consent_needed_for_course(request, user, course_id, enrollment_exists=False) return consent_needed +@enterprise_is_enabled(otherwise=set()) def get_consent_required_courses(user, course_ids): """ Returns a set of course_ids that require consent Note that this function makes use of the Enterprise models directly instead of using the API calls """ result = set() - if not enterprise_enabled(): - return result - enterprise_learner = EnterpriseCustomerUser.objects.filter(user_id=user.id).first() if not enterprise_learner or not enterprise_learner.enterprise_customer: return result @@ -456,6 +501,7 @@ def get_consent_required_courses(user, course_ids): return result +@enterprise_is_enabled(otherwise='') def get_enterprise_consent_url(request, course_id, user=None, return_to=None, enrollment_exists=False): """ Build a URL to redirect the user to the Enterprise app to provide data sharing @@ -468,9 +514,6 @@ def get_enterprise_consent_url(request, course_id, user=None, return_to=None, en * return_to: url name label for the page to return to after consent is granted. If None, return to request.path instead. """ - if not enterprise_enabled(): - return '' - user = user or request.user if not consent_needed_for_course(request, user, course_id, enrollment_exists=enrollment_exists): @@ -499,29 +542,24 @@ def get_enterprise_consent_url(request, course_id, user=None, return_to=None, en return full_url -def get_enterprise_learner_data(site, user): +@enterprise_is_enabled() +def get_enterprise_learner_data(user): """ Client API operation adapter/wrapper """ - if not enterprise_enabled(): - return None - - enterprise_learner_data = EnterpriseApiClient(user=user).fetch_enterprise_learner_data(site=site, user=user) + enterprise_learner_data = EnterpriseApiClient(user=user).fetch_enterprise_learner_data(user) if enterprise_learner_data: return enterprise_learner_data['results'] +@enterprise_is_enabled(otherwise={}) def get_enterprise_customer_for_learner(site, user): """ Return enterprise customer to whom given learner belongs. """ - if not enterprise_enabled(): - return {} - - enterprise_learner_data = get_enterprise_learner_data(site, user) + enterprise_learner_data = get_enterprise_learner_data(user) if enterprise_learner_data: return enterprise_learner_data[0]['enterprise_customer'] - return {} @@ -544,6 +582,7 @@ def get_consent_notification_data(enterprise_customer): return title_template, message_template +@enterprise_is_enabled(otherwise='') def get_dashboard_consent_notification(request, user, course_enrollments): """ If relevant to the request at hand, create a banner on the dashboard indicating consent failed. @@ -556,9 +595,6 @@ def get_dashboard_consent_notification(request, user, course_enrollments): Returns: str: Either an empty string, or a string containing the HTML code for the notification banner. """ - if not enterprise_enabled(): - return '' - enrollment = None consent_needed = False course_id = request.GET.get(CONSENT_FAILED_PARAMETER) @@ -612,14 +648,12 @@ def get_dashboard_consent_notification(request, user, course_enrollments): return '' +@enterprise_is_enabled() def insert_enterprise_pipeline_elements(pipeline): """ If the enterprise app is enabled, insert additional elements into the pipeline related to enterprise. """ - if not enterprise_enabled(): - return - additional_elements = ( 'enterprise.tpa_pipeline.handle_enterprise_logistration', ) diff --git a/openedx/features/enterprise_support/middleware.py b/openedx/features/enterprise_support/middleware.py new file mode 100644 index 0000000000000000000000000000000000000000..b2b18695cc74985dbfbebe85a42db77bcdee433b --- /dev/null +++ b/openedx/features/enterprise_support/middleware.py @@ -0,0 +1,29 @@ +""" +Middleware for the Enterprise feature. + +The Enterprise feature must be turned on for this middleware to have any effect. +""" + +from django.core.exceptions import MiddlewareNotUsed + +from openedx.features.enterprise_support import api + + +class EnterpriseMiddleware(object): + """ + Middleware that adds Enterprise-related content to the request. + """ + + def __init__(self): + """ + We don't need to use this middleware if the Enterprise feature isn't enabled. + """ + if not api.enterprise_enabled(): + raise MiddlewareNotUsed() + + def process_request(self, request): + """ + Fill the request with Enterprise-related content. + """ + if 'enterprise_customer' not in request.session and request.user.is_authenticated(): + request.session['enterprise_customer'] = api.enterprise_customer_for_request(request) diff --git a/openedx/features/enterprise_support/tests/__init__.py b/openedx/features/enterprise_support/tests/__init__.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..82c57415f8049a04264ed1b199fbd70e0c3c9d90 100644 --- a/openedx/features/enterprise_support/tests/__init__.py +++ b/openedx/features/enterprise_support/tests/__init__.py @@ -0,0 +1,23 @@ +""" +Things commonly needed in Enterprise tests. +""" + +from django.conf import settings + +FEATURES_WITH_ENTERPRISE_ENABLED = settings.FEATURES.copy() +FEATURES_WITH_ENTERPRISE_ENABLED['ENABLE_ENTERPRISE_INTEGRATION'] = True + +FAKE_ENTERPRISE_CUSTOMER = { + 'active': True, + 'branding_configuration': None, + 'catalog': None, + 'enable_audit_enrollment': False, + 'enable_data_sharing_consent': False, + 'enforce_data_sharing_consent': 'at_enrollment', + 'enterprise_customer_entitlements': [], + 'identity_provider': None, + 'name': 'EnterpriseCustomer', + 'replace_sensitive_sso_username': True, + 'site': {'domain': 'example.com', 'name': 'example.com'}, + 'uuid': '1cbf230f-f514-4a05-845e-d57b8e29851c' +} diff --git a/openedx/features/enterprise_support/tests/mixins/enterprise.py b/openedx/features/enterprise_support/tests/mixins/enterprise.py index d628640d78ae93b06fbc5dd34880d4d74d42ec89..e3016e7f6dd4940e0eb18195bd322206dddefc17 100644 --- a/openedx/features/enterprise_support/tests/mixins/enterprise.py +++ b/openedx/features/enterprise_support/tests/mixins/enterprise.py @@ -10,6 +10,8 @@ from django.core.cache import cache from django.core.urlresolvers import reverse from django.test import SimpleTestCase +from openedx.features.enterprise_support.tests import FAKE_ENTERPRISE_CUSTOMER + class EnterpriseServiceMockMixin(object): """ @@ -177,7 +179,8 @@ class EnterpriseServiceMockMixin(object): 'enterprise_customer': enterprise_customer_uuid, 'entitlement_id': entitlement_id } - ] + ], + 'replace_sensitive_sso_username': True, }, 'user_id': 5, 'user': { @@ -220,6 +223,7 @@ class EnterpriseTestConsentRequired(SimpleTestCase): Mixin to help test the data_sharing_consent_required decorator. """ + @mock.patch('openedx.features.enterprise_support.api.enterprise_customer_from_api') @mock.patch('openedx.features.enterprise_support.api.enterprise_customer_uuid_for_request') @mock.patch('openedx.features.enterprise_support.api.reverse') @mock.patch('openedx.features.enterprise_support.api.enterprise_enabled') @@ -232,6 +236,7 @@ class EnterpriseTestConsentRequired(SimpleTestCase): mock_enterprise_enabled, mock_reverse, mock_enterprise_customer_uuid_for_request, + mock_enterprise_customer_from_api, status_code=200, ): """ @@ -247,6 +252,7 @@ class EnterpriseTestConsentRequired(SimpleTestCase): mock_reverse.side_effect = mock_consent_reverse mock_enterprise_enabled.return_value = True mock_enterprise_customer_uuid_for_request.return_value = 'fake-uuid' + mock_enterprise_customer_from_api.return_value = FAKE_ENTERPRISE_CUSTOMER # Ensure that when consent is necessary, the user is redirected to the consent page. mock_consent_necessary.return_value = True response = client.get(url) diff --git a/openedx/features/enterprise_support/tests/test_api.py b/openedx/features/enterprise_support/tests/test_api.py index 554f35eb6159c1edefdaad599812b7bb6d3aea63..6a41ab97cbdb0d9e3dd5510ac0a68693c9e61150 100644 --- a/openedx/features/enterprise_support/tests/test_api.py +++ b/openedx/features/enterprise_support/tests/test_api.py @@ -2,8 +2,6 @@ Test the enterprise support APIs. """ -import unittest - import ddt import httpretty import mock @@ -15,7 +13,7 @@ from django.http import HttpResponseRedirect from django.test.utils import override_settings from consent.models import DataSharingConsent -from openedx.core.djangolib.testing.utils import CacheIsolationTestCase +from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, skip_unless_lms from openedx.features.enterprise_support.api import ( ConsentApiClient, ConsentApiServiceClient, @@ -30,16 +28,13 @@ from openedx.features.enterprise_support.api import ( insert_enterprise_pipeline_elements, enterprise_enabled, ) +from openedx.features.enterprise_support.tests import FEATURES_WITH_ENTERPRISE_ENABLED from openedx.features.enterprise_support.tests.factories import EnterpriseCustomerUserFactory from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseServiceMockMixin from openedx.features.enterprise_support.utils import get_cache_key from student.tests.factories import UserFactory -FEATURES_WITH_ENTERPRISE_ENABLED = settings.FEATURES.copy() -FEATURES_WITH_ENTERPRISE_ENABLED['ENABLE_ENTERPRISE_INTEGRATION'] = True - - class MockEnrollment(mock.MagicMock): """ Mock object for an enrollment which has a consistent string representation @@ -51,7 +46,7 @@ class MockEnrollment(mock.MagicMock): @ddt.ddt @override_settings(FEATURES=FEATURES_WITH_ENTERPRISE_ENABLED) -@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') +@skip_unless_lms class TestEnterpriseApi(EnterpriseServiceMockMixin, CacheIsolationTestCase): """ Test enterprise support APIs. diff --git a/openedx/features/enterprise_support/tests/test_middleware.py b/openedx/features/enterprise_support/tests/test_middleware.py new file mode 100644 index 0000000000000000000000000000000000000000..460f20192d433ded9938096b247b08abd481f247 --- /dev/null +++ b/openedx/features/enterprise_support/tests/test_middleware.py @@ -0,0 +1,66 @@ +""" +Tests for Enterprise middleware. +""" + +import mock + +from django.core.urlresolvers import reverse +from django.test import TestCase +from django.test.utils import override_settings + +from openedx.core.djangolib.testing.utils import skip_unless_lms +from openedx.features.enterprise_support.tests import ( + FAKE_ENTERPRISE_CUSTOMER, FEATURES_WITH_ENTERPRISE_ENABLED, + factories +) +from student.tests.factories import UserFactory + + +@override_settings(FEATURES=FEATURES_WITH_ENTERPRISE_ENABLED) +@skip_unless_lms +class EnterpriseMiddlewareTest(TestCase): + """ + Test for `EnterpriseMiddleware`. + """ + + def setUp(self): + """Initiate commonly needed objects.""" + super(EnterpriseMiddlewareTest, self).setUp() + + # Customer & Learner details. + self.user = UserFactory.create(username='username', password='password') + self.enterprise_customer = FAKE_ENTERPRISE_CUSTOMER + self.enterprise_learner = factories.EnterpriseCustomerUserFactory(user_id=self.user.id) + + # Request details. + self.client.login(username='username', password='password') + self.dashboard = reverse('dashboard') + + # Mocks. + patcher = mock.patch('openedx.features.enterprise_support.api.enterprise_customer_from_api') + self.mock_enterprise_customer_from_api = patcher.start() + self.mock_enterprise_customer_from_api.return_value = self.enterprise_customer + self.addCleanup(patcher.stop) + + def test_anonymous_user(self): + """The `enterprise_customer` is not set in the session if the user is anonymous.""" + self.client.logout() + self.client.get(self.dashboard) + assert self.client.session.get('enterprise_customer') is None + + def test_enterprise_customer(self): + """The `enterprise_customer` gets set in the session.""" + self.client.get(self.dashboard) + assert self.client.session.get('enterprise_customer') == self.enterprise_customer + + def test_enterprise_customer_cached(self): + """The middleware doesn't attempt to refill `enterprise_customer` if it already exists in the session.""" + assert not self.mock_enterprise_customer_from_api.called + + # First call populates the session by calling the API. + self.client.get(self.dashboard) + assert self.mock_enterprise_customer_from_api.call_count == 1 + + # Second same call has no need to call the API because the session already contains the data. + self.client.get(self.dashboard) + assert self.mock_enterprise_customer_from_api.call_count == 1 diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 813bf5ee8763e6dfd5808c1f2e1abbeb5456fe0d..4d8d0952bd28d1b43ea3ee00706c69a9a04f5e87 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -115,13 +115,13 @@ edx-django-oauth2-provider==1.2.5 edx-django-release-util==0.3.1 edx-django-sites-extensions==2.3.1 edx-drf-extensions==1.2.5 -edx-enterprise==0.67.5 +edx-enterprise==0.67.6 edx-i18n-tools==0.4.4 edx-milestones==0.1.13 edx-oauth2-provider==1.2.2 edx-opaque-keys[django]==0.4.4 edx-organizations==0.4.10 -edx-proctoring==1.3.9 +edx-proctoring==1.4.0 edx-rest-api-client==1.7.1 edx-search==1.1.0 edx-submissions==2.0.12 diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 1111e4f0ba8043369a13e2683de18f4356333b97..7fbc6f2be88a51ab211f57403fa77a2a7ac3aa9a 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -134,14 +134,14 @@ edx-django-oauth2-provider==1.2.5 edx-django-release-util==0.3.1 edx-django-sites-extensions==2.3.1 edx-drf-extensions==1.2.5 -edx-enterprise==0.67.5 +edx-enterprise==0.67.6 edx-i18n-tools==0.4.4 edx-lint==0.5.4 edx-milestones==0.1.13 edx-oauth2-provider==1.2.2 edx-opaque-keys[django]==0.4.4 edx-organizations==0.4.10 -edx-proctoring==1.3.9 +edx-proctoring==1.4.0 edx-rest-api-client==1.7.1 edx-search==1.1.0 edx-sphinx-theme==1.3.0 @@ -328,4 +328,4 @@ xblock-review==1.1.5 xblock==1.1.1 xmltodict==0.4.1 zendesk==1.1.1 -zope.interface==4.4.3 +zope.interface==4.5.0 diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index a202e693f278952db89f7146b5bb5700492aa543..e3bae8fb261f4ef92ea34c8b85a4889cdc161945 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -129,14 +129,14 @@ edx-django-oauth2-provider==1.2.5 edx-django-release-util==0.3.1 edx-django-sites-extensions==2.3.1 edx-drf-extensions==1.2.5 -edx-enterprise==0.67.5 +edx-enterprise==0.67.6 edx-i18n-tools==0.4.4 edx-lint==0.5.4 edx-milestones==0.1.13 edx-oauth2-provider==1.2.2 edx-opaque-keys[django]==0.4.4 edx-organizations==0.4.10 -edx-proctoring==1.3.9 +edx-proctoring==1.4.0 edx-rest-api-client==1.7.1 edx-search==1.1.0 edx-submissions==2.0.12 @@ -311,4 +311,4 @@ xblock-review==1.1.5 xblock==1.1.1 xmltodict==0.4.1 zendesk==1.1.1 -zope.interface==4.4.3 # via twisted +zope.interface==4.5.0 # via twisted