1408 lines
62 KiB
Python
1408 lines
62 KiB
Python
"""
|
|
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 '<MockEnrollment course_id={}>'.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()
|