diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_api.py b/openedx/core/djangoapps/user_api/accounts/tests/test_api.py index 4ed9357c67..9ea3f2e136 100644 --- a/openedx/core/djangoapps/user_api/accounts/tests/test_api.py +++ b/openedx/core/djangoapps/user_api/accounts/tests/test_api.py @@ -4,6 +4,7 @@ Unit tests for behavior that is specific to the api methods (vs. the view method Most of the functionality is covered in test_views.py. """ +import itertools import re import unicodedata @@ -231,20 +232,54 @@ class TestAccountApi(UserSettingsEventTestMixin, EmailTemplateTagMixin, Retireme account_settings = get_account_settings(self.default_request)[0] self.assertEqual(level_of_education, account_settings["level_of_education"]) + @patch('openedx.features.enterprise_support.api.get_enterprise_customer_for_learner') + @patch('openedx.features.enterprise_support.utils.third_party_auth.provider.Registry.get') @ddt.data( - ("email", "new_email@example.com"), - ("name", "New Name"), - ("country", "New Country"), + *itertools.product( + # field_name_value values + (("email", "new_email@example.com"), ("name", "new name"), ("country", "IN")), + # is_enterprise_user + (True, False), + # is_synch_learner_profile_data + (True, False), + ) ) @ddt.unpack - def test_update_validation_error_for_enterprise(self, field_name, field_value): - EnterpriseCustomerUserFactory(user_id=self.user.id) - update_data = {field_name: field_value} + def test_update_validation_error_for_enterprise( + self, + field_name_value, + is_enterprise_user, + is_synch_learner_profile_data, + mock_auth_provider, + mock_customer, + ): + mock_customer.return_value = {} + if is_enterprise_user: + mock_customer.return_value.update({ + 'uuid': 'real-ent-uuid', + 'name': 'Dummy Enterprise', + 'identity_provider': 'saml-ubc' + }) + mock_auth_provider.return_value.sync_learner_profile_data = is_synch_learner_profile_data - with self.assertRaises(AccountValidationError) as validation_error: - update_account_settings(self.user, update_data) - field_errors = validation_error.exception.field_errors - self.assertEqual("This field is not editable via this API", field_errors[field_name]["developer_message"]) + update_data = {field_name_value[0]: field_name_value[1]} + + # prevent actual email change requests + with patch('openedx.core.djangoapps.user_api.accounts.api.student_views.do_email_change_request'): + # expect field un-editability only when both of the following conditions are met + if is_enterprise_user and is_synch_learner_profile_data: + with self.assertRaises(AccountValidationError) as validation_error: + update_account_settings(self.user, update_data) + field_errors = validation_error.exception.field_errors + self.assertEqual( + "This field is not editable via this API", + field_errors[field_name_value[0]]["developer_message"], + ) + else: + update_account_settings(self.user, update_data) + account_settings = get_account_settings(self.default_request)[0] + if field_name_value[0] != "email": + self.assertEqual(field_name_value[1], account_settings[field_name_value[0]]) def test_update_error_validating(self): """Test that AccountValidationError is thrown if incorrect values are supplied.""" diff --git a/openedx/features/enterprise_support/utils.py b/openedx/features/enterprise_support/utils.py index f80c456f52..84dc18e78c 100644 --- a/openedx/features/enterprise_support/utils.py +++ b/openedx/features/enterprise_support/utils.py @@ -242,22 +242,13 @@ def update_account_settings_context_for_enterprise(context, enterprise_customer) """ enterprise_context = { - 'enterprise_name': None, - 'sync_learner_profile_data': False, + 'enterprise_name': enterprise_customer['name'] if enterprise_customer else None, + 'sync_learner_profile_data': _get_sync_learner_profile_data(enterprise_customer), 'edx_support_url': configuration_helpers.get_value('SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK), 'enterprise_readonly_account_fields': { 'fields': settings.ENTERPRISE_READONLY_ACCOUNT_FIELDS } } - - if enterprise_customer: - enterprise_context['enterprise_name'] = enterprise_customer['name'] - identity_provider = third_party_auth.provider.Registry.get( - provider_id=enterprise_customer['identity_provider'], - ) - if identity_provider: - enterprise_context['sync_learner_profile_data'] = identity_provider.sync_learner_profile_data - context.update(enterprise_context) @@ -265,7 +256,27 @@ def get_enterprise_readonly_account_fields(user): """ Returns a set of account fields that are read-only for enterprise users. """ - return set(settings.ENTERPRISE_READONLY_ACCOUNT_FIELDS) if is_enterprise_learner(user) else set() + # TODO circular dependency between enterprise_support.api and enterprise_support.utils + from openedx.features.enterprise_support.api import get_enterprise_customer_for_learner + enterprise_customer = get_enterprise_customer_for_learner(user) + + sync_learner_profile_data = _get_sync_learner_profile_data(enterprise_customer) + return set(settings.ENTERPRISE_READONLY_ACCOUNT_FIELDS) if sync_learner_profile_data else set() + + +def _get_sync_learner_profile_data(enterprise_customer): + """ + Returns whether the configuration of the given enterprise customer supports + synching learner profile data. + """ + if enterprise_customer: + identity_provider = third_party_auth.provider.Registry.get( + provider_id=enterprise_customer['identity_provider'], + ) + if identity_provider: + return identity_provider.sync_learner_profile_data + + return False def get_enterprise_learner_generic_name(request): @@ -278,6 +289,7 @@ def get_enterprise_learner_generic_name(request): # Prevent a circular import. This function makes sense to be in this module though. And see function description. from openedx.features.enterprise_support.api import enterprise_customer_for_request enterprise_customer = enterprise_customer_for_request(request) + return ( enterprise_customer['name'] + 'Learner' if enterprise_customer and enterprise_customer['replace_sensitive_sso_username']