Commit 80bc2c1e by Will Daly

Show a friendlier error message when the submission API reports a request error

Show a more specific error message for answer too long errors
parent c80d9141
......@@ -26,14 +26,15 @@ class SubmissionMixin(object):
"""
submit_errors = {
SUBMIT_ERRORS = {
# Reported to user sometimes, and useful in tests
'ENODATA': _(u'API returned an empty response.'),
'EBADFORM': _(u'API Submission Request Error.'),
'EUNKNOWN': _(u'API returned unclassified exception.'),
'ENODATA': _(u'An unexpected error occurred.'),
'EBADFORM': _(u'An unexpected error occurred.'),
'EUNKNOWN': _(u'An unexpected error occurred.'),
'ENOMULTI': _(u'Multiple submissions are not allowed.'),
'ENOPREVIEW': _(u'To submit a response, view this component in Preview or Live mode.'),
'EBADARGS': _(u'"submission" required to submit answer.')
'EBADARGS': _(u'An unexpected error occurred.'),
'EANSWERLENGTH': _(u'This response exceeds the size limit.')
}
@XBlock.json_handler
......@@ -57,7 +58,7 @@ class SubmissionMixin(object):
"""
if 'submission' not in data:
return False, 'EBADARGS', self.submit_errors['EBADARGS']
return False, 'EBADARGS', self.SUBMIT_ERRORS['EBADARGS']
status = False
status_text = None
......@@ -67,7 +68,7 @@ class SubmissionMixin(object):
# Short-circuit if no user is defined (as in Studio Preview mode)
# Since students can't submit, they will never be able to progress in the workflow
if self.in_studio_preview:
return False, 'ENOPREVIEW', self.submit_errors['ENOPREVIEW']
return False, 'ENOPREVIEW', self.SUBMIT_ERRORS['ENOPREVIEW']
workflow = self.get_workflow_info()
......@@ -80,10 +81,35 @@ class SubmissionMixin(object):
student_sub
)
except api.SubmissionRequestError as err:
status_tag = 'EBADFORM'
status_text = unicode(err.field_errors)
# Handle the case of an answer that's too long as a special case,
# so we can display a more specific error message.
# Although we limit the number of characters the user can
# enter on the client side, the submissions API uses the JSON-serialized
# submission to calculate length. If each character submitted
# by the user takes more than 1 byte to encode (for example, double-escaped
# newline characters or non-ASCII unicode), then the user might
# exceed the limits set by the submissions API. In that case,
# we display an error message indicating that the answer is too long.
answer_too_long = any(
"maximum answer size exceeded" in answer_err.lower()
for answer_err in err.field_errors.get('answer', [])
)
if answer_too_long:
status_tag = 'EANSWERLENGTH'
else:
msg = (
u"The submissions API reported an invalid request error "
u"when submitting a response for the user: {student_item}"
).format(student_item=student_item_dict)
logger.exception(msg)
status_tag = 'EBADFORM'
except (api.SubmissionError, AssessmentWorkflowError):
logger.exception("This response was not submitted.")
msg = (
u"An unknown error occurred while submitting "
u"a response for the user: {student_item}"
).format(student_item=student_item_dict)
logger.exception(msg)
status_tag = 'EUNKNOWN'
else:
status = True
......@@ -91,7 +117,7 @@ class SubmissionMixin(object):
status_text = submission.get('attempt_number')
# relies on success being orthogonal to errors
status_text = status_text if status_text else self.submit_errors[status_tag]
status_text = status_text if status_text else self.SUBMIT_ERRORS[status_tag]
return status, status_tag, status_text
@XBlock.json_handler
......
......@@ -9,7 +9,6 @@ import pytz
from mock import patch, Mock
from submissions import api as sub_api
from submissions.api import SubmissionRequestError, SubmissionInternalError
from openassessment.xblock.submission_mixin import SubmissionMixin
from .base import XBlockHandlerTestCase, scenario
......@@ -23,6 +22,17 @@ class SubmissionTest(XBlockHandlerTestCase):
self.assertTrue(resp[0])
@scenario('data/basic_scenario.xml', user_id='Bob')
def test_submit_answer_too_long(self, xblock):
# Maximum answer length is 100K, once the answer has been JSON-encoded
long_submission = json.dumps({
'submission': 'longcat is long ' * 100000
})
resp = self.request(xblock, 'submit', long_submission, response_format='json')
self.assertFalse(resp[0])
self.assertEqual(resp[1], "EANSWERLENGTH")
self.assertEqual(resp[2], xblock.SUBMIT_ERRORS["EANSWERLENGTH"])
@scenario('data/basic_scenario.xml', user_id='Bob')
def test_submission_multisubmit_failure(self, xblock):
# We don't care about return value of first one
self.request(xblock, 'submit', self.SUBMISSION, response_format='json')
......@@ -31,7 +41,7 @@ class SubmissionTest(XBlockHandlerTestCase):
resp = self.request(xblock, 'submit', self.SUBMISSION, response_format='json')
self.assertFalse(resp[0])
self.assertEqual(resp[1], "ENOMULTI")
self.assertEqual(resp[2], xblock.submit_errors["ENOMULTI"])
self.assertEqual(resp[2], xblock.SUBMIT_ERRORS["ENOMULTI"])
@scenario('data/basic_scenario.xml', user_id='Bob')
@patch.object(sub_api, 'create_submission')
......@@ -40,15 +50,16 @@ class SubmissionTest(XBlockHandlerTestCase):
resp = self.request(xblock, 'submit', self.SUBMISSION, response_format='json')
self.assertFalse(resp[0])
self.assertEqual(resp[1], "EUNKNOWN")
self.assertEqual(resp[2], SubmissionMixin().submit_errors["EUNKNOWN"])
self.assertEqual(resp[2], xblock.SUBMIT_ERRORS["EUNKNOWN"])
@scenario('data/basic_scenario.xml', user_id='Bob')
@patch.object(sub_api, 'create_submission')
def test_submission_API_failure(self, xblock, mock_submit):
mock_submit.side_effect = SubmissionRequestError("Cat on fire.")
mock_submit.side_effect = SubmissionRequestError(msg="Cat on fire.")
resp = self.request(xblock, 'submit', self.SUBMISSION, response_format='json')
self.assertFalse(resp[0])
self.assertEqual(resp[1], "EBADFORM")
self.assertEqual(resp[2], xblock.SUBMIT_ERRORS["EBADFORM"])
# In Studio preview mode, the runtime sets the user ID to None
@scenario('data/basic_scenario.xml', user_id=None)
......@@ -65,7 +76,7 @@ class SubmissionTest(XBlockHandlerTestCase):
resp = self.request(xblock, 'submit', self.SUBMISSION, response_format='json')
self.assertFalse(resp[0])
self.assertEqual(resp[1], "ENOPREVIEW")
self.assertEqual(resp[2], "To submit a response, view this component in Preview or Live mode.")
self.assertEqual(resp[2], xblock.SUBMIT_ERRORS["ENOPREVIEW"])
@scenario('data/over_grade_scenario.xml', user_id='Alice')
def test_closed_submissions(self, xblock):
......
......@@ -6,7 +6,7 @@
git+https://github.com/edx/XBlock.git@fc5fea25c973ec66d8db63cf69a817ce624f5ef5#egg=XBlock
git+https://github.com/edx/xblock-sdk.git@643900aadcb18aaeb7fe67271ca9dbf36e463ee6#egg=xblock-sdk
edx-submissions==0.0.3
edx-submissions==0.0.5
# Third Party Requirements
boto==2.13.3
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment