""" Test the enterprise support APIs. """ 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 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, 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 from common.djangoapps.student.tests.factories import UserFactory from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, skip_unless_lms from openedx.features.enterprise_support.api import ( _CACHE_MISS, ENTERPRISE_CUSTOMER_KEY_NAME, ConsentApiClient, ConsentApiServiceClient, EnterpriseApiClient, EnterpriseApiException, EnterpriseApiServiceClient, activate_learner_enterprise, add_enterprise_customer_to_session, consent_needed_for_course, data_sharing_consent_required, enterprise_customer_for_request, enterprise_customer_from_api, enterprise_customer_from_session, 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, get_data_sharing_consents, get_enterprise_consent_url, get_enterprise_course_enrollments, get_enterprise_learner_data_from_api, get_enterprise_learner_data_from_db, get_enterprise_learner_portal_enabled_message, insert_enterprise_pipeline_elements, unlink_enterprise_user_from_idp, ) from openedx.features.enterprise_support.tests import FEATURES_WITH_ENTERPRISE_ENABLED from openedx.features.enterprise_support.tests.factories import ( EnterpriseCourseEnrollmentFactory, EnterpriseCustomerIdentityProviderFactory, EnterpriseCustomerUserFactory, ) from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseServiceMockMixin 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): """ Mock object for an enrollment which has a consistent string representation suitable for use in ddt parameters. """ def __repr__(self): return ''.format(getattr(self, 'course_id', None)) @ddt.ddt @override_settings(FEATURES=FEATURES_WITH_ENTERPRISE_ENABLED) @skip_unless_lms class TestEnterpriseApi(EnterpriseServiceMockMixin, CacheIsolationTestCase): """ Test enterprise support APIs. """ ENABLED_CACHES = ['default'] @classmethod def setUpTestData(cls): cls.user = UserFactory.create( username=settings.ENTERPRISE_SERVICE_WORKER_USERNAME, email='ent_worker@example.com', password='password123', ) super().setUpTestData() def _assert_api_service_client(self, api_client, mocked_jwt_builder): """ Verify that the provided api client uses the enterprise service user to generate JWT token for auth. """ mocked_jwt_builder.return_value = 'test-token' enterprise_service_user = User.objects.get(username=settings.ENTERPRISE_SERVICE_WORKER_USERNAME) enterprise_api_service_client = api_client() mocked_jwt_builder.assert_called_once_with(enterprise_service_user) # pylint: disable=protected-access assert enterprise_api_service_client.client.auth.token == 'test-token' def _assert_api_client_with_user(self, api_client, mocked_jwt_builder): """ Verify that the provided api client uses the expected user to generate JWT token for auth. """ mocked_jwt_builder.return_value = 'test-token' dummy_enterprise_user = UserFactory.create( username='dummy-enterprise-user', email='dummy-enterprise-user@example.com', password='password123', ) enterprise_api_service_client = api_client(dummy_enterprise_user) mocked_jwt_builder.assert_called_once_with(dummy_enterprise_user) # pylint: disable=protected-access assert enterprise_api_service_client.client.auth.token == 'test-token' return enterprise_api_service_client def _assert_get_enterprise_customer(self, api_client, enterprise_api_data_for_mock): """ DRY method to verify caching for get enterprise customer method. """ cache_key = get_cache_key( resource='enterprise-customer', resource_id=enterprise_api_data_for_mock['uuid'], username=settings.ENTERPRISE_SERVICE_WORKER_USERNAME, ) self.mock_get_enterprise_customer(enterprise_api_data_for_mock['uuid'], enterprise_api_data_for_mock, 200) self._assert_get_enterprise_customer_with_cache(api_client, enterprise_api_data_for_mock, cache_key) def _assert_get_enterprise_customer_with_cache(self, api_client, enterprise_customer_data, cache_key): """ DRY method to verify that get enterprise customer response is cached. """ cached_enterprise_customer = cache.get(cache_key) assert cached_enterprise_customer is None enterprise_customer = api_client.get_enterprise_customer(enterprise_customer_data['uuid']) assert enterprise_customer_data == enterprise_customer cached_enterprise_customer = cache.get(cache_key) assert cached_enterprise_customer == enterprise_customer @httpretty.activate @mock.patch('openedx.features.enterprise_support.api.create_jwt_for_user') def test_enterprise_api_client_with_service_user(self, mock_jwt_builder): """ Verify that enterprise API service client uses enterprcreate_jwt_for_userise service user by default to authenticate and access enterprise API. """ self._assert_api_service_client(EnterpriseApiServiceClient, mock_jwt_builder) # Verify that enterprise customer data is cached properly for the # enterprise api client. enterprise_api_client = EnterpriseApiServiceClient() enterprise_api_data_for_mock_1 = {'name': 'dummy-enterprise-customer-1', 'uuid': 'enterprise-uuid-1'} self._assert_get_enterprise_customer(enterprise_api_client, enterprise_api_data_for_mock_1) # Now try to get enterprise customer for another enterprise and verify # that enterprise api client returns data according to the provided # enterprise UUID. enterprise_api_data_for_mock_2 = {'name': 'dummy-enterprise-customer-2', 'uuid': 'enterprise-uuid-2'} self._assert_get_enterprise_customer(enterprise_api_client, enterprise_api_data_for_mock_2) @httpretty.activate @mock.patch('openedx.features.enterprise_support.api.create_jwt_for_user') def test_enterprise_api_client_with_user(self, mock_jwt_builder): """ Verify that enterprise API client uses the provided user to authenticate and access enterprise API. """ self._assert_api_client_with_user(EnterpriseApiClient, mock_jwt_builder) @ddt.data(True, False) @httpretty.activate @mock.patch('openedx.features.enterprise_support.api.create_jwt_for_user') def test_enterprise_api_client_with_user_post_enrollment(self, should_raise_http_error, mock_jwt_builder): """ Verify that enterprise API client uses the provided user to authenticate and access enterprise API. """ api_client = self._assert_api_client_with_user(EnterpriseApiClient, mock_jwt_builder) mock_client = mock.Mock() api_client.client = mock_client if should_raise_http_error: mock_client.post.side_effect = HTTPError username = 'spongebob' course_id = 'burger-flipping-101' if should_raise_http_error: with pytest.raises(EnterpriseApiException): api_client.post_enterprise_course_enrollment(username, course_id) else: api_client.post_enterprise_course_enrollment(username, course_id) mock_client.post.assert_called_once_with( f"{api_client.base_api_url}enterprise-course-enrollment/", data={ 'username': username, 'course_id': course_id, } ) @mock.patch('openedx.features.enterprise_support.api.enterprise_customer_uuid_for_request') @mock.patch('openedx.features.enterprise_support.api.EnterpriseApiClient') def test_enterprise_customer_from_api_cache_miss(self, mock_client_class, mock_uuid_from_request): mock_uuid_from_request.return_value = _CACHE_MISS mock_request = mock.Mock() actual_result = enterprise_customer_from_api(mock_request) assert actual_result is None assert not mock_client_class.called @httpretty.activate @mock.patch('openedx.features.enterprise_support.api.create_jwt_for_user') def test_enterprise_consent_api_client_with_service_user(self, mock_jwt_builder): """ Verify that enterprise API consent service client uses enterprise service user by default to authenticate and access enterprise API. """ self._assert_api_service_client(ConsentApiServiceClient, mock_jwt_builder) @httpretty.activate @mock.patch('openedx.features.enterprise_support.api.create_jwt_for_user') def test_enterprise_consent_api_client_with_user(self, mock_jwt_builder): """ Verify that enterprise API consent service client uses the provided user to authenticate and access enterprise API. """ consent_client = self._assert_api_client_with_user(ConsentApiClient, mock_jwt_builder) mock_client = mock.Mock() consent_client.client = mock_client kwargs = { 'foo': 'a', 'bar': 'b', } consent_client.provide_consent(**kwargs) consent_client.revoke_consent(**kwargs) mock_client.post.assert_called_once_with(consent_client.consent_endpoint, json=kwargs) mock_client.delete.assert_called_once_with(consent_client.consent_endpoint, json=kwargs) @httpretty.activate @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, site=SiteFactory(domain="example.com"), session={}, COOKIES={}, GET={}, ) course_id = 'fake-course' mock_get_active_enterprise_customer_user.return_value = None # 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_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, site=SiteFactory(domain="example.com"), session={}, COOKIES={}, GET={}, ) ec_uuid = 'cf246b88-d5f6-4908-a522-fc307e0b0c59' course_id = 'fake-course' mock_get_active_enterprise_customer_user.return_value = self.get_mock_active_enterprise_learner_details( learner_id=user.id, enterprise_customer_uuid=ec_uuid, ) # 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') def test_get_consent_required_courses(self, mock_catalog_contains_course): mock_catalog_contains_course.return_value = True user = UserFactory() enterprise_customer_user = EnterpriseCustomerUserFactory(user_id=user.id) course_id = 'fake-course' data_sharing_consent = DataSharingConsent( course_id=course_id, enterprise_customer=enterprise_customer_user.enterprise_customer, username=user.username, granted=False ) data_sharing_consent.save() consent_required = get_consent_required_courses(user, [course_id]) assert course_id in consent_required # now grant consent and call our method again data_sharing_consent.granted = True data_sharing_consent.save() consent_required = get_consent_required_courses(user, [course_id]) assert course_id not in consent_required def test_consent_not_required_for_non_enterprise_user(self): user = UserFactory() course_id = 'fake-course' consent_required_courses = get_consent_required_courses(user, [course_id]) assert set() == consent_required_courses @mock.patch('openedx.features.enterprise_support.api.create_jwt_for_user') def test_fetch_enterprise_learner_data_unauthenticated(self, mock_jwt_builder): api_client = self._assert_api_client_with_user(EnterpriseApiClient, mock_jwt_builder) setattr(api_client.client, 'enterprise-learner', mock.Mock()) mock_endpoint = getattr(api_client.client, 'enterprise-learner') user = mock.Mock(is_authenticated=False) assert api_client.fetch_enterprise_learner_data(user) is None assert not mock_endpoint.called @mock.patch('openedx.features.enterprise_support.api.create_jwt_for_user') def test_fetch_enterprise_learner_data(self, mock_jwt_builder): """ Test EnterpriseApiClient's fetch_enterprise_learner_data method. """ api_client = self._assert_api_client_with_user(EnterpriseApiClient, mock_jwt_builder) mock_client = mock.Mock() api_client.client = mock_client user = mock.Mock(is_authenticated=True, username='spongebob') response = api_client.fetch_enterprise_learner_data(user) assert mock_client.get.return_value.json.return_value == response mock_client.get.assert_called_once_with( f"{api_client.base_api_url}enterprise-learner/", params={'username': user.username}, ) @mock.patch('openedx.features.enterprise_support.api.get_current_request') @mock.patch('openedx.features.enterprise_support.api.create_jwt_for_user') def test_fetch_enterprise_learner_data_http_error(self, mock_jwt_builder, mock_get_current_request): """ Test error handling for the EnterpriseApiClient's fetch_enterprise_learner_data method. """ api_client = self._assert_api_client_with_user(EnterpriseApiClient, mock_jwt_builder) mock_client = mock.Mock() mock_client.get.side_effect = HTTPError api_client.client = mock_client mock_get_current_request.return_value.META = { 'PATH_INFO': 'whatever', } user = mock.Mock(is_authenticated=True, username='spongebob') assert api_client.fetch_enterprise_learner_data(user) is None url = f"{api_client.base_api_url}enterprise-learner/" mock_client.get.assert_called_once_with(url, params={'username': user.username}) @mock.patch('openedx.features.enterprise_support.api.EnterpriseApiClient') def test_get_enterprise_learner_data_from_api(self, mock_api_client_class): user = mock.Mock(is_authenticated=True) mock_client = mock_api_client_class.return_value mock_client.fetch_enterprise_learner_data.return_value = { 'results': 'the-learner-data', } learner_data = get_enterprise_learner_data_from_api(user) assert 'the-learner-data' == learner_data mock_api_client_class.assert_called_once_with(user=user) mock_client.fetch_enterprise_learner_data.assert_called_once_with(user) def test_activate_learner_enterprise(self): """ Test enterprise is activated successfully for user """ request_mock = mock.MagicMock(session={}, user=self.user) enterprise_customer_user = EnterpriseCustomerUserFactory(user_id=self.user.id) enterprise_customer_uuid = enterprise_customer_user.enterprise_customer.uuid activate_learner_enterprise(request_mock, self.user, enterprise_customer_uuid) assert request_mock.session['enterprise_customer']['uuid'] == str(enterprise_customer_uuid) def test_get_enterprise_learner_data_from_db_no_data(self): assert not get_enterprise_learner_data_from_db(self.user) def test_get_enterprise_learner_data_from_db(self): EnterpriseCustomerUserFactory(user_id=self.user.id) 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): mock_enterprise_enabled.return_value = is_enterprise_enabled enterprise_customer_user = EnterpriseCustomerUserFactory(user_id=self.user.id) if not is_enterprise_enabled: assert get_data_sharing_consents(self.user) == [] else: course_id = 'fake-course' data_sharing_consent = DataSharingConsent( course_id=course_id, enterprise_customer=enterprise_customer_user.enterprise_customer, username=self.user.username, granted=False ) data_sharing_consent.save() data_sharing_consents = get_data_sharing_consents(self.user) assert len(data_sharing_consents) == 1 assert data_sharing_consents[0].id == data_sharing_consent.id @ddt.data(True, False) @mock.patch('openedx.features.enterprise_support.api.enterprise_enabled') def test_get_enterprise_course_enrollments(self, is_enterprise_enabled, mock_enterprise_enabled): mock_enterprise_enabled.return_value = is_enterprise_enabled enterprise_customer_user = EnterpriseCustomerUserFactory(user_id=self.user.id) if not is_enterprise_enabled: assert get_enterprise_course_enrollments(self.user) == [] else: ece = EnterpriseCourseEnrollmentFactory(enterprise_customer_user=enterprise_customer_user) enterprise_course_enrollments = get_enterprise_course_enrollments(self.user) assert len(enterprise_course_enrollments) == 1 assert enterprise_course_enrollments[0].id == ece.id @httpretty.activate @mock.patch('openedx.features.enterprise_support.api.get_enterprise_learner_data_from_db') @mock.patch('openedx.features.enterprise_support.api.EnterpriseCustomer') @mock.patch('openedx.features.enterprise_support.api.get_partial_pipeline') @mock.patch('openedx.features.enterprise_support.api.Registry') def test_enterprise_customer_for_request( self, mock_registry, mock_partial, mock_enterprise_customer_model, mock_get_enterprise_learner_data, ): def mock_get_enterprise_customer(**kwargs): uuid = kwargs.get('enterprise_customer_identity_providers__provider_id') if uuid: return mock.MagicMock(uuid=uuid, user=self.user) raise Exception dummy_request = mock.MagicMock(session={}, user=self.user) mock_enterprise_customer_model.objects.get.side_effect = mock_get_enterprise_customer mock_enterprise_customer_model.DoesNotExist = Exception mock_partial.return_value = True mock_registry.get_from_pipeline.return_value.provider_id = 'real-ent-uuid' # Verify that the method `enterprise_customer_for_request` returns # expected enterprise customer against the requesting user. self.mock_get_enterprise_customer('real-ent-uuid', {'real': 'enterprisecustomer'}, 200) enterprise_customer = enterprise_customer_for_request(dummy_request) assert enterprise_customer == {'real': 'enterprisecustomer'} httpretty.reset() # Verify that the method `enterprise_customer_for_request` returns no # enterprise customer if the enterprise customer API throws 404. del dummy_request.session['enterprise_customer'] self.mock_get_enterprise_customer('real-ent-uuid', {'detail': 'Not found.'}, 404) enterprise_customer = enterprise_customer_for_request(dummy_request) assert enterprise_customer is None httpretty.reset() # Verify that the method `enterprise_customer_for_request` returns # expected enterprise customer against the requesting user even if # the third-party auth pipeline has no `provider_id`. mock_registry.get_from_pipeline.return_value.provider_id = None self.mock_get_enterprise_customer('real-ent-uuid', {'real': 'enterprisecustomer'}, 200) mock_request = mock.MagicMock( GET={'enterprise_customer': 'real-ent-uuid'}, COOKIES={}, session={}, user=self.user ) enterprise_customer = enterprise_customer_for_request(mock_request) assert enterprise_customer == {'real': 'enterprisecustomer'} # Verify that the method `enterprise_customer_for_request` returns # expected enterprise customer against the requesting user even if # the third-party auth pipeline has no `provider_id` but there is # enterprise customer UUID in the cookie. mock_request = mock.MagicMock( GET={}, COOKIES={settings.ENTERPRISE_CUSTOMER_COOKIE_NAME: 'real-ent-uuid'}, session={}, user=self.user ) enterprise_customer = enterprise_customer_for_request(mock_request) assert enterprise_customer == {'real': 'enterprisecustomer'} # Verify that the method `enterprise_customer_for_request` returns # expected enterprise customer against the requesting user if # data is cached only in the request session mock_registry.get_from_pipeline.return_value.provider_id = None self.mock_get_enterprise_customer('real-ent-uuid', {'real': 'enterprisecustomer'}, 200) mock_request = mock.MagicMock( GET={}, COOKIES={}, session={'enterprise_customer': {'real': 'enterprisecustomer'}}, user=self.user ) enterprise_customer = enterprise_customer_for_request(mock_request) assert enterprise_customer == {'real': 'enterprisecustomer'} # Verify that we can still get enterprise customer from enterprise # learner API even if we are unable to get it from preferred sources, # e.g. url query parameters, third-party auth pipeline, enterprise # cookie, or session. mock_get_enterprise_learner_data.return_value = [{'enterprise_customer': {'uuid': 'real-ent-uuid'}}] mock_request = mock.MagicMock( GET={}, COOKIES={}, session={}, user=self.user, site=1 ) enterprise_customer = enterprise_customer_for_request(mock_request) assert enterprise_customer == {'real': 'enterprisecustomer'} def test_enterprise_customer_for_request_with_session(self): """ Verify enterprise_customer_for_request stores and retrieves data from session appropriately """ dummy_request = mock.MagicMock(session={}, user=self.user) enterprise_data = {'name': 'dummy-enterprise-customer', 'uuid': '8dc65e66-27c9-447b-87ff-ede6d66e3a5d'} # Verify enterprise customer data fetched from API when it is not available in session with mock.patch( 'openedx.features.enterprise_support.api.enterprise_customer_from_api', return_value=enterprise_data ): assert dummy_request.session.get('enterprise_customer') is None enterprise_customer = enterprise_customer_for_request(dummy_request) assert enterprise_customer == enterprise_data assert dummy_request.session.get('enterprise_customer') == enterprise_data # Verify enterprise customer data fetched from session for subsequent calls with mock.patch( 'openedx.features.enterprise_support.api.enterprise_customer_from_api', return_value=enterprise_data ) as mock_enterprise_customer_from_api, mock.patch( 'openedx.features.enterprise_support.api.enterprise_customer_from_session', return_value=enterprise_data ) as mock_enterprise_customer_from_session: enterprise_customer = enterprise_customer_for_request(dummy_request) assert enterprise_customer == enterprise_data assert mock_enterprise_customer_from_api.called is False assert mock_enterprise_customer_from_session.called is True # Verify enterprise customer data fetched from session for subsequent calls # with unauthenticated user in SAML case del dummy_request.user with mock.patch( 'openedx.features.enterprise_support.api.enterprise_customer_from_api', return_value=enterprise_data ) as mock_enterprise_customer_from_api, mock.patch( 'openedx.features.enterprise_support.api.enterprise_customer_from_session', return_value=enterprise_data ) as mock_enterprise_customer_from_session: enterprise_customer = enterprise_customer_for_request(dummy_request) assert enterprise_customer == enterprise_data assert mock_enterprise_customer_from_api.called is False assert mock_enterprise_customer_from_session.called is True def check_data_sharing_consent(self, consent_required=False, consent_url=None): """ Used to test the data_sharing_consent_required view decorator. """ # Test by wrapping a function that has the expected signature @data_sharing_consent_required def view_func(request, course_id, *args, **kwargs): """ Return the function arguments, so they can be tested. """ return ((request, course_id,) + args, kwargs) # Call the wrapped function args = (mock.MagicMock(), 'course-id', 'another arg', 'and another') kwargs = dict(a=1, b=2, c=3) response = view_func(*args, **kwargs) # If consent required, then the response should be a redirect to the consent URL, and the view function would # not be called. if consent_required: assert isinstance(response, HttpResponseRedirect) assert response.url == consent_url # pylint: disable=no-member # Otherwise, the view function should have been called with the expected arguments. else: assert response == (args, kwargs) @mock.patch('openedx.features.enterprise_support.api.enterprise_enabled') @mock.patch('openedx.features.enterprise_support.api.consent_needed_for_course') def test_data_consent_required_enterprise_disabled(self, mock_consent_necessary, mock_enterprise_enabled): """ Verify that the wrapped view is called directly when enterprise integration is disabled, without checking for course consent necessary. """ mock_enterprise_enabled.return_value = False self.check_data_sharing_consent(consent_required=False) mock_enterprise_enabled.assert_called_once() mock_consent_necessary.assert_not_called() @mock.patch('openedx.features.enterprise_support.api.enterprise_enabled') @mock.patch('openedx.features.enterprise_support.api.consent_needed_for_course') def test_no_course_data_consent_required(self, mock_consent_necessary, mock_enterprise_enabled): """ Verify that the wrapped view is called directly when enterprise integration is enabled, and no course consent is required. """ mock_enterprise_enabled.return_value = True mock_consent_necessary.return_value = False self.check_data_sharing_consent(consent_required=False) mock_enterprise_enabled.assert_called_once() mock_consent_necessary.assert_called_once() @mock.patch('openedx.features.enterprise_support.api.enterprise_enabled') @mock.patch('openedx.features.enterprise_support.api.consent_needed_for_course') @mock.patch('openedx.features.enterprise_support.api.get_enterprise_consent_url') def test_data_consent_required(self, mock_get_consent_url, mock_consent_necessary, mock_enterprise_enabled): """ Verify that the wrapped function returns a redirect to the consent URL when enterprise integration is enabled, and course consent is required. """ mock_enterprise_enabled.return_value = True mock_consent_necessary.return_value = True consent_url = '/abc/def' mock_get_consent_url.return_value = consent_url self.check_data_sharing_consent(consent_required=True, consent_url=consent_url) mock_get_consent_url.assert_called_once() @ddt.data(True, False) @httpretty.activate @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.consent_needed_for_course') def test_get_enterprise_consent_url( self, is_return_to_null, needed_for_course_mock, reverse_mock, enterprise_customer_uuid_for_request_mock, ): """ Verify that get_enterprise_consent_url correctly builds URLs. """ def fake_reverse(*args, **kwargs): if args[0] == 'grant_data_sharing_permissions': return '/enterprise/grant_data_sharing_permissions' return reverse(*args, **kwargs) enterprise_customer_uuid_for_request_mock.return_value = 'cf246b88-d5f6-4908-a522-fc307e0b0c59' reverse_mock.side_effect = fake_reverse needed_for_course_mock.return_value = True request_mock = mock.MagicMock( user=self.user, path='/request_path', build_absolute_uri=lambda x: 'http://localhost:8000' + x # Don't do it like this in prod. Ever. ) course_id = 'course-v1:edX+DemoX+Demo_Course' return_to = None if is_return_to_null else 'courseware' if is_return_to_null: expected_path = request_mock.path else: expected_path = '/courses/course-v1:edX+DemoX+Demo_Course/courseware' expected_url_args = { 'course_id': ['course-v1:edX+DemoX+Demo_Course'], 'source': ['lms'], 'failure_url': ['http://localhost:8000/dashboard?consent_failed=course-v1%3AedX%2BDemoX%2BDemo_Course'], 'enterprise_customer_uuid': ['cf246b88-d5f6-4908-a522-fc307e0b0c59'], 'next': [f'http://localhost:8000{expected_path}'] } actual_url = get_enterprise_consent_url(request_mock, course_id, return_to=return_to) actual_url_args = parse_qs(actual_url.split('/enterprise/grant_data_sharing_permissions?')[1]) assert actual_url_args == expected_url_args @ddt.data( (False, {'real': 'enterprise', 'uuid': ''}, 'course', [], [], "", ""), (True, {}, 'course', [], [], "", ""), (True, {'real': 'enterprise'}, None, [], [], "", ""), (True, {'name': 'GriffCo', 'uuid': ''}, 'real-course', [], [], "", ""), (True, {'name': 'GriffCo', 'uuid': ''}, 'real-course', [MockEnrollment(course_id='other-id')], [], "", ""), ( True, {'name': 'GriffCo', 'uuid': 'real-uuid'}, 'real-course', [ MockEnrollment( course_id='real-course', course_overview=mock.MagicMock( display_name='My Cool Course' ) ) ], [ 'If you have concerns about sharing your data, please contact your administrator at GriffCo.', 'Enrollment in My Cool Course was not complete.' ], "", "" ), ( True, {'name': 'GriffCo', 'uuid': 'real-uuid'}, 'real-course', [ MockEnrollment( course_id='real-course', course_overview=mock.MagicMock( display_name='My Cool Course' ) ) ], [ 'If you have concerns about sharing your data, please contact your administrator at GriffCo.', 'Enrollment in My Cool Course was not complete.' ], "Title from DataSharingConsentTextOverrides model in consent app", "Message from DataSharingConsentTextOverrides model in consent app" ), ) @ddt.unpack @mock.patch('openedx.features.enterprise_support.api.ConsentApiClient') @mock.patch('openedx.features.enterprise_support.api.enterprise_customer_for_request') @mock.patch('openedx.features.enterprise_support.api.get_consent_notification_data') def test_get_dashboard_consent_notification( self, consent_return_value, enterprise_customer, course_id, enrollments, expected_substrings, notification_title, notification_message, consent_notification_data, ec_for_request, consent_client_class ): request = mock.MagicMock( GET={'consent_failed': course_id} ) consent_notification_data.return_value = notification_title, notification_message consent_client = consent_client_class.return_value consent_client.consent_required.return_value = consent_return_value ec_for_request.return_value = enterprise_customer user = mock.MagicMock() notification_string = get_dashboard_consent_notification( request, user, enrollments, ) if notification_message and notification_title: assert notification_title in notification_string assert notification_message in notification_string elif expected_substrings: for substr in expected_substrings: assert substr in notification_string else: assert notification_string == '' @override_settings(FEATURES=dict(ENABLE_ENTERPRISE_INTEGRATION=False)) def test_utils_with_enterprise_disabled(self): """ Test that disabling the enterprise integration flag causes the utilities to return the expected default values. """ assert not enterprise_enabled() assert insert_enterprise_pipeline_elements(None) is None def test_utils_with_enterprise_enabled(self): """ Test that enabling enterprise integration (which is currently on by default) causes the the utilities to return the expected values. """ assert enterprise_enabled() pipeline = ['abc', 'social_core.pipeline.social_auth.load_extra_data', 'def'] insert_enterprise_pipeline_elements(pipeline) assert pipeline == \ [ 'abc', 'enterprise.tpa_pipeline.handle_enterprise_logistration', 'social_core.pipeline.social_auth.load_extra_data', 'def' ] @mock.patch('openedx.features.enterprise_support.api.get_enterprise_learner_data_from_db') def test_enterprise_customer_from_session_or_db_cache_miss_no_customer(self, mock_learner_data_from_db): """ When no customer data exists in the request session _and_ no customer is associated with the requesting user, then ``enterprise_customer_from_session_or_learner_data()`` should return None. """ mock_request = mock.Mock(session={}) mock_learner_data_from_db.return_value = None actual_result = enterprise_customer_from_session_or_learner_data(mock_request) assert actual_result is None mock_learner_data_from_db.assert_called_once_with(mock_request.user) @mock.patch('openedx.features.enterprise_support.api.get_enterprise_learner_data_from_db') @override_settings(ENTERPRISE_LEARNER_PORTAL_BASE_URL='http://localhost') def test_enterprise_customer_from_session_or_db_cache_miss_customer_exists(self, mock_learner_data_from_db): """ When no customer data exists in the request session but a customer is associated with the requesting user, then ``enterprise_customer_from_session_or_learner_data()`` should return the customer metadata. """ mock_request = mock.Mock(session={}) mock_enterprise_customer = { 'uuid': 'some-uuid', 'name': 'Best Corp', 'enable_learner_portal': True, 'slug': 'best-corp', } mock_learner_data_from_db.return_value = [ { 'enterprise_customer': mock_enterprise_customer, }, ] actual_result = enterprise_customer_from_session_or_learner_data(mock_request) assert actual_result['uuid'] == mock_enterprise_customer['uuid'] mock_learner_data_from_db.assert_called_once_with(mock_request.user) # assert we cached the enterprise customer data in the request session after fetching it assert mock_request.session.get(ENTERPRISE_CUSTOMER_KEY_NAME) == mock_enterprise_customer @mock.patch('openedx.features.enterprise_support.api.get_enterprise_learner_data_from_db') def test_enterprise_customer_from_session_or_db_cache_hit_no_customer(self, mock_learner_data_from_db): """ When customer data exists in the request session but it's null/empty, then ``enterprise_customer_from_session_or_learner_data()`` should return None. """ mock_request = mock.Mock(session={ ENTERPRISE_CUSTOMER_KEY_NAME: None, }) actual_result = enterprise_customer_from_session_or_learner_data(mock_request) assert actual_result is None assert not mock_learner_data_from_db.called @ddt.data(True, False) @override_settings(ENTERPRISE_LEARNER_PORTAL_BASE_URL='http://localhost') def test_enterprise_learner_portal_message_customer_exists(self, enable_learner_portal): """ When an enterprise customer exists with learner portal enabled, then ``get_enterprise_learner_portal_enabled_message()`` should return an appropriate message for that customer. """ mock_enterprise_customer = { 'uuid': 'some-uuid', 'name': 'Best Corp', 'enable_learner_portal': enable_learner_portal, 'slug': 'best-corp', } actual_result = get_enterprise_learner_portal_enabled_message(mock_enterprise_customer) if not enable_learner_portal: assert actual_result is None else: assert 'To access the courses available to you through' in actual_result assert 'Best Corp' in actual_result def test_enterprise_learner_portal_message_no_customer(self): """ When an enterprise customer does not exists, then ``get_enterprise_learner_portal_enabled_message()`` should return None. """ actual_result = get_enterprise_learner_portal_enabled_message(None) assert actual_result is None @mock.patch('openedx.features.enterprise_support.api.get_partial_pipeline', return_value=None) def test_customer_uuid_for_request_sso_provider_id_customer_exists(self, mock_partial_pipeline): mock_idp = EnterpriseCustomerIdentityProviderFactory.create() mock_customer = mock_idp.enterprise_customer mock_request = mock.Mock( GET={'tpa_hint': mock_idp.provider_id}, COOKIES={}, session={}, ) actual_uuid = enterprise_customer_uuid_for_request(mock_request) expected_uuid = mock_customer.uuid assert expected_uuid == actual_uuid mock_partial_pipeline.assert_called_once_with(mock_request) assert ENTERPRISE_CUSTOMER_KEY_NAME not in mock_request.session @mock.patch('openedx.features.enterprise_support.api.get_enterprise_learner_data_from_db') @mock.patch('openedx.features.enterprise_support.api.get_partial_pipeline', return_value=None) def test_customer_uuid_for_request_sso_provider_id_customer_non_existent_but_exist_in_db( self, mock_partial_pipeline, mock_data_from_db, ): enterprise_customer_uuid = 'adab9a14-f263-42e6-a234-db707026c4a6' mock_request = mock.Mock( GET={'tpa_hint': 'my-third-party-auth'}, COOKIES={}, session={}, ) mock_data_from_db.return_value = [ {'enterprise_customer': {'uuid': enterprise_customer_uuid}}, ] actual_uuid = enterprise_customer_uuid_for_request(mock_request) assert actual_uuid == enterprise_customer_uuid mock_partial_pipeline.assert_called_once_with(mock_request) assert ENTERPRISE_CUSTOMER_KEY_NAME in mock_request.session @mock.patch('openedx.features.enterprise_support.api.get_partial_pipeline', return_value=None) def test_enterprise_uuid_for_request_from_query_params(self, mock_partial_pipeline): expected_uuid = 'my-uuid' mock_request = mock.Mock( GET={ENTERPRISE_CUSTOMER_KEY_NAME: expected_uuid}, COOKIES={}, session={}, ) actual_uuid = enterprise_customer_uuid_for_request(mock_request) assert expected_uuid == actual_uuid mock_partial_pipeline.assert_called_once_with(mock_request) assert ENTERPRISE_CUSTOMER_KEY_NAME not in mock_request.session @mock.patch('openedx.features.enterprise_support.api.get_partial_pipeline', return_value=None) def test_enterprise_uuid_for_request_from_cookies(self, mock_partial_pipeline): expected_uuid = 'my-uuid' mock_request = mock.Mock( GET={}, COOKIES={settings.ENTERPRISE_CUSTOMER_COOKIE_NAME: expected_uuid}, session={}, ) actual_uuid = enterprise_customer_uuid_for_request(mock_request) assert expected_uuid == actual_uuid mock_partial_pipeline.assert_called_once_with(mock_request) assert ENTERPRISE_CUSTOMER_KEY_NAME not in mock_request.session @mock.patch('openedx.features.enterprise_support.api.get_partial_pipeline', return_value=None) def test_enterprise_uuid_for_request_from_session(self, mock_partial_pipeline): expected_uuid = 'my-uuid' mock_request = mock.Mock( GET={}, COOKIES={}, session={ENTERPRISE_CUSTOMER_KEY_NAME: {'uuid': expected_uuid}}, ) actual_uuid = enterprise_customer_uuid_for_request(mock_request) assert expected_uuid == actual_uuid mock_partial_pipeline.assert_called_once_with(mock_request) assert {'uuid': expected_uuid} == mock_request.session.get(ENTERPRISE_CUSTOMER_KEY_NAME) @mock.patch('openedx.features.enterprise_support.api.get_enterprise_learner_data_from_db') @mock.patch('openedx.features.enterprise_support.api.get_partial_pipeline', return_value=None) def test_enterprise_uuid_for_request_cache_miss_but_exists_in_db(self, mock_partial_pipeline, mock_data_from_db): mock_request = mock.Mock( GET={}, COOKIES={}, session={}, ) mock_data_from_db.return_value = [ {'enterprise_customer': {'uuid': 'my-uuid'}}, ] actual_uuid = enterprise_customer_uuid_for_request(mock_request) expected_uuid = 'my-uuid' assert expected_uuid == actual_uuid mock_partial_pipeline.assert_called_once_with(mock_request) mock_data_from_db.assert_called_once_with(mock_request.user) assert {'uuid': 'my-uuid'} == mock_request.session[ENTERPRISE_CUSTOMER_KEY_NAME] @ddt.data(True, False) @mock.patch('openedx.features.enterprise_support.api.get_enterprise_learner_data_from_db', return_value=None) @mock.patch('openedx.features.enterprise_support.api.get_partial_pipeline', return_value=None) def test_enterprise_uuid_for_request_cache_miss_non_existent( self, is_user_authenticated, mock_partial_pipeline, mock_data_from_db ): mock_request = mock.Mock( GET={}, COOKIES={}, session={}, ) mock_request.user.is_authenticated = is_user_authenticated actual_uuid = enterprise_customer_uuid_for_request(mock_request) assert actual_uuid is None mock_partial_pipeline.assert_called_once_with(mock_request) if is_user_authenticated: mock_data_from_db.assert_called_once_with(mock_request.user) assert mock_request.session[ENTERPRISE_CUSTOMER_KEY_NAME] is None else: assert not mock_data_from_db.called assert ENTERPRISE_CUSTOMER_KEY_NAME not in mock_request.session def test_enterprise_customer_from_session(self): mock_request = mock.Mock( GET={}, COOKIES={}, session={}, ) mock_request.user.is_authenticated = True enterprise_customer = { 'name': 'abc', 'uuid': 'cf246b88-d5f6-4908-a522-fc307e0b0c59' } # set enterprise customer info with authenticate user add_enterprise_customer_to_session(mock_request, enterprise_customer) assert mock_request.session[ENTERPRISE_CUSTOMER_KEY_NAME] == enterprise_customer # Now try to set info with un-authenticated user mock_request.user.is_authenticated = False add_enterprise_customer_to_session(mock_request, None) # verify that existing session value should not be updated for un-authenticate user assert mock_request.session[ENTERPRISE_CUSTOMER_KEY_NAME] == enterprise_customer @ddt.data(None, object()) def test_enterprise_customer_from_session_no_session_CACHE_MISS(self, request): assert enterprise_customer_from_session(request) == _CACHE_MISS def test_get_consent_notification_data_no_overrides(self): enterprise_customer = { 'name': 'abc', 'uuid': 'cf246b88-d5f6-4908-a522-fc307e0b0c59' } title_template, message_template = get_consent_notification_data(enterprise_customer) assert title_template is None assert message_template is None @mock.patch('openedx.features.enterprise_support.api.DataSharingConsentTextOverrides') def test_get_consent_notification_data(self, mock_override_model): enterprise_customer = { 'name': 'abc', 'uuid': 'cf246b88-d5f6-4908-a522-fc307e0b0c59' } mock_override = mock.Mock( declined_notification_title='the title', declined_notification_message='the message', ) mock_override_model.objects.get.return_value = mock_override title_template, message_template = get_consent_notification_data(enterprise_customer) assert mock_override.declined_notification_title == title_template assert mock_override.declined_notification_message == message_template @mock.patch('openedx.features.enterprise_support.api.Registry') @mock.patch('openedx.features.enterprise_support.api.enterprise_customer_for_request') def test_unlink_enterprise_user_from_idp(self, mock_customer_from_request, mock_registry): customer_idp = EnterpriseCustomerIdentityProviderFactory.create( provider_id='the-provider', ) customer = customer_idp.enterprise_customer customer_user = EnterpriseCustomerUserFactory.create( # lint-amnesty, pylint: disable=unused-variable enterprise_customer=customer, user_id=self.user.id, ) mock_customer_from_request.return_value = { 'uuid': customer.uuid, } mock_registry.get_enabled_by_backend_name.return_value = [ mock.Mock(provider_id='the-provider') ] request = mock.Mock() unlink_enterprise_user_from_idp(request, self.user, idp_backend_name='the-backend-name') assert 0 == EnterpriseCustomerUser.objects.filter(user_id=self.user.id).count() @mock.patch('openedx.features.enterprise_support.api.Registry') @mock.patch('openedx.features.enterprise_support.api.enterprise_customer_for_request') def test_unlink_enterprise_user_from_idp_no_customer_user(self, mock_customer_from_request, mock_registry): customer_idp = EnterpriseCustomerIdentityProviderFactory.create( provider_id='the-provider', ) customer = customer_idp.enterprise_customer mock_customer_from_request.return_value = { 'uuid': customer.uuid, } mock_registry.get_enabled_by_backend_name.return_value = [ mock.Mock(provider_id='the-provider') ] request = mock.Mock() unlink_enterprise_user_from_idp(request, self.user, idp_backend_name='the-backend-name') assert 0 == EnterpriseCustomerUser.objects.filter(user_id=self.user.id).count()