Files
edx-platform/scripts/user_retirement/tests/test_retire_one_learner.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

413 lines
15 KiB
Python

"""
Test the retire_one_learner.py script
"""
from unittest.mock import DEFAULT, patch
from click.testing import CliRunner
from scripts.user_retirement.retire_one_learner import (
END_STATES,
ERR_BAD_CONFIG,
ERR_BAD_LEARNER,
ERR_SETUP_FAILED,
ERR_UNKNOWN_STATE,
ERR_USER_AT_END_STATE,
ERR_USER_IN_WORKING_STATE,
retire_learner
)
from scripts.user_retirement.tests.retirement_helpers import fake_config_file, get_fake_user_retirement
from scripts.user_retirement.utils.exception import HttpDoesNotExistException
def _call_script(username, fetch_ecom_segment_id=False):
"""
Call the retired learner script with the given username and a generic, temporary config file.
Returns the CliRunner.invoke results
"""
runner = CliRunner()
with runner.isolated_filesystem():
with open('test_config.yml', 'w') as f:
fake_config_file(f, fetch_ecom_segment_id=fetch_ecom_segment_id)
result = runner.invoke(retire_learner, args=['--username', username, '--config_file', 'test_config.yml'])
print(result)
print(result.output)
return result
@patch('scripts.user_retirement.utils.edx_api.BaseApiClient.get_access_token')
@patch('scripts.user_retirement.utils.edx_api.EcommerceApi.get_tracking_key')
@patch.multiple(
'scripts.user_retirement.utils.edx_api.LmsApi',
get_learner_retirement_state=DEFAULT,
update_learner_retirement_state=DEFAULT,
retirement_retire_forum=DEFAULT,
retirement_retire_mailings=DEFAULT,
retirement_unenroll=DEFAULT,
retirement_lms_retire=DEFAULT
)
def test_successful_retirement(*args, **kwargs):
username = 'test_username'
mock_get_access_token = args[1]
mock_get_retirement_state = kwargs['get_learner_retirement_state']
mock_update_learner_state = kwargs['update_learner_retirement_state']
mock_retire_forum = kwargs['retirement_retire_forum']
mock_retire_mailings = kwargs['retirement_retire_mailings']
mock_unenroll = kwargs['retirement_unenroll']
mock_lms_retire = kwargs['retirement_lms_retire']
mock_get_access_token.return_value = ('THIS_IS_A_JWT', None)
mock_get_retirement_state.return_value = get_fake_user_retirement(original_username=username)
result = _call_script(username, fetch_ecom_segment_id=True)
# Called once per API we instantiate (LMS, ECommerce, Credentials)
assert mock_get_access_token.call_count == 3
mock_get_retirement_state.assert_called_once_with(username)
assert mock_update_learner_state.call_count == 9
# Called once per retirement
for mock_call in (
mock_retire_forum,
mock_retire_mailings,
mock_unenroll,
mock_lms_retire
):
mock_call.assert_called_once_with(mock_get_retirement_state.return_value)
assert result.exit_code == 0
assert 'Retirement complete' in result.output
@patch('scripts.user_retirement.utils.edx_api.BaseApiClient.get_access_token')
@patch.multiple(
'scripts.user_retirement.utils.edx_api.LmsApi',
get_learner_retirement_state=DEFAULT,
update_learner_retirement_state=DEFAULT
)
def test_user_does_not_exist(*args, **kwargs):
username = 'test_username'
mock_get_access_token = args[0]
mock_get_retirement_state = kwargs['get_learner_retirement_state']
mock_update_learner_state = kwargs['update_learner_retirement_state']
mock_get_access_token.return_value = ('THIS_IS_A_JWT', None)
mock_get_retirement_state.side_effect = Exception
result = _call_script(username)
assert mock_get_access_token.call_count == 3
mock_get_retirement_state.assert_called_once_with(username)
mock_update_learner_state.assert_not_called()
assert result.exit_code == ERR_SETUP_FAILED
assert 'Exception' in result.output
def test_bad_config():
username = 'test_username'
runner = CliRunner()
result = runner.invoke(retire_learner, args=['--username', username, '--config_file', 'does_not_exist.yml'])
assert result.exit_code == ERR_BAD_CONFIG
assert 'does_not_exist.yml' in result.output
@patch('scripts.user_retirement.utils.edx_api.BaseApiClient.get_access_token')
@patch.multiple(
'scripts.user_retirement.utils.edx_api.LmsApi',
get_learner_retirement_state=DEFAULT,
update_learner_retirement_state=DEFAULT
)
def test_bad_learner(*args, **kwargs):
username = 'test_username'
mock_get_access_token = args[0]
mock_get_retirement_state = kwargs['get_learner_retirement_state']
mock_update_learner_state = kwargs['update_learner_retirement_state']
mock_get_access_token.return_value = ('THIS_IS_A_JWT', None)
# Broken API call, no state returned
mock_get_retirement_state.side_effect = HttpDoesNotExistException
result = _call_script(username)
assert mock_get_access_token.call_count == 3
mock_get_retirement_state.assert_called_once_with(username)
mock_update_learner_state.assert_not_called()
assert result.exit_code == ERR_BAD_LEARNER
@patch('scripts.user_retirement.utils.edx_api.BaseApiClient.get_access_token')
@patch.multiple(
'scripts.user_retirement.utils.edx_api.LmsApi',
get_learner_retirement_state=DEFAULT,
update_learner_retirement_state=DEFAULT
)
def test_user_in_working_state(*args, **kwargs):
username = 'test_username'
mock_get_access_token = args[0]
mock_get_retirement_state = kwargs['get_learner_retirement_state']
mock_update_learner_state = kwargs['update_learner_retirement_state']
mock_get_access_token.return_value = ('THIS_IS_A_JWT', None)
mock_get_retirement_state.return_value = get_fake_user_retirement(
original_username=username,
current_state_name='RETIRING_FORUMS'
)
result = _call_script(username)
assert mock_get_access_token.call_count == 3
mock_get_retirement_state.assert_called_once_with(username)
mock_update_learner_state.assert_not_called()
assert result.exit_code == ERR_USER_IN_WORKING_STATE
assert 'in a working state' in result.output
@patch('scripts.user_retirement.utils.edx_api.BaseApiClient.get_access_token')
@patch.multiple(
'scripts.user_retirement.utils.edx_api.LmsApi',
get_learner_retirement_state=DEFAULT,
update_learner_retirement_state=DEFAULT
)
def test_user_in_bad_state(*args, **kwargs):
username = 'test_username'
bad_state = 'BOGUS_STATE'
mock_get_access_token = args[0]
mock_get_retirement_state = kwargs['get_learner_retirement_state']
mock_update_learner_state = kwargs['update_learner_retirement_state']
mock_get_access_token.return_value = ('THIS_IS_A_JWT', None)
mock_get_retirement_state.return_value = get_fake_user_retirement(
original_username=username,
current_state_name=bad_state
)
result = _call_script(username)
assert mock_get_access_token.call_count == 3
mock_get_retirement_state.assert_called_once_with(username)
mock_update_learner_state.assert_not_called()
assert result.exit_code == ERR_UNKNOWN_STATE
assert bad_state in result.output
@patch('scripts.user_retirement.utils.edx_api.BaseApiClient.get_access_token')
@patch.multiple(
'scripts.user_retirement.utils.edx_api.LmsApi',
get_learner_retirement_state=DEFAULT,
update_learner_retirement_state=DEFAULT
)
def test_user_in_end_state(*args, **kwargs):
username = 'test_username'
mock_get_access_token = args[0]
mock_get_retirement_state = kwargs['get_learner_retirement_state']
mock_update_learner_state = kwargs['update_learner_retirement_state']
mock_get_access_token.return_value = ('THIS_IS_A_JWT', None)
# pytest.parameterize doesn't play nicely with patch.multiple, this seemed more
# readable than the alternatives.
for end_state in END_STATES:
mock_get_retirement_state.return_value = {
'original_username': username,
'current_state': {
'state_name': end_state
}
}
result = _call_script(username)
assert mock_get_access_token.call_count == 3
mock_get_retirement_state.assert_called_once_with(username)
mock_update_learner_state.assert_not_called()
assert result.exit_code == ERR_USER_AT_END_STATE
assert end_state in result.output
# Reset our call counts for the next test
mock_get_access_token.reset_mock()
mock_get_retirement_state.reset_mock()
@patch('scripts.user_retirement.utils.edx_api.BaseApiClient.get_access_token')
@patch.multiple(
'scripts.user_retirement.utils.edx_api.LmsApi',
get_learner_retirement_state=DEFAULT,
update_learner_retirement_state=DEFAULT,
retirement_retire_forum=DEFAULT,
retirement_retire_mailings=DEFAULT,
retirement_unenroll=DEFAULT,
retirement_lms_retire=DEFAULT
)
def test_skipping_states(*args, **kwargs):
username = 'test_username'
mock_get_access_token = args[0]
mock_get_retirement_state = kwargs['get_learner_retirement_state']
mock_update_learner_state = kwargs['update_learner_retirement_state']
mock_retire_forum = kwargs['retirement_retire_forum']
mock_retire_mailings = kwargs['retirement_retire_mailings']
mock_unenroll = kwargs['retirement_unenroll']
mock_lms_retire = kwargs['retirement_lms_retire']
mock_get_access_token.return_value = ('THIS_IS_A_JWT', None)
mock_get_retirement_state.return_value = get_fake_user_retirement(
original_username=username,
current_state_name='EMAIL_LISTS_COMPLETE'
)
result = _call_script(username)
# Called once per API we instantiate (LMS, ECommerce, Credentials)
assert mock_get_access_token.call_count == 3
mock_get_retirement_state.assert_called_once_with(username)
assert mock_update_learner_state.call_count == 5
# Skipped
for mock_call in (
mock_retire_forum,
mock_retire_mailings
):
mock_call.assert_not_called()
# Called once per retirement
for mock_call in (
mock_unenroll,
mock_lms_retire
):
mock_call.assert_called_once_with(mock_get_retirement_state.return_value)
assert result.exit_code == 0
for required_output in (
'RETIRING_FORUMS completed in previous run',
'RETIRING_EMAIL_LISTS completed in previous run',
'Starting state RETIRING_ENROLLMENTS',
'State RETIRING_ENROLLMENTS completed',
'Starting state RETIRING_LMS',
'State RETIRING_LMS completed',
'Retirement complete'
):
assert required_output in result.output
@patch('scripts.user_retirement.utils.edx_api.BaseApiClient.get_access_token')
@patch('scripts.user_retirement.utils.edx_api.EcommerceApi.get_tracking_key')
@patch.multiple(
'scripts.user_retirement.utils.edx_api.LmsApi',
get_learner_retirement_state=DEFAULT,
update_learner_retirement_state=DEFAULT,
retirement_retire_forum=DEFAULT,
retirement_retire_mailings=DEFAULT,
retirement_unenroll=DEFAULT,
retirement_lms_retire=DEFAULT
)
def test_get_segment_id_success(*args, **kwargs):
username = 'test_username'
mock_get_tracking_key = args[0]
mock_get_access_token = args[1]
mock_get_retirement_state = kwargs['get_learner_retirement_state']
mock_retirement_retire_forum = kwargs['retirement_retire_forum']
mock_get_access_token.return_value = ('THIS_IS_A_JWT', None)
mock_get_tracking_key.return_value = {'id': 1, 'ecommerce_tracking_id': 'ecommerce-1'}
# The learner starts off with these values, 'ecommerce_segment_id' is added during script
# startup
mock_get_retirement_state.return_value = get_fake_user_retirement(
original_username=username,
)
_call_script(username, fetch_ecom_segment_id=True)
mock_get_tracking_key.assert_called_once_with(mock_get_retirement_state.return_value)
config_after_get_segment_id = mock_get_retirement_state.return_value
config_after_get_segment_id['ecommerce_segment_id'] = 'ecommerce-1'
mock_retirement_retire_forum.assert_called_once_with(config_after_get_segment_id)
@patch('scripts.user_retirement.utils.edx_api.BaseApiClient.get_access_token')
@patch('scripts.user_retirement.utils.edx_api.EcommerceApi.get_tracking_key')
@patch.multiple(
'scripts.user_retirement.utils.edx_api.LmsApi',
get_learner_retirement_state=DEFAULT,
update_learner_retirement_state=DEFAULT,
retirement_retire_forum=DEFAULT,
retirement_retire_mailings=DEFAULT,
retirement_unenroll=DEFAULT,
retirement_lms_retire=DEFAULT
)
def test_get_segment_id_not_found(*args, **kwargs):
username = 'test_username'
mock_get_tracking_key = args[0]
mock_get_access_token = args[1]
mock_get_retirement_state = kwargs['get_learner_retirement_state']
mock_get_access_token.return_value = ('THIS_IS_A_JWT', None)
mock_get_tracking_key.side_effect = HttpDoesNotExistException('{} not found'.format(username))
mock_get_retirement_state.return_value = get_fake_user_retirement(
original_username=username,
)
result = _call_script(username, fetch_ecom_segment_id=True)
mock_get_tracking_key.assert_called_once_with(mock_get_retirement_state.return_value)
assert 'Setting Ecommerce Segment ID to None' in result.output
# Reset our call counts for the next test
mock_get_access_token.reset_mock()
mock_get_retirement_state.reset_mock()
@patch('scripts.user_retirement.utils.edx_api.BaseApiClient.get_access_token')
@patch('scripts.user_retirement.utils.edx_api.EcommerceApi.get_tracking_key')
@patch.multiple(
'scripts.user_retirement.utils.edx_api.LmsApi',
get_learner_retirement_state=DEFAULT,
update_learner_retirement_state=DEFAULT,
retirement_retire_forum=DEFAULT,
retirement_retire_mailings=DEFAULT,
retirement_unenroll=DEFAULT,
retirement_lms_retire=DEFAULT
)
def test_get_segment_id_error(*args, **kwargs):
username = 'test_username'
mock_get_tracking_key = args[0]
mock_get_access_token = args[1]
mock_get_retirement_state = kwargs['get_learner_retirement_state']
mock_update_learner_state = kwargs['update_learner_retirement_state']
mock_get_access_token.return_value = ('THIS_IS_A_JWT', None)
test_exception_message = 'Test Exception!'
mock_get_tracking_key.side_effect = Exception(test_exception_message)
mock_get_retirement_state.return_value = get_fake_user_retirement(
original_username=username,
)
mock_get_retirement_state.return_value = {
'original_username': username,
'current_state': {
'state_name': 'PENDING'
}
}
result = _call_script(username, fetch_ecom_segment_id=True)
mock_get_tracking_key.assert_called_once_with(mock_get_retirement_state.return_value)
mock_update_learner_state.assert_not_called()
assert result.exit_code == ERR_SETUP_FAILED
assert 'Unexpected error fetching Ecommerce tracking id!' in result.output
assert test_exception_message in result.output