Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions integrated_channels/moodle/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@
from integrated_channels.integrated_channel.client import IntegratedChannelApiClient
from integrated_channels.utils import generate_formatted_log, stringify_and_store_api_record

MOODLE_ERROR_STATUS_MAP = {
"shortnametaken": 409,
"courseidnumbertaken": 409,
"cannotfindcourse": 404,
"cannotfinduser": 404,
"missingparam": 400,
}

LOGGER = logging.getLogger(__name__)


Expand Down Expand Up @@ -82,17 +90,30 @@ def inner(self, *args, **kwargs):
raise ClientError('Moodle API Grade Update failed with possible error: {body}'.format(body=body), 500)
error_code = body.get('errorcode')
warnings = body.get('warnings')

# Define mapped_status based on error_code
mapped_status = {
'invalidtoken': 'Invalid Token',
'missingfield': 'Missing Field',
'duplicatedata': 'Duplicate Data',
}.get(error_code, 'Unknown Error')

if error_code and error_code == 'invalidtoken':
self.token = self._get_access_token() # pylint: disable=protected-access
response = method(self, *args, **kwargs)
elif error_code:
raise MoodleClientError(
'Moodle API Client Task "{method}" failed with error code '
'"{code}" and message: "{msg}" '.format(
method=method.__name__, code=error_code, msg=body.get('message'),
method=method.__name__,
code=error_code,
msg=body.get('message'),
),
response.status_code,
error_code,
moodle_error={
'mapped_status': mapped_status,
'error_code': error_code,
},
)
elif warnings:
# More Moodle nonsense!
Expand Down Expand Up @@ -464,9 +485,18 @@ def create_content_metadata(self, serialized_data):
except MoodleClientError as error:
# treat duplicate as successful, but only if its a single course
# set chunk size settings to 1 if youre seeing a lot of these errors
if error.moodle_error == 'shortnametaken' and not more_than_one_course:
if (
error.moodle_error
and error.moodle_error.get('error_code') == 'shortnametaken'
and not more_than_one_course
):
return 200, "shortnametaken"
elif error.moodle_error == 'courseidnumbertaken' and not more_than_one_course:

elif (
error.moodle_error
and error.moodle_error.get('error_code') == 'courseidnumbertaken'
and not more_than_one_course
):
return 200, "courseidnumbertaken"
else:
raise error
Expand Down
41 changes: 41 additions & 0 deletions tests/test_integrated_channels/test_moodle/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,3 +460,44 @@ def test_create_content_metadata_with_mocked_api_requests(self):
client.create_content_metadata(SERIALIZED_DATA)
assert IntegratedChannelAPIRequestLogs.objects.count() == 2
assert len(responses.calls) == 2

def test_missing_param_error_handling(self):
"""
Test handling of 400 response for missing parameters.
"""
# pylint: disable=protected-access

client = MoodleAPIClient(self.enterprise_config)

missing_param_response = unittest.mock.Mock(spec=Response)
missing_param_response.json.return_value = {
'errorcode': 'missingparam',
'message': 'A required parameter is missing.'
}
missing_param_response.status_code = 400

client._post = unittest.mock.MagicMock( # Mocking _wrapped_post to prevent real HTTP calls
name='_post',
return_value=missing_param_response
)
client._wrapped_post = unittest.mock.MagicMock(name='_wrapped_post', return_value=missing_param_response)

# Ensure the mocked _wrapped_post method raises the expected error
client._wrapped_post.side_effect = MoodleClientError(
message='A required parameter is missing.',
status_code=400,
moodle_error={'error_code': 'missingparam'}
)

with self.assertRaises(MoodleClientError) as context:
client._wrapped_post(SERIALIZED_DATA)

self.assertEqual(context.exception.status_code, 400)
self.assertEqual(
context.exception.moodle_error.get('error_code'),
'missingparam'
)
self.assertIn(
'A required parameter is missing.',
context.exception.message
)