ENT-3503 | Adds more unit tests for enterprise_support.api, and one TODO for same.

This commit is contained in:
Alex Dusenbery
2020-11-20 11:53:26 -05:00
committed by Alex Dusenbery
parent a3691da03b
commit 2204caaa6b
2 changed files with 244 additions and 13 deletions

View File

@@ -37,7 +37,7 @@ try:
)
from enterprise.api.v1.serializers import EnterpriseCustomerUserReadOnlySerializer
from consent.models import DataSharingConsent, DataSharingConsentTextOverrides
except ImportError:
except ImportError: # pragma: no cover
pass
@@ -586,10 +586,16 @@ def consent_needed_for_course(request, user, course_id, enrollment_exists=False)
)
if not consent_needed:
# TODO: https://openedx.atlassian.net/browse/ENT-3724
# this whole code branch seems to do nothing other than log some misleading info:
# the consent requirement doesn't actually fail. If there's an enterprise or site mismatch,
# we'll still end up in the else branch of "if consent_needed:" below, where
# we'll log that consent is not needed, and ultimately, return False.
# Are we supposed to raise some exceptions in here?
enterprises = [str(learner['enterprise_customer']['uuid']) for learner in enterprise_learner_details]
if str(current_enterprise_uuid) not in enterprises:
LOGGER.info(
LOGGER.info( # pragma: no cover
'[ENTERPRISE DSC] Consent requirement failed due to enterprise mismatch. '
'USER: [%s], CurrentEnterprise: [%s], LearnerEnterprises: [%s]',
user.username,
@@ -599,7 +605,7 @@ def consent_needed_for_course(request, user, course_id, enrollment_exists=False)
else:
domains = [learner['enterprise_customer']['site']['domain'] for learner in enterprise_learner_details]
if not Site.objects.filter(domain__in=domains).filter(id=request.site.id).exists():
LOGGER.info(
LOGGER.info( # pragma: no cover
'[ENTERPRISE DSC] Consent requirement failed due to site mismatch. '
'USER: [%s], RequestSite: [%s], LearnerEnterpriseDomains: [%s]',
user.username,

View File

@@ -15,12 +15,14 @@ from django.test.utils import override_settings
from django.urls import reverse
from edx_django_utils.cache import get_cache_key
from six.moves.urllib.parse import parse_qs
from slumber.exceptions import HttpClientError
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,
EnterpriseApiException,
add_enterprise_customer_to_session,
ConsentApiClient,
ConsentApiServiceClient,
@@ -32,11 +34,15 @@ from openedx.features.enterprise_support.api import (
enterprise_customer_from_api,
enterprise_customer_uuid_for_request,
enterprise_enabled,
get_consent_notification_data,
get_consent_required_courses,
get_dashboard_consent_notification,
get_enterprise_consent_url,
get_enterprise_learner_data_from_api,
get_enterprise_learner_data_from_db,
get_enterprise_learner_portal_enabled_message,
insert_enterprise_pipeline_elements
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 (
@@ -47,6 +53,8 @@ from openedx.features.enterprise_support.tests.mixins.enterprise import Enterpri
from openedx.features.enterprise_support.utils import clear_data_consent_share_cache
from common.djangoapps.student.tests.factories import UserFactory
from enterprise.models import EnterpriseCustomerUser
class MockEnrollment(mock.MagicMock):
"""
@@ -104,6 +112,7 @@ class TestEnterpriseApi(EnterpriseServiceMockMixin, CacheIsolationTestCase):
mocked_jwt_builder.assert_called_once_with(dummy_enterprise_user)
# pylint: disable=protected-access
self.assertEqual(enterprise_api_service_client.client._store['session'].auth.token, 'test-token')
return enterprise_api_service_client
def _assert_get_enterprise_customer(self, api_client, enterprise_api_data_for_mock):
"""
@@ -159,6 +168,36 @@ class TestEnterpriseApi(EnterpriseServiceMockMixin, CacheIsolationTestCase):
"""
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)
setattr(api_client.client, 'enterprise-course-enrollment', mock.Mock())
mock_endpoint = getattr(api_client.client, 'enterprise-course-enrollment')
if should_raise_http_error:
mock_endpoint.post.side_effect = HttpClientError
username = 'spongebob'
course_id = 'burger-flipping-101'
consent_granted = True
if should_raise_http_error:
with self.assertRaises(EnterpriseApiException):
api_client.post_enterprise_course_enrollment(username, course_id, consent_granted)
else:
api_client.post_enterprise_course_enrollment(username, course_id, consent_granted)
mock_endpoint.post.assert_called_once_with(data={
'username': username,
'course_id': course_id,
'consent_granted': consent_granted,
})
@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):
@@ -185,7 +224,18 @@ class TestEnterpriseApi(EnterpriseServiceMockMixin, CacheIsolationTestCase):
Verify that enterprise API consent service client uses the provided
user to authenticate and access enterprise API.
"""
self._assert_api_client_with_user(ConsentApiClient, mock_jwt_builder)
consent_client = self._assert_api_client_with_user(ConsentApiClient, mock_jwt_builder)
consent_client.consent_endpoint = mock.Mock()
kwargs = {
'foo': 'a',
'bar': 'b',
}
consent_client.provide_consent(**kwargs)
consent_client.revoke_consent(**kwargs)
consent_client.consent_endpoint.post.assert_called_once_with(kwargs)
consent_client.consent_endpoint.delete.assert_called_once_with(**kwargs)
@httpretty.activate
@mock.patch('openedx.features.enterprise_support.api.get_enterprise_learner_data_from_db')
@@ -203,7 +253,7 @@ class TestEnterpriseApi(EnterpriseServiceMockMixin, CacheIsolationTestCase):
mock_get_enterprise_learner_data.return_value = self.get_mock_enterprise_learner_results()
self.mock_enterprise_learner_api()
# test not required consent for example non enterprise customer
# test that consent is not required for a non-enterprise customer
self.mock_consent_not_required(user.username, course_id, ec_uuid)
self.assertFalse(consent_needed_for_course(request, user, course_id))
@@ -221,6 +271,31 @@ class TestEnterpriseApi(EnterpriseServiceMockMixin, CacheIsolationTestCase):
self.mock_consent_get(user.username, course_id, ec_uuid)
self.assertFalse(consent_needed_for_course(request, user, course_id))
# test when the enrollment already exists without a consent record existing.
clear_data_consent_share_cache(user.id, course_id)
self.mock_consent_missing(user.username, course_id, ec_uuid)
self.assertFalse(consent_needed_for_course(request, user, course_id, enrollment_exists=True))
@httpretty.activate
@mock.patch('openedx.features.enterprise_support.api.get_enterprise_learner_data_from_db')
def test_consent_needed_for_course_no_learner_data(self, mock_get_enterprise_learner_data):
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_enterprise_learner_data.return_value = None
self.mock_enterprise_learner_api()
# test that consent is not required for a non-enterprise customer
self.mock_consent_not_required(user.username, course_id, ec_uuid)
self.assertFalse(consent_needed_for_course(request, user, course_id))
@httpretty.activate
@mock.patch('enterprise.models.EnterpriseCustomer.catalog_contains_course')
def test_get_consent_required_courses(self, mock_catalog_contains_course):
@@ -245,6 +320,76 @@ class TestEnterpriseApi(EnterpriseServiceMockMixin, CacheIsolationTestCase):
consent_required = get_consent_required_courses(user, [course_id])
self.assertNotIn(course_id, 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)
self.assertIsNone(api_client.fetch_enterprise_learner_data(user))
self.assertFalse(mock_endpoint.called)
@mock.patch('openedx.features.enterprise_support.api.create_jwt_for_user')
def test_fetch_enterprise_learner_data(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=True, username='spongebob')
response = api_client.fetch_enterprise_learner_data(user)
assert mock_endpoint.return_value.get.return_value == response
mock_endpoint.return_value.get.assert_called_once_with(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):
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')
mock_endpoint.return_value.get.side_effect = HttpClientError
mock_get_current_request.return_value.META = {
'PATH_INFO': 'whatever',
}
user = mock.Mock(is_authenticated=True, username='spongebob')
self.assertIsNone(api_client.fetch_enterprise_learner_data(user))
mock_endpoint.return_value.get.assert_called_once_with(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_get_enterprise_learner_data_from_db_no_data(self):
assert [] == get_enterprise_learner_data_from_db(self.user)
def test_get_enterprise_learner_data_from_db(self):
enterprise_customer_user = 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
@httpretty.activate
@mock.patch('openedx.features.enterprise_support.api.get_enterprise_learner_data_from_db')
@mock.patch('openedx.features.enterprise_support.api.EnterpriseCustomer')
@@ -466,12 +611,14 @@ class TestEnterpriseApi(EnterpriseServiceMockMixin, CacheIsolationTestCase):
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,
@@ -490,17 +637,19 @@ class TestEnterpriseApi(EnterpriseServiceMockMixin, CacheIsolationTestCase):
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 = 'info'
return_to = None if is_return_to_null else 'info'
expected_path = request_mock.path if is_return_to_null else '/courses/course-v1:edX+DemoX+Demo_Course/info'
expected_url_args = {
'course_id': ['course-v1:edX+DemoX+Demo_Course'],
'failure_url': ['http://localhost:8000/dashboard?consent_failed=course-v1%3AedX%2BDemoX%2BDemo_Course'],
'enterprise_customer_uuid': ['cf246b88-d5f6-4908-a522-fc307e0b0c59'],
'next': ['http://localhost:8000/courses/course-v1:edX+DemoX+Demo_Course/info']
'next': ['http://localhost:8000{}'.format(expected_path)]
}
actual_url = get_enterprise_consent_url(request_mock, course_id, return_to=return_to)
@@ -671,9 +820,12 @@ class TestEnterpriseApi(EnterpriseServiceMockMixin, CacheIsolationTestCase):
self.assertIsNone(actual_result)
self.assertFalse(mock_learner_data_from_db.called)
@ddt.data(True, False)
@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_learner_portal_message_cache_hit_customer_exists(self, mock_learner_data_from_db):
def test_enterprise_learner_portal_message_cache_hit_customer_exists(
self, enable_learner_portal, mock_learner_data_from_db
):
"""
When customer data exists in the request session and it's a non-empty customer,
then ``get_enterprise_learner_portal_enabled_message()`` should return
@@ -682,7 +834,7 @@ class TestEnterpriseApi(EnterpriseServiceMockMixin, CacheIsolationTestCase):
mock_enterprise_customer = {
'uuid': 'some-uuid',
'name': 'Best Corp',
'enable_learner_portal': True,
'enable_learner_portal': enable_learner_portal,
'slug': 'best-corp',
}
mock_request = mock.Mock(session={
@@ -690,9 +842,12 @@ class TestEnterpriseApi(EnterpriseServiceMockMixin, CacheIsolationTestCase):
})
actual_result = get_enterprise_learner_portal_enabled_message(mock_request)
self.assertIn('custom dashboard for learning', actual_result)
self.assertIn('Best Corp', actual_result)
self.assertFalse(mock_learner_data_from_db.called)
if not enable_learner_portal:
self.assertIsNone(actual_result)
else:
self.assertIn('custom dashboard for learning', actual_result)
self.assertIn('Best Corp', actual_result)
self.assertFalse(mock_learner_data_from_db.called)
@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):
@@ -840,3 +995,73 @@ class TestEnterpriseApi(EnterpriseServiceMockMixin, CacheIsolationTestCase):
add_enterprise_customer_to_session(mock_request, None)
# verify that existing session value should not be updated for un-authenticate user
self.assertEqual(mock_request.session[ENTERPRISE_CUSTOMER_KEY_NAME], enterprise_customer)
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)
self.assertIsNone(title_template)
self.assertIsNone(message_template)
@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(
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()