Files
edx-platform/scripts/user_retirement/tests/utils/test_edx_api.py
Justin Hynes e4855536fd Revert: revert generic retirement update (#35317)
* Revert "fix: change settings config to empty list not dict"

This reverts commit b65550c796.

* Revert "fix: dependencies again"

This reverts commit c06416bed7.

* Revert "feat: updated user retirement docs"

This reverts commit c9641b35d4.

* Revert "fix: install dependencies"

This reverts commit a5442b2409.

* Revert "Revert "fix: dependencies""

This reverts commit 4cde950007.

* Revert "fix: dependencies"

This reverts commit 8a1c30ebc5.

* Revert "fix: Add CI update for tests"

This reverts commit 64098b6dab.

* Revert "fix: tests"

This reverts commit 5e636dea16.

* Revert "fix: generalize internal services"

This reverts commit e8f9db428d.

* Revert "fix: quality"

This reverts commit 77ca0f754a.

* Revert "feat: Commerce Coordinator step in retirement pipeline"

This reverts commit c24c87499f.
2024-08-15 12:28:52 -04:00

545 lines
19 KiB
Python

"""
Tests for edX API calls.
"""
import unittest
from unittest.mock import DEFAULT, patch
from urllib.parse import urljoin
import requests
import responses
from ddt import data, ddt, unpack
from requests.exceptions import ConnectionError, HTTPError
from responses import GET, PATCH, POST, matchers
from responses.registries import OrderedRegistry
from scripts.user_retirement.tests.mixins import OAuth2Mixin
from scripts.user_retirement.tests.retirement_helpers import (
FAKE_DATETIME_OBJECT,
FAKE_ORIGINAL_USERNAME,
FAKE_RESPONSE_MESSAGE,
FAKE_USERNAME_MAPPING,
FAKE_USERNAMES,
TEST_RETIREMENT_QUEUE_STATES,
TEST_RETIREMENT_STATE,
get_fake_user_retirement,
)
from scripts.user_retirement.utils import edx_api
class BackoffTriedException(Exception):
"""
Raise this from a backoff handler to indicate that backoff was tried.
"""
@ddt
class TestLmsApi(OAuth2Mixin, unittest.TestCase):
"""
Test the edX LMS API client.
"""
@responses.activate(registry=OrderedRegistry)
def setUp(self):
super().setUp()
self.mock_access_token_response()
self.lms_base_url = 'http://localhost:18000/'
self.lms_api = edx_api.LmsApi(
self.lms_base_url,
self.lms_base_url,
'the_client_id',
'the_client_secret'
)
def tearDown(self):
super().tearDown()
responses.reset()
@patch.object(edx_api.LmsApi, 'learners_to_retire')
def test_learners_to_retire(self, mock_method):
params = {
'states': TEST_RETIREMENT_QUEUE_STATES,
'cool_off_days': 365,
}
responses.add(
GET,
urljoin(self.lms_base_url, 'api/user/v1/accounts/retirement_queue/'),
match=[matchers.query_param_matcher(params)],
)
self.lms_api.learners_to_retire(
TEST_RETIREMENT_QUEUE_STATES, cool_off_days=365)
mock_method.assert_called_once_with(
TEST_RETIREMENT_QUEUE_STATES, cool_off_days=365)
@patch.object(edx_api.LmsApi, 'get_learners_by_date_and_status')
def test_get_learners_by_date_and_status(self, mock_method):
query_params = {
'start_date': FAKE_DATETIME_OBJECT.strftime('%Y-%m-%d'),
'end_date': FAKE_DATETIME_OBJECT.strftime('%Y-%m-%d'),
'state': TEST_RETIREMENT_STATE,
}
responses.add(
GET,
urljoin(self.lms_base_url, 'api/user/v1/accounts/retirements_by_status_and_date/'),
match=[matchers.query_param_matcher(query_params)]
)
self.lms_api.get_learners_by_date_and_status(
state_to_request=TEST_RETIREMENT_STATE,
start_date=FAKE_DATETIME_OBJECT,
end_date=FAKE_DATETIME_OBJECT
)
mock_method.assert_called_once_with(
state_to_request=TEST_RETIREMENT_STATE,
start_date=FAKE_DATETIME_OBJECT,
end_date=FAKE_DATETIME_OBJECT
)
@patch.object(edx_api.LmsApi, 'get_learner_retirement_state')
def test_get_learner_retirement_state(self, mock_method):
responses.add(
GET,
urljoin(self.lms_base_url, f'api/user/v1/accounts/{FAKE_ORIGINAL_USERNAME}/retirement_status/'),
)
self.lms_api.get_learner_retirement_state(
username=FAKE_ORIGINAL_USERNAME
)
mock_method.assert_called_once_with(
username=FAKE_ORIGINAL_USERNAME
)
@patch.object(edx_api.LmsApi, 'update_learner_retirement_state')
def test_update_leaner_retirement_state(self, mock_method):
json_data = {
'username': FAKE_ORIGINAL_USERNAME,
'new_state': TEST_RETIREMENT_STATE,
'response': FAKE_RESPONSE_MESSAGE,
}
responses.add(
PATCH,
urljoin(self.lms_base_url, 'api/user/v1/accounts/update_retirement_status/'),
match=[matchers.json_params_matcher(json_data)]
)
self.lms_api.update_learner_retirement_state(
username=FAKE_ORIGINAL_USERNAME,
new_state_name=TEST_RETIREMENT_STATE,
message=FAKE_RESPONSE_MESSAGE
)
mock_method.assert_called_once_with(
username=FAKE_ORIGINAL_USERNAME,
new_state_name=TEST_RETIREMENT_STATE,
message=FAKE_RESPONSE_MESSAGE
)
@data(
{
'api_url': 'api/user/v1/accounts/deactivate_logout/',
'mock_method': 'retirement_deactivate_logout',
'method': 'POST',
},
{
'api_url': 'api/discussion/v1/accounts/retire_forum/',
'mock_method': 'retirement_retire_forum',
'method': 'POST',
},
{
'api_url': 'api/user/v1/accounts/retire_mailings/',
'mock_method': 'retirement_retire_mailings',
'method': 'POST',
},
{
'api_url': 'api/enrollment/v1/unenroll/',
'mock_method': 'retirement_unenroll',
'method': 'POST',
},
{
'api_url': 'api/edxnotes/v1/retire_user/',
'mock_method': 'retirement_retire_notes',
'method': 'POST',
},
{
'api_url': 'api/user/v1/accounts/retire_misc/',
'mock_method': 'retirement_lms_retire_misc',
'method': 'POST',
},
{
'api_url': 'api/user/v1/accounts/retire/',
'mock_method': 'retirement_lms_retire',
'method': 'POST',
},
{
'api_url': 'api/user/v1/accounts/retirement_partner_report/',
'mock_method': 'retirement_partner_queue',
'method': 'PUT',
},
)
@unpack
@patch.multiple(
'scripts.user_retirement.utils.edx_api.LmsApi',
retirement_deactivate_logout=DEFAULT,
retirement_retire_forum=DEFAULT,
retirement_retire_mailings=DEFAULT,
retirement_unenroll=DEFAULT,
retirement_retire_notes=DEFAULT,
retirement_lms_retire_misc=DEFAULT,
retirement_lms_retire=DEFAULT,
retirement_partner_queue=DEFAULT,
)
def test_learner_retirement(self, api_url, mock_method, method, **kwargs):
json_data = {
'username': FAKE_ORIGINAL_USERNAME,
}
responses.add(
method,
urljoin(self.lms_base_url, api_url),
match=[matchers.json_params_matcher(json_data)]
)
getattr(self.lms_api, mock_method)(get_fake_user_retirement(original_username=FAKE_ORIGINAL_USERNAME))
kwargs[mock_method].assert_called_once_with(get_fake_user_retirement(original_username=FAKE_ORIGINAL_USERNAME))
@patch.object(edx_api.LmsApi, 'retirement_partner_report')
def test_retirement_partner_report(self, mock_method):
responses.add(
POST,
urljoin(self.lms_base_url, 'api/user/v1/accounts/retirement_partner_report/')
)
self.lms_api.retirement_partner_report(
learner=get_fake_user_retirement(
original_username=FAKE_ORIGINAL_USERNAME
)
)
mock_method.assert_called_once_with(
learner=get_fake_user_retirement(
original_username=FAKE_ORIGINAL_USERNAME
)
)
@patch.object(edx_api.LmsApi, 'retirement_partner_cleanup')
def test_retirement_partner_cleanup(self, mock_method):
json_data = FAKE_USERNAMES
responses.add(
POST,
urljoin(self.lms_base_url, 'api/user/v1/accounts/retirement_partner_report_cleanup/'),
match=[matchers.json_params_matcher(json_data)]
)
self.lms_api.retirement_partner_cleanup(
usernames=FAKE_USERNAMES
)
mock_method.assert_called_once_with(
usernames=FAKE_USERNAMES
)
@patch.object(edx_api.LmsApi, 'retirement_retire_proctoring_data')
def test_retirement_retire_proctoring_data(self, mock_method):
learner = get_fake_user_retirement()
responses.add(
POST,
urljoin(self.lms_base_url, f"api/edx_proctoring/v1/retire_user/{learner['user']['id']}/"),
)
self.lms_api.retirement_retire_proctoring_data()
mock_method.assert_called_once()
@patch.object(edx_api.LmsApi, 'retirement_retire_proctoring_backend_data')
def test_retirement_retire_proctoring_backend_data(self, mock_method):
learner = get_fake_user_retirement()
responses.add(
POST,
urljoin(self.lms_base_url, f"api/edx_proctoring/v1/retire_backend_user/{learner['user']['id']}/"),
)
self.lms_api.retirement_retire_proctoring_backend_data()
mock_method.assert_called_once()
@patch.object(edx_api.LmsApi, 'replace_lms_usernames')
def test_replace_lms_usernames(self, mock_method):
json_data = {
'username_mappings': FAKE_USERNAME_MAPPING
}
responses.add(
POST,
urljoin(self.lms_base_url, 'api/user/v1/accounts/replace_usernames/'),
match=[matchers.json_params_matcher(json_data)]
)
self.lms_api.replace_lms_usernames(
username_mappings=FAKE_USERNAME_MAPPING
)
mock_method.assert_called_once_with(
username_mappings=FAKE_USERNAME_MAPPING
)
@patch.object(edx_api.LmsApi, 'replace_forums_usernames')
def test_replace_forums_usernames(self, mock_method):
json_data = {
'username_mappings': FAKE_USERNAME_MAPPING
}
responses.add(
POST,
urljoin(self.lms_base_url, 'api/discussion/v1/accounts/replace_usernames/'),
match=[matchers.json_params_matcher(json_data)]
)
self.lms_api.replace_forums_usernames(
username_mappings=FAKE_USERNAME_MAPPING
)
mock_method.assert_called_once_with(
username_mappings=FAKE_USERNAME_MAPPING
)
@data(504, 500)
@patch('scripts.user_retirement.utils.edx_api._backoff_handler')
@patch.object(edx_api.LmsApi, 'learners_to_retire')
def test_retrieve_learner_queue_backoff(
self,
svr_status_code,
mock_backoff_handler,
mock_learners_to_retire
):
mock_backoff_handler.side_effect = BackoffTriedException
params = {
'states': TEST_RETIREMENT_QUEUE_STATES,
'cool_off_days': 365,
}
response = requests.Response()
response.status_code = svr_status_code
responses.add(
GET,
urljoin(self.lms_base_url, 'api/user/v1/accounts/retirement_queue/'),
status=200,
match=[matchers.query_param_matcher(params)],
)
mock_learners_to_retire.side_effect = HTTPError(response=response)
with self.assertRaises(BackoffTriedException):
self.lms_api.learners_to_retire(
TEST_RETIREMENT_QUEUE_STATES, cool_off_days=365)
@data(104)
@responses.activate
@patch('scripts.user_retirement.utils.edx_api._backoff_handler')
@patch.object(edx_api.LmsApi, 'retirement_partner_cleanup')
def test_retirement_partner_cleanup_backoff_on_connection_error(
self,
svr_status_code,
mock_backoff_handler,
mock_retirement_partner_cleanup
):
mock_backoff_handler.side_effect = BackoffTriedException
response = requests.Response()
response.status_code = svr_status_code
mock_retirement_partner_cleanup.retirement_partner_cleanup.side_effect = ConnectionError(
response=response
)
with self.assertRaises(BackoffTriedException):
self.lms_api.retirement_partner_cleanup([{'original_username': 'test'}])
class TestEcommerceApi(OAuth2Mixin, unittest.TestCase):
"""
Test the edX Ecommerce API client.
"""
@responses.activate(registry=OrderedRegistry)
def setUp(self):
super().setUp()
self.mock_access_token_response()
self.lms_base_url = 'http://localhost:18000/'
self.ecommerce_base_url = 'http://localhost:18130/'
self.ecommerce_api = edx_api.EcommerceApi(
self.lms_base_url,
self.ecommerce_base_url,
'the_client_id',
'the_client_secret'
)
def tearDown(self):
super().tearDown()
responses.reset()
@patch.object(edx_api.EcommerceApi, 'retire_learner')
def test_retirement_partner_report(self, mock_method):
json_data = {
'username': FAKE_ORIGINAL_USERNAME,
}
responses.add(
POST,
urljoin(self.lms_base_url, 'api/v2/user/retire/'),
match=[matchers.json_params_matcher(json_data)]
)
self.ecommerce_api.retire_learner(
learner=get_fake_user_retirement(original_username=FAKE_ORIGINAL_USERNAME)
)
mock_method.assert_called_once_with(
learner=get_fake_user_retirement(original_username=FAKE_ORIGINAL_USERNAME)
)
@patch.object(edx_api.EcommerceApi, 'retire_learner')
def get_tracking_key(self, mock_method):
original_username = {
'original_username': get_fake_user_retirement(original_username=FAKE_ORIGINAL_USERNAME)
}
responses.add(
GET,
urljoin(self.lms_base_url, f"api/v2/retirement/tracking_id/{original_username}/"),
)
self.ecommerce_api.get_tracking_key(
learner=get_fake_user_retirement(original_username=FAKE_ORIGINAL_USERNAME)
)
mock_method.assert_called_once_with(
learner=get_fake_user_retirement(original_username=FAKE_ORIGINAL_USERNAME)
)
@patch.object(edx_api.EcommerceApi, 'replace_usernames')
def test_replace_usernames(self, mock_method):
json_data = {
"username_mappings": FAKE_USERNAME_MAPPING
}
responses.add(
POST,
urljoin(self.lms_base_url, 'api/v2/user_management/replace_usernames/'),
match=[matchers.json_params_matcher(json_data)]
)
self.ecommerce_api.replace_usernames(
username_mappings=FAKE_USERNAME_MAPPING
)
mock_method.assert_called_once_with(
username_mappings=FAKE_USERNAME_MAPPING
)
class TestCredentialApi(OAuth2Mixin, unittest.TestCase):
"""
Test the edX Credential API client.
"""
@responses.activate(registry=OrderedRegistry)
def setUp(self):
super().setUp()
self.mock_access_token_response()
self.lms_base_url = 'http://localhost:18000/'
self.credentials_base_url = 'http://localhost:18150/'
self.credentials_api = edx_api.CredentialsApi(
self.lms_base_url,
self.credentials_base_url,
'the_client_id',
'the_client_secret'
)
def tearDown(self):
super().tearDown()
responses.reset()
@patch.object(edx_api.CredentialsApi, 'retire_learner')
def test_retire_learner(self, mock_method):
json_data = {
'username': FAKE_ORIGINAL_USERNAME
}
responses.add(
POST,
urljoin(self.credentials_base_url, 'user/retire/'),
match=[matchers.json_params_matcher(json_data)]
)
self.credentials_api.retire_learner(
learner=get_fake_user_retirement(original_username=FAKE_ORIGINAL_USERNAME)
)
mock_method.assert_called_once_with(
learner=get_fake_user_retirement(original_username=FAKE_ORIGINAL_USERNAME)
)
@patch.object(edx_api.CredentialsApi, 'replace_usernames')
def test_replace_usernames(self, mock_method):
json_data = {
"username_mappings": FAKE_USERNAME_MAPPING
}
responses.add(
POST,
urljoin(self.credentials_base_url, 'api/v2/replace_usernames/'),
match=[matchers.json_params_matcher(json_data)]
)
self.credentials_api.replace_usernames(
username_mappings=FAKE_USERNAME_MAPPING
)
mock_method.assert_called_once_with(
username_mappings=FAKE_USERNAME_MAPPING
)
class TestDiscoveryApi(OAuth2Mixin, unittest.TestCase):
"""
Test the edX Discovery API client.
"""
@responses.activate(registry=OrderedRegistry)
def setUp(self):
super().setUp()
self.mock_access_token_response()
self.lms_base_url = 'http://localhost:18000/'
self.discovery_base_url = 'http://localhost:18150/'
self.discovery_api = edx_api.DiscoveryApi(
self.lms_base_url,
self.discovery_base_url,
'the_client_id',
'the_client_secret'
)
def tearDown(self):
super().tearDown()
responses.reset()
@patch.object(edx_api.DiscoveryApi, 'replace_usernames')
def test_replace_usernames(self, mock_method):
json_data = {
"username_mappings": FAKE_USERNAME_MAPPING
}
responses.add(
POST,
urljoin(self.discovery_base_url, 'api/v1/replace_usernames/'),
match=[matchers.json_params_matcher(json_data)]
)
self.discovery_api.replace_usernames(
username_mappings=FAKE_USERNAME_MAPPING
)
mock_method.assert_called_once_with(
username_mappings=FAKE_USERNAME_MAPPING
)
class TestLicenseManagerApi(OAuth2Mixin, unittest.TestCase):
"""
Test the edX License Manager API client.
"""
@responses.activate(registry=OrderedRegistry)
def setUp(self):
super().setUp()
self.mock_access_token_response()
self.lms_base_url = 'http://localhost:18000/'
self.license_manager_base_url = 'http://localhost:18170/'
self.license_manager_api = edx_api.LicenseManagerApi(
self.lms_base_url,
self.license_manager_base_url,
'the_client_id',
'the_client_secret'
)
def tearDown(self):
super().tearDown()
responses.reset()
@patch.object(edx_api.LicenseManagerApi, 'retire_learner')
def test_retire_learner(self, mock_method):
json_data = {
'lms_user_id': get_fake_user_retirement()['user']['id'],
'original_username': FAKE_ORIGINAL_USERNAME,
}
responses.add(
POST,
urljoin(self.license_manager_base_url, 'api/v1/retire_user/'),
match=[matchers.json_params_matcher(json_data)]
)
self.license_manager_api.retire_learner(
learner=get_fake_user_retirement(
original_username=FAKE_ORIGINAL_USERNAME
)
)
mock_method.assert_called_once_with(
learner=get_fake_user_retirement(
original_username=FAKE_ORIGINAL_USERNAME
)
)