diff --git a/openedx/features/enterprise_support/api.py b/openedx/features/enterprise_support/api.py index 2074b7a563..f06df135a3 100644 --- a/openedx/features/enterprise_support/api.py +++ b/openedx/features/enterprise_support/api.py @@ -595,107 +595,116 @@ def enterprise_customer_for_request(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 + Wraps the enterprise app check to determine if the user needs to grant data sharing permissions before accessing a course. """ LOGGER.info( - "Determining if user [{username}] must consent to data sharing for course [{course_id}]".format( + "[ENTERPRISE DSC] Determining if user [{username}] must consent to data sharing for course" + " [{course_id}]".format( username=user.username, course_id=course_id ) ) - enterprise_learner_details = get_enterprise_learner_data_from_db(user) - enterprise_customer_uuid = None - if enterprise_learner_details: - enterprise_customer = enterprise_learner_details[0]['enterprise_customer'] - enterprise_customer_uuid = enterprise_customer['uuid'] - - consent_cache_key = get_data_consent_share_cache_key(user.id, course_id, enterprise_customer_uuid) - data_sharing_consent_needed_cache = TieredCache.get_cached_response(consent_cache_key) - if data_sharing_consent_needed_cache.is_found and data_sharing_consent_needed_cache.value == 0: + active_enterprise_learner_info = get_active_enterprise_customer_user(user) + if not active_enterprise_learner_info: + # user is not linked to any enterprise so return False LOGGER.info( - "Consent from user [{username}] is not needed for course [{course_id}]. The DSC cache was checked," - " and the value was 0.".format( + "[ENTERPRISE DSC] Consent from user [{username}] is not needed for course [{course_id}]." + " The user is not linked to an enterprise.".format( username=user.username, course_id=course_id ) ) return False - consent_needed = False - if not enterprise_learner_details: + active_enterprise_customer = active_enterprise_learner_info['enterprise_customer'] + + # Check if DSC required from cache + consent_cache_key = get_data_consent_share_cache_key(user.id, course_id, active_enterprise_customer['uuid']) + data_sharing_consent_needed_cache = TieredCache.get_cached_response(consent_cache_key) + if data_sharing_consent_needed_cache.is_found and data_sharing_consent_needed_cache.value == 0: LOGGER.info( - "Consent from user [{username}] is not needed for course [{course_id}]. The user is not linked to an" - " enterprise.".format( + "[ENTERPRISE DSC] Consent from user [{username}] is not needed for course [{course_id}]. " + "The DSC cache was checked and the value was 0.".format( username=user.username, course_id=course_id ) ) - else: - client = ConsentApiClient(user=request.user) - current_enterprise_uuid = enterprise_customer_uuid_for_request(request) - consent_needed = any( - str(current_enterprise_uuid) == str(learner['enterprise_customer']['uuid']) - and Site.objects.get(domain=learner['enterprise_customer']['site']['domain']) == request.site - and client.consent_required( + return False + + # Check if DSC enabled by the enterprise customer + enable_data_sharing_consent = active_enterprise_customer['enable_data_sharing_consent'] + if not enable_data_sharing_consent: + # enterprise has disabled DSC so no need to move forward + LOGGER.info( + "[ENTERPRISE DSC] DSC is disabled for enterprise customer [{slug}]. Consent from user [{username}] is not " + "needed for course [{course_id}]".format( + slug=active_enterprise_customer['slug'], username=user.username, - course_id=course_id, - enterprise_customer_uuid=current_enterprise_uuid, - enrollment_exists=enrollment_exists, + course_id=course_id ) - for learner in enterprise_learner_details ) - - if not consent_needed: - # TODO: https://openedx.atlassian.net/browse/ENT-3724 - # this whole code branch seems to do nothing other than log some misleading info: - # the consent requirement doesn't actually fail. If there's an enterprise or site mismatch, - # we'll still end up in the else branch of "if consent_needed:" below, where - # we'll log that consent is not needed, and ultimately, return False. - # Are we supposed to raise some exceptions in here? - enterprises = [str(learner['enterprise_customer']['uuid']) for learner in enterprise_learner_details] - - if str(current_enterprise_uuid) not in enterprises: - LOGGER.info( # pragma: no cover - '[ENTERPRISE DSC] Enterprise mismatch. USER: [%s], CurrentEnterprise: [%s], UserEnterprises: [%s]', - user.username, - current_enterprise_uuid, - enterprises - ) - else: - domains = [learner['enterprise_customer']['site']['domain'] for learner in enterprise_learner_details] - if not Site.objects.filter(domain__in=domains).filter(id=request.site.id).exists(): - LOGGER.info( # pragma: no cover - '[ENTERPRISE DSC] Site mismatch. USER: [%s], RequestSite: [%s], LearnerEnterpriseDomains: [%s]', - user.username, - request.site, - domains - ) - - if consent_needed: - LOGGER.info( - "Consent from user [{username}] is needed for course [{course_id}]. The user's current enterprise" - " required data sharing consent, and it has not been given.".format( - username=user.username, - course_id=course_id - ) - ) - else: - LOGGER.info( - "Consent from user [{username}] is not needed for course [{course_id}]. The user's current enterprise " - "does not require data sharing consent.".format( - username=user.username, - course_id=course_id - ) - ) - - if not consent_needed: - # Set an ephemeral item in the cache to prevent us from needing - # to make a Consent API request every time this function is called. TieredCache.set_all_tiers(consent_cache_key, 0, settings.DATA_CONSENT_SHARE_CACHE_TIMEOUT) + return False - return consent_needed + # check if the request enterprise and learner's active enterprise matches + current_enterprise_uuid = enterprise_customer_uuid_for_request(request) + active_enterprise_match = str(current_enterprise_uuid) == str(active_enterprise_customer['uuid']) + if not active_enterprise_match: + LOGGER.info( + '[ENTERPRISE DSC] Enterprise mismatch. USER: [{username}], RequestEnterprise: [{current_enterprise_uuid}], ' + 'LearnerEnterprise: [{active_enterprise_customer}]'.format( + username=user.username, + current_enterprise_uuid=current_enterprise_uuid, + active_enterprise_customer=active_enterprise_customer['uuid'], + ) + ) + TieredCache.set_all_tiers(consent_cache_key, 0, settings.DATA_CONSENT_SHARE_CACHE_TIMEOUT) + return False + + # check if the enterprise and learner's site matches + enterprise_domain = Site.objects.get(domain=active_enterprise_customer['site']['domain']) + enterprise_and_learner_have_same_domain = enterprise_domain == request.site + if not enterprise_and_learner_have_same_domain: + LOGGER.info( + '[ENTERPRISE DSC] Site mismatch. USER: [{username}], RequestSite: [{request_site}], ' + 'LearnerEnterpriseDomain: [{enterprise_domain}]'.format( + username=user.username, + request_site=request.site, + enterprise_domain=enterprise_domain + ) + ) + TieredCache.set_all_tiers(consent_cache_key, 0, settings.DATA_CONSENT_SHARE_CACHE_TIMEOUT) + return False + + # check if consent required + client = ConsentApiClient(user=request.user) + consent_required = client.consent_required( + username=user.username, + course_id=course_id, + enterprise_customer_uuid=current_enterprise_uuid, + enrollment_exists=enrollment_exists, + ) + if not consent_required: + LOGGER.info( + "[ENTERPRISE DSC] Consent from user [{username}] is not needed for course [{course_id}]. The user's current" + " enterprise does not require data sharing consent.".format( + username=user.username, + course_id=course_id + ) + ) + TieredCache.set_all_tiers(consent_cache_key, 0, settings.DATA_CONSENT_SHARE_CACHE_TIMEOUT) + return False + + LOGGER.info( + "[ENTERPRISE DSC] Consent from user [{username}] is needed for course [{course_id}]. The user's " + "current enterprise requires data sharing consent, and it has not been given.".format( + username=user.username, + course_id=course_id + ) + ) + return True @enterprise_is_enabled(otherwise=set()) @@ -792,6 +801,23 @@ def get_enterprise_learner_data_from_db(user): return serializer.data +@enterprise_is_enabled(otherwise=None) +def get_active_enterprise_customer_user(user): + """ + Query the database to return active enterprise customer user and serialize the result. There can only be one active + EnterpriseCustomerUser instance against a user_id. + """ + if user.is_authenticated: + try: + enterprise_customer_user = EnterpriseCustomerUser.objects.get(user_id=user.id, active=True) + except EnterpriseCustomerUser.DoesNotExist: + LOGGER.info( + "Active EnterpriseCustomerUser for user [{username}] does not exist".format(username=user.username) + ) + return None + return EnterpriseCustomerUserReadOnlySerializer(instance=enterprise_customer_user).data + + @enterprise_is_enabled(otherwise=[]) def get_data_sharing_consents(user): """ diff --git a/openedx/features/enterprise_support/tests/mixins/enterprise.py b/openedx/features/enterprise_support/tests/mixins/enterprise.py index c4f716f85d..8b8f420ddd 100644 --- a/openedx/features/enterprise_support/tests/mixins/enterprise.py +++ b/openedx/features/enterprise_support/tests/mixins/enterprise.py @@ -142,6 +142,56 @@ class EnterpriseServiceMockMixin: required=False, ) + def get_mock_active_enterprise_learner_details( + self, + learner_id=1, + enterprise_customer_uuid='cf246b88-d5f6-4908-a522-fc307e0b0c59', + enable_audit_enrollment=False, + enable_data_sharing_consent=True, + slug='test-shib', + data_sharing_consent_records=None, + site_domain='example.com', + ): + """ + Helper function to format enterprise learner API response. + """ + mock_result = { + 'id': learner_id, + 'enterprise_customer': { + 'uuid': enterprise_customer_uuid, + 'name': 'TestShib', + 'slug': slug, + 'active': True, + 'site': { + 'domain': site_domain, + 'name': 'example.com' + }, + 'enable_data_sharing_consent': enable_data_sharing_consent, + 'enforce_data_sharing_consent': 'at_login', + 'enable_audit_enrollment': enable_audit_enrollment, + }, + 'active': True, + 'user_id': learner_id, + 'user': { + 'id': learner_id, + 'username': 'verified', + 'first_name': '', + 'last_name': '', + 'email': 'verified@example.com', + 'is_staff': True, + 'is_active': True, + 'date_joined': '2016-09-01T19:18:26.026495Z' + }, + 'data_sharing_consent_records': data_sharing_consent_records or [], + 'groups': [], + 'created': '2016-09-01T19:18:26.026495Z', + 'invite_key': '', + 'role_assignments': [ + 'enterprise_learner' + ] + } + return mock_result + def get_mock_enterprise_learner_results( self, entitlement_id=1, diff --git a/openedx/features/enterprise_support/tests/test_api.py b/openedx/features/enterprise_support/tests/test_api.py index 81f416f8cc..8629caae90 100644 --- a/openedx/features/enterprise_support/tests/test_api.py +++ b/openedx/features/enterprise_support/tests/test_api.py @@ -6,6 +6,7 @@ from unittest import mock import ddt import httpretty import pytest +from testfixtures import LogCapture from consent.models import DataSharingConsent from django.conf import settings from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user @@ -13,7 +14,7 @@ from django.core.cache import cache from django.http import HttpResponseRedirect from django.test.utils import override_settings from django.urls import reverse -from edx_django_utils.cache import get_cache_key +from edx_django_utils.cache import get_cache_key, TieredCache from enterprise.models import EnterpriseCustomerUser # lint-amnesty, pylint: disable=wrong-import-order from requests.exceptions import HTTPError from six.moves.urllib.parse import parse_qs @@ -38,6 +39,7 @@ from openedx.features.enterprise_support.api import ( enterprise_customer_from_session_or_learner_data, enterprise_customer_uuid_for_request, enterprise_enabled, + get_active_enterprise_customer_user, get_consent_notification_data, get_consent_required_courses, get_dashboard_consent_notification, @@ -57,7 +59,9 @@ 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 clear_data_consent_share_cache +from openedx.features.enterprise_support.utils import clear_data_consent_share_cache, get_data_consent_share_cache_key + +LOGGER_NAME = "edx.enterprise_helpers" class MockEnrollment(mock.MagicMock): @@ -244,8 +248,9 @@ class TestEnterpriseApi(EnterpriseServiceMockMixin, CacheIsolationTestCase): mock_client.delete.assert_called_once_with(consent_client.consent_endpoint, json=kwargs) @httpretty.activate - @mock.patch('openedx.features.enterprise_support.api.get_enterprise_learner_data_from_db') - def test_consent_needed_for_course(self, mock_get_enterprise_learner_data): + @mock.patch('openedx.features.enterprise_support.api.get_active_enterprise_customer_user') + def test_consent_needed_for_course_no_active_enterprise_user(self, mock_get_active_enterprise_customer_user): + """Tests that consent is not needed for non-enterprise user.""" user = UserFactory(username='janedoe') request = mock.MagicMock( user=user, @@ -254,37 +259,28 @@ class TestEnterpriseApi(EnterpriseServiceMockMixin, CacheIsolationTestCase): COOKIES={}, GET={}, ) - ec_uuid = 'cf246b88-d5f6-4908-a522-fc307e0b0c59' course_id = 'fake-course' - mock_get_enterprise_learner_data.return_value = self.get_mock_enterprise_learner_results() - self.mock_enterprise_learner_api() + mock_get_active_enterprise_customer_user.return_value = None - # test that consent is not required for a non-enterprise customer - self.mock_consent_not_required(user.username, course_id, ec_uuid) - assert not consent_needed_for_course(request, user, course_id) - - # test required and missing consent for example now he becomes a enterprise customer - self.mock_consent_missing(user.username, course_id, ec_uuid) - # still result should be False as it has been stored in cache "Not to show consent", so it will confirm that - # cache is working fine - assert not consent_needed_for_course(request, user, course_id) - # Removing cache - clear_data_consent_share_cache(user.id, course_id, ec_uuid) - # Now test again - assert consent_needed_for_course(request, user, course_id) - - # test after consent permission is granted - self.mock_consent_get(user.username, course_id, ec_uuid) - assert not consent_needed_for_course(request, user, course_id) - - # test when the enrollment already exists without a consent record existing. - clear_data_consent_share_cache(user.id, course_id, ec_uuid) - self.mock_consent_missing(user.username, course_id, ec_uuid) - assert not consent_needed_for_course(request, user, course_id, enrollment_exists=True) + # test that consent is not required for a non-enterprise learner and appropriate message is logged + expected_message = "[ENTERPRISE DSC] Consent from user [{username}] is not needed for course [{course_id}]." \ + " The user is not linked to an enterprise." + with LogCapture(LOGGER_NAME) as log: + assert not consent_needed_for_course(request, user, course_id) + log.check_present( + ( + LOGGER_NAME, + 'INFO', + expected_message.format(username=user.username, course_id=course_id), + ) + ) @httpretty.activate - @mock.patch('openedx.features.enterprise_support.api.get_enterprise_learner_data_from_db') - def test_consent_needed_for_course_no_learner_data(self, mock_get_enterprise_learner_data): + @mock.patch('openedx.features.enterprise_support.api.get_active_enterprise_customer_user') + def test_consent_needed_for_course_dsc_cache_found(self, mock_get_active_enterprise_customer_user): + """ + Tests that a consent is not needed when a DSC record is already found in cache and it is equal to 0. + """ user = UserFactory(username='janedoe') request = mock.MagicMock( user=user, @@ -295,12 +291,246 @@ class TestEnterpriseApi(EnterpriseServiceMockMixin, CacheIsolationTestCase): ) ec_uuid = 'cf246b88-d5f6-4908-a522-fc307e0b0c59' course_id = 'fake-course' - mock_get_enterprise_learner_data.return_value = None - self.mock_enterprise_learner_api() + mock_get_active_enterprise_customer_user.return_value = self.get_mock_active_enterprise_learner_details( + learner_id=user.id, + enterprise_customer_uuid=ec_uuid, + ) - # test that consent is not required for a non-enterprise customer - self.mock_consent_not_required(user.username, course_id, ec_uuid) - assert not consent_needed_for_course(request, user, course_id) + # store a DSC record value in cache + consent_cache_key = get_data_consent_share_cache_key(user.id, course_id, ec_uuid) + TieredCache.set_all_tiers(consent_cache_key, 0, settings.DATA_CONSENT_SHARE_CACHE_TIMEOUT) + + # test that consent is not required if DSC record is already found in cache. + expected_message = "[ENTERPRISE DSC] Consent from user [{username}] is not needed for course [{course_id}]. " \ + "The DSC cache was checked and the value was 0." + with LogCapture(LOGGER_NAME) as log: + assert not consent_needed_for_course(request, user, course_id) + log.check_present( + ( + LOGGER_NAME, + 'INFO', + expected_message.format(username=user.username, course_id=course_id), + ) + ) + # clearing up + clear_data_consent_share_cache(user.id, course_id, ec_uuid) + + @httpretty.activate + @mock.patch('openedx.features.enterprise_support.api.get_active_enterprise_customer_user') + def test_consent_needed_for_course_dsc_not_enabled(self, mock_get_active_enterprise_customer_user): + """ + Tests that a consent is not needed when enterprise customer has disabled DSC. + """ + user = UserFactory(username='janedoe') + request = mock.MagicMock( + user=user, + site=SiteFactory(domain="example.com"), + session={}, + COOKIES={}, + GET={}, + ) + + ec_uuid = 'cf246b88-d5f6-4908-a522-fc307e0b0c59' + course_id = 'fake-course' + ent_slug = 'test-slug' + mock_get_active_enterprise_customer_user.return_value = self.get_mock_active_enterprise_learner_details( + learner_id=user.id, + enterprise_customer_uuid=ec_uuid, + enable_data_sharing_consent=False, + slug=ent_slug + ) + + # test that consent is not required if DSC is disabled for the enterprise. + expected_message = "[ENTERPRISE DSC] DSC is disabled for enterprise customer [{slug}]. " \ + "Consent from user [{username}] is not needed for course [{course_id}]" + with LogCapture(LOGGER_NAME) as log: + assert not consent_needed_for_course(request, user, course_id) + log.check_present( + ( + LOGGER_NAME, + 'INFO', + expected_message.format(slug=ent_slug, username=user.username, course_id=course_id) + ) + ) + + @httpretty.activate + @mock.patch('openedx.features.enterprise_support.api.get_active_enterprise_customer_user') + @mock.patch('openedx.features.enterprise_support.api.enterprise_customer_uuid_for_request') + def test_consent_needed_for_course_enterprise_mismatch( + self, + enterprise_customer_uuid_for_request_mock, + mock_get_active_enterprise_customer_user + ): + """ + Tests that a consent is not needed when current request enterprise and learner's active + enterprise does not match. + """ + user = UserFactory(username='janedoe') + request = mock.MagicMock( + user=user, + site=SiteFactory(domain='example.com'), + session={}, + COOKIES={}, + GET={}, + ) + + learner_enterprise = 'cf246b88-d5f6-4908-a522-fc307e0b0c59' + current_request_enterprise = '1234-23434-vfdfa-242sdczz' + course_id = 'fake-course' + enterprise_customer_uuid_for_request_mock.return_value = current_request_enterprise + mock_get_active_enterprise_customer_user.return_value = self.get_mock_active_enterprise_learner_details( + learner_id=user.id, + enterprise_customer_uuid=learner_enterprise, + ) + + expected_message = '[ENTERPRISE DSC] Enterprise mismatch. USER: [{username}], RequestEnterprise: ' \ + '[{current_enterprise_uuid}], LearnerEnterprise: [{active_enterprise_customer}]' + with LogCapture(LOGGER_NAME) as log: + assert not consent_needed_for_course(request, user, course_id) + log.check_present( + ( + LOGGER_NAME, + 'INFO', + expected_message.format( + username=user.username, + current_enterprise_uuid=current_request_enterprise, + active_enterprise_customer=learner_enterprise + ) + ) + ) + + @httpretty.activate + @mock.patch('openedx.features.enterprise_support.api.get_active_enterprise_customer_user') + @mock.patch('openedx.features.enterprise_support.api.enterprise_customer_uuid_for_request') + def test_consent_needed_for_course_site_mismatch( + self, + enterprise_customer_uuid_for_request_mock, + mock_get_active_enterprise_customer_user, + ): + """ + Tests that a consent is not needed when enterprise customer and learner's site domain does not match. + """ + user = UserFactory(username='janedoe') + request_site_domain = 'site-mismatch.com' + request = mock.MagicMock( + user=user, + site=SiteFactory(domain=request_site_domain), + session={}, + COOKIES={}, + GET={}, + ) + + ec_uuid = 'cf246b88-d5f6-4908-a522-fc307e0b0c59' + course_id = 'fake-course' + ent_site = SiteFactory(domain='ent-site.com') + enterprise_customer_uuid_for_request_mock.return_value = ec_uuid + mock_get_active_enterprise_customer_user.return_value = self.get_mock_active_enterprise_learner_details( + learner_id=user.id, + enterprise_customer_uuid=ec_uuid, + site_domain=str(ent_site.domain), + ) + + expected_message = "[ENTERPRISE DSC] Site mismatch. USER: [{username}], RequestSite: [{request_site}], " \ + "LearnerEnterpriseDomain: [{enterprise_domain}]" + with LogCapture(LOGGER_NAME) as log: + assert not consent_needed_for_course(request, user, course_id) + log.check_present( + ( + LOGGER_NAME, + 'INFO', + expected_message.format( + username=user.username, + request_site=request_site_domain, + enterprise_domain=ent_site.domain + ) + ) + ) + + @httpretty.activate + @mock.patch('openedx.features.enterprise_support.api.get_active_enterprise_customer_user') + @mock.patch('openedx.features.enterprise_support.api.ConsentApiClient.consent_required') + @mock.patch('openedx.features.enterprise_support.api.enterprise_customer_uuid_for_request') + def test_consent_needed_for_course_when_consent_is_not_required( + self, + enterprise_customer_uuid_for_request_mock, + mock_consent_required, + mock_get_active_enterprise_customer_user + ): + """ + Tests that a consent is not needed when learner is not already enrolled and the enterprise requires consent. + """ + mock_consent_required.return_value = False + user = UserFactory(username='janedoe') + request = mock.MagicMock( + user=user, + site=SiteFactory(domain="example.com"), + session={}, + COOKIES={}, + GET={}, + ) + ec_uuid = 'cf246b88-d5f6-4908-a522-fc307e0b0c59' + course_id = 'fake-course' + enterprise_customer_uuid_for_request_mock.return_value = ec_uuid + + mock_get_active_enterprise_customer_user.return_value = self.get_mock_active_enterprise_learner_details( + learner_id=user.id, + enterprise_customer_uuid=ec_uuid, + ) + + expected_message = "[ENTERPRISE DSC] Consent from user [{username}] is not needed for course [{course_id}]. " \ + "The user's current enterprise does not require data sharing consent." + with LogCapture(LOGGER_NAME) as log: + assert not consent_needed_for_course(request, user, course_id) + log.check_present( + ( + LOGGER_NAME, + 'INFO', + expected_message.format(username=user.username, course_id=course_id) + ) + ) + + @httpretty.activate + @mock.patch('openedx.features.enterprise_support.api.get_active_enterprise_customer_user') + @mock.patch('openedx.features.enterprise_support.api.ConsentApiClient.consent_required') + @mock.patch('openedx.features.enterprise_support.api.enterprise_customer_uuid_for_request') + def test_consent_needed_for_course_when_consent_is_required( + self, + enterprise_customer_uuid_for_request_mock, + mock_consent_required, + mock_get_active_enterprise_customer_user + ): + """ + Tests that a consent is needed when learner is not already enrolled and the enterprise requires consent. + """ + mock_consent_required.return_value = True + user = UserFactory(username='janedoe') + request = mock.MagicMock( + user=user, + site=SiteFactory(domain="example.com"), + session={}, + COOKIES={}, + GET={}, + ) + ec_uuid = 'cf246b88-d5f6-4908-a522-fc307e0b0c59' + course_id = 'fake-course' + enterprise_customer_uuid_for_request_mock.return_value = ec_uuid + + mock_get_active_enterprise_customer_user.return_value = self.get_mock_active_enterprise_learner_details( + learner_id=user.id, + enterprise_customer_uuid=ec_uuid, + ) + + expected_message = "[ENTERPRISE DSC] Consent from user [{username}] is needed for course [{course_id}]. " \ + "The user's current enterprise requires data sharing consent, and it has not been given." + with LogCapture(LOGGER_NAME) as log: + assert consent_needed_for_course(request, user, course_id) + log.check_present( + ( + LOGGER_NAME, + 'INFO', + expected_message.format(username=user.username, course_id=course_id) + ) + ) @httpretty.activate @mock.patch('enterprise.models.EnterpriseCustomer.catalog_contains_course') @@ -417,6 +647,35 @@ class TestEnterpriseApi(EnterpriseServiceMockMixin, CacheIsolationTestCase): user_data = get_enterprise_learner_data_from_db(self.user)[0]['user'] assert user_data['username'] == self.user.username + @ddt.data(True, False) + @mock.patch('openedx.features.enterprise_support.api.enterprise_enabled') + def test_get_active_enterprise_customer_user(self, is_enterprise_enabled, mock_enterprise_enabled): + """Tests that get_active_enterprise_customer_user utility returns correct user.""" + mock_enterprise_enabled.return_value = is_enterprise_enabled + EnterpriseCustomerUserFactory(user_id=self.user.id, active=True) + if not is_enterprise_enabled: + assert not get_active_enterprise_customer_user(self.user) + else: + user_data = get_active_enterprise_customer_user(self.user) + assert user_data['user']['username'] == self.user.username + assert user_data['active'] + + def test_get_active_enterprise_customer_user_no_user_found(self): + """ + Test that get_active_enterprise_customer_user utility returns None if active user not found. + And logs the appropriate message. + """ + expected_logger_message = "Active EnterpriseCustomerUser for user [{username}] does not exist" + with LogCapture(LOGGER_NAME) as log: + assert not get_active_enterprise_customer_user(self.user) + log.check_present( + ( + LOGGER_NAME, + 'INFO', + expected_logger_message.format(username=self.user.username), + ) + ) + @ddt.data(True, False) @mock.patch('openedx.features.enterprise_support.api.enterprise_enabled') def test_get_data_sharing_consents(self, is_enterprise_enabled, mock_enterprise_enabled):