diff --git a/lms/envs/common.py b/lms/envs/common.py index d0d2eeae76..1057c798f6 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -4996,6 +4996,8 @@ RETIREMENT_STATES = [ 'COMPLETE', ] +EXTRA_SERVICES_TO_RETIRE_FROM = {} + USERNAME_REPLACEMENT_WORKER = "REPLACE WITH VALID USERNAME" ############## Settings for Microfrontends ######################### diff --git a/lms/envs/test.py b/lms/envs/test.py index 3c4bb95649..13799a3c40 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -662,6 +662,8 @@ SUBSCRIPTIONS_TRIAL_LENGTH = 7 CSRF_TRUSTED_ORIGINS = ['.example.com'] CSRF_TRUSTED_ORIGINS_WITH_SCHEME = ['https://*.example.com'] +EXTRA_SERVICES_TO_RETIRE_FROM = {} + # values are already updated above with default CSRF_TRUSTED_ORIGINS values but in # case of new django version these values will override. if django.VERSION[0] >= 4: # for greater than django 3.2 use with schemes. diff --git a/scripts/user_retirement/tests/test_retire_one_learner.py b/scripts/user_retirement/tests/test_retire_one_learner.py index 844325ea3e..e620d850ee 100644 --- a/scripts/user_retirement/tests/test_retire_one_learner.py +++ b/scripts/user_retirement/tests/test_retire_one_learner.py @@ -4,6 +4,8 @@ Test the retire_one_learner.py script from unittest.mock import DEFAULT, patch from click.testing import CliRunner +from django.conf import settings +from django.test.utils import override_settings from scripts.user_retirement.retire_one_learner import ( END_STATES, @@ -19,6 +21,13 @@ from scripts.user_retirement.tests.retirement_helpers import fake_config_file, g from scripts.user_retirement.utils.exception import HttpDoesNotExistException +@override_settings(EXTRA_SERVICES_TO_RETIRE_FROM=[ + { + 'name': 'MOCK_SERVICE', + 'service_base_url': 'http://fake_service_base_url', + 'retirement_url_path': 'fake_retirement_url_path' + } +]) def _call_script(username, fetch_ecom_segment_id=False): """ Call the retired learner script with the given username and a generic, temporary config file. diff --git a/scripts/user_retirement/tests/utils/test_edx_api.py b/scripts/user_retirement/tests/utils/test_edx_api.py index 037ea68628..2c86c2cb97 100644 --- a/scripts/user_retirement/tests/utils/test_edx_api.py +++ b/scripts/user_retirement/tests/utils/test_edx_api.py @@ -544,9 +544,9 @@ class TestLicenseManagerApi(OAuth2Mixin, unittest.TestCase): ) -class TestCommerceCoordinatorApi(OAuth2Mixin, unittest.TestCase): +class TestGenericApi(OAuth2Mixin, unittest.TestCase): """ - Test the edX Commerce-Coordinator API client. + Test the Generic API client. """ @responses.activate(registry=OrderedRegistry) @@ -554,15 +554,17 @@ class TestCommerceCoordinatorApi(OAuth2Mixin, unittest.TestCase): super().setUp() self.mock_access_token_response() self.lms_base_url = 'http://localhost:18000/' - self.commerce_coordinator_base_url = 'http://localhost:8140/' - self.commerce_coordinator_api = edx_api.CommerceCoordinatorApi( + self.service_api_base_url = 'http://mock_service_url/' + self.retirement_url = 'mock/retirement_url' + self.generic_api = edx_api.GenericRetirementApi( self.lms_base_url, - self.commerce_coordinator_base_url, + self.service_api_base_url, 'the_client_id', - 'the_client_secret' + 'the_client_secret', + self.retirement_url ) - @patch.object(edx_api.CommerceCoordinatorApi, '_request') + @patch.object(edx_api.GenericRetirementApi, '_request') def test_retire_learner(self, mock_request): learner_data = get_fake_user_retirement() json_data = { @@ -570,14 +572,14 @@ class TestCommerceCoordinatorApi(OAuth2Mixin, unittest.TestCase): } responses.add( POST, - urljoin(self.commerce_coordinator_base_url, 'lms/user_retirement'), + urljoin(self.service_api_base_url, 'mock/retirement_url'), match=[matchers.json_params_matcher(json_data)] ) - self.commerce_coordinator_api.retire_learner(learner=learner_data) + self.generic_api.retire_learner(learner=learner_data) mock_request.assert_called_once_with( 'POST', - urljoin(self.commerce_coordinator_base_url, 'lms/user_retirement/'), + urljoin(self.service_api_base_url, 'mock/retirement_url/'), json=json_data ) diff --git a/scripts/user_retirement/utils/edx_api.py b/scripts/user_retirement/utils/edx_api.py index d259e9d2eb..b0c5ffae46 100644 --- a/scripts/user_retirement/utils/edx_api.py +++ b/scripts/user_retirement/utils/edx_api.py @@ -492,16 +492,19 @@ class LicenseManagerApi(BaseApiClient): return True -class CommerceCoordinatorApi(BaseApiClient): +class GenericRetirementApi(BaseApiClient): """ - Commerce-Coordinator API client. + Generic API client. """ + def __init__(self, lms_base_url, api_base_url, client_id, client_secret, retirement_url_path): + super().__init__(lms_base_url, api_base_url, client_id, client_secret) + self.retirement_url_path = retirement_url_path + @_retry_lms_api() def retire_learner(self, learner): """ - Performs the learner retirement step for Commerce-Coordinator. - Passes the learner's LMS User Id instead of username. + Performs the learner retirement step for additonal services. """ data = {'edx_lms_user_id': learner['user']['id']} - api_url = self.get_api_url('lms/user_retirement') + api_url = self.get_api_url(self.retirement_url_path) return self._request('POST', api_url, json=data) diff --git a/scripts/user_retirement/utils/helpers.py b/scripts/user_retirement/utils/helpers.py index 42723b0999..0c3af6006a 100644 --- a/scripts/user_retirement/utils/helpers.py +++ b/scripts/user_retirement/utils/helpers.py @@ -16,9 +16,10 @@ import unicodedata import yaml from six import text_type +from django.conf import settings from scripts.user_retirement.utils.edx_api import LmsApi # pylint: disable=wrong-import-position -from scripts.user_retirement.utils.edx_api import CommerceCoordinatorApi, CredentialsApi, EcommerceApi, \ +from scripts.user_retirement.utils.edx_api import CredentialsApi, EcommerceApi, GenericRetirementApi, \ LicenseManagerApi from scripts.user_retirement.utils.thirdparty_apis.amplitude_api import \ AmplitudeApi # pylint: disable=wrong-import-position @@ -155,7 +156,6 @@ def _setup_all_apis_or_exit(fail_func, fail_code, config): credentials_base_url = config['base_urls'].get('credentials', None) segment_base_url = config['base_urls'].get('segment', None) license_manager_base_url = config['base_urls'].get('license_manager', None) - commerce_coordinator_base_url = config['base_urls'].get('commerce_coordinator', None) client_id = config['client_id'] client_secret = config['client_secret'] braze_api_key = config.get('braze_api_key', None) @@ -174,16 +174,19 @@ def _setup_all_apis_or_exit(fail_func, fail_code, config): hubspot_from_address = config.get('hubspot_from_address', None) hubspot_alert_email = config.get('hubspot_alert_email', None) + required_services = [ + ('BRAZE', braze_api_key), + ('AMPLITUDE', amplitude_api_key), + ('ECOMMERCE', ecommerce_base_url), + ('CREDENTIALS', credentials_base_url), + ('SEGMENT', segment_base_url), + ('HUBSPOT', hubspot_api_key), + ] + extra_services = [(service['name'], service['service_base_url']) for service in settings.EXTRA_SERVICES_TO_RETIRE_FROM] + all_services = required_services + extra_services + for state in config['retirement_pipeline']: - for service, service_url in ( - ('BRAZE', braze_api_key), - ('AMPLITUDE', amplitude_api_key), - ('ECOMMERCE', ecommerce_base_url), - ('CREDENTIALS', credentials_base_url), - ('SEGMENT', segment_base_url), - ('HUBSPOT', hubspot_api_key), - ('COMMERCE_COORDINATOR', commerce_coordinator_base_url), - ): + for service, service_url in (all_services): if state[2] == service and service_url is None: fail_func(fail_code, 'Service URL is not configured, but required for state {}'.format(state)) @@ -239,12 +242,14 @@ def _setup_all_apis_or_exit(fail_func, fail_code, config): segment_workspace_slug ) - if commerce_coordinator_base_url: - config['COMMERCE_COORDINATOR'] = CommerceCoordinatorApi( + for service_config in extra_services: + service_name = service_config['name'] + config[service_name] = GenericRetirementApi( lms_base_url, - commerce_coordinator_base_url, + service_config['service_base_url'], client_id, client_secret, + service_config['retirement_url_path'] ) except Exception as exc: # pylint: disable=broad-except fail_func(fail_code, 'Unexpected error occurred!', exc)