Skip to content

Commit

Permalink
fix: Refactoring of sending information to sent_to_submission
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrielC1409 committed Feb 27, 2025
1 parent b1b18b5 commit 4575be5
Show file tree
Hide file tree
Showing 12 changed files with 231 additions and 232 deletions.
2 changes: 0 additions & 2 deletions xmodule/capa/capa_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@
import xmodule.capa.inputtypes as inputtypes
import xmodule.capa.responsetypes as responsetypes
import xmodule.capa.xqueue_interface as xqueue_interface
import xmodule.capa.xqueue_submission as xqueue_submission
from xmodule.capa.xqueue_interface import get_flag_by_name
from xmodule.capa.correctmap import CorrectMap
from xmodule.capa.safe_exec import safe_exec
from xmodule.capa.util import contextualize_text, convert_files_to_filenames, get_course_id_from_capa_block
Expand Down
50 changes: 50 additions & 0 deletions xmodule/capa/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# errors.py
"""
Custom error handling for the XQueue submission interface.
"""

class XQueueSubmissionError(Exception):
"""Base class for all XQueue submission errors."""
# No es necesario el `pass`, la clase ya hereda de Exception.

class JSONParsingError(XQueueSubmissionError):
"""Raised when JSON parsing fails."""
MESSAGE = "Error parsing {name}: {error}"

def __init__(self, name, error):
super().__init__(self.MESSAGE.format(name=name, error=error))

class MissingKeyError(XQueueSubmissionError):
"""Raised when a required key is missing."""
MESSAGE = "Missing key: {key}"

def __init__(self, key):
super().__init__(self.MESSAGE.format(key=key))

class ValidationError(XQueueSubmissionError):
"""Raised when a validation check fails."""
MESSAGE = "Validation error: {error}"

def __init__(self, error):
super().__init__(self.MESSAGE.format(error=error))

class TypeErrorSubmission(XQueueSubmissionError):
"""Raised when an invalid type is encountered."""
MESSAGE = "Type error: {error}"

def __init__(self, error):
super().__init__(self.MESSAGE.format(error=error))

class RuntimeErrorSubmission(XQueueSubmissionError):
"""Raised for runtime errors."""
MESSAGE = "Runtime error: {error}"

def __init__(self, error):
super().__init__(self.MESSAGE.format(error=error))

class GetSubmissionParamsError(XQueueSubmissionError):
"""Raised when there is an issue getting submission parameters."""
MESSAGE = "Block instance is not defined!"

def __init__(self):
super().__init__(self.MESSAGE)
4 changes: 2 additions & 2 deletions xmodule/capa/inputtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@

from lxml import etree

from xmodule.capa.xqueue_interface import XQUEUE_TIMEOUT, get_flag_by_name
from xmodule.capa.xqueue_interface import XQUEUE_TIMEOUT
from openedx.core.djangolib.markup import HTML, Text
from xmodule.stringify import stringify_children

from . import xqueue_interface, xqueue_submission
from . import xqueue_interface
from .registry import TagRegistry
from .util import sanitize_html

Expand Down
22 changes: 11 additions & 11 deletions xmodule/capa/tests/test_capa_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def test_label_and_description_inside_responsetype(self, question):
""".format(question=question)
problem = new_loncapa_problem(xml)
assert problem.problem_data ==\
{'1_2_1': {'label': question, 'descriptions': {'description_1_1_1': 'Only the paranoid survive.'}}}
{'1_2_1': {'label': question, 'descriptions': {'description_1_1_1': 'Only the paranoid survive.'}}}
assert len(problem.tree.xpath('//label')) == 0

@ddt.unpack
Expand Down Expand Up @@ -123,7 +123,7 @@ def test_neither_label_tag_nor_attribute(self, question1, question2):
""".format(question1, question2)
problem = new_loncapa_problem(xml)
assert problem.problem_data ==\
{'1_2_1': {'label': question1, 'descriptions': {}}, '1_3_1': {'label': question2, 'descriptions': {}}}
{'1_2_1': {'label': question1, 'descriptions': {}}, '1_3_1': {'label': question2, 'descriptions': {}}}
for question in (question1, question2):
assert len(problem.tree.xpath('//label[text()="{}"]'.format(question))) == 0

Expand All @@ -146,8 +146,8 @@ def test_multiple_descriptions(self):
""".format(desc1, desc2)
problem = new_loncapa_problem(xml)
assert problem.problem_data ==\
{'1_2_1': {'label': '___ requires sacrifices.',
'descriptions': {'description_1_1_1': desc1, 'description_1_1_2': desc2}}}
{'1_2_1': {'label': '___ requires sacrifices.',
'descriptions': {'description_1_1_1': desc1, 'description_1_1_2': desc2}}}

def test_additional_answer_is_skipped_from_resulting_html(self):
"""Tests that additional_answer element is not present in transformed HTML"""
Expand Down Expand Up @@ -236,10 +236,10 @@ def test_multiple_questions_problem(self):
"""
problem = new_loncapa_problem(xml)
assert problem.problem_data ==\
{'1_2_1': {'label': 'Select the correct synonym of paranoid?',
'descriptions': {'description_1_1_1': 'Only the paranoid survive.'}},
'1_3_1': {'label': 'What Apple device competed with the portable CD player?',
'descriptions': {'description_1_2_1': 'Device looks like an egg plant.'}}}
{'1_2_1': {'label': 'Select the correct synonym of paranoid?',
'descriptions': {'description_1_1_1': 'Only the paranoid survive.'}},
'1_3_1': {'label': 'What Apple device competed with the portable CD player?',
'descriptions': {'description_1_2_1': 'Device looks like an egg plant.'}}}
assert len(problem.tree.xpath('//label')) == 0

def test_question_title_not_removed_got_children(self):
Expand Down Expand Up @@ -291,8 +291,8 @@ def test_multiple_inputtypes(self, group_label):

problem = new_loncapa_problem(xml)
assert problem.problem_data ==\
{'1_2_1': {'group_label': group_label, 'label': input1_label, 'descriptions': {}},
'1_2_2': {'group_label': group_label, 'label': input2_label, 'descriptions': {}}}
{'1_2_1': {'group_label': group_label, 'label': input1_label, 'descriptions': {}},
'1_2_2': {'group_label': group_label, 'label': input2_label, 'descriptions': {}}}

def test_single_inputtypes(self):
"""
Expand Down Expand Up @@ -355,7 +355,7 @@ def assert_question_tag(self, question1, question2, tag, label_attr=False):
)
problem = new_loncapa_problem(xml)
assert problem.problem_data ==\
{'1_2_1': {'label': question1, 'descriptions': {}}, '1_3_1': {'label': question2, 'descriptions': {}}}
{'1_2_1': {'label': question1, 'descriptions': {}}, '1_3_1': {'label': question2, 'descriptions': {}}}
assert len(problem.tree.xpath('//{}'.format(tag))) == 0

@ddt.unpack
Expand Down
43 changes: 43 additions & 0 deletions xmodule/capa/tests/test_errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""
Unit tests for custom error handling in the XQueue submission interface.
"""

import pytest
from xmodule.capa.errors import (
JSONParsingError,
MissingKeyError,
ValidationError,
TypeErrorSubmission,
RuntimeErrorSubmission,
GetSubmissionParamsError
)

def test_json_parsing_error():
with pytest.raises(JSONParsingError) as excinfo:
raise JSONParsingError("test_name", "test_error")
assert str(excinfo.value) == "Error parsing test_name: test_error"

def test_missing_key_error():
with pytest.raises(MissingKeyError) as excinfo:
raise MissingKeyError("test_key")
assert str(excinfo.value) == "Missing key: test_key"

def test_validation_error():
with pytest.raises(ValidationError) as excinfo:
raise ValidationError("test_error")
assert str(excinfo.value) == "Validation error: test_error"

def test_type_error_submission():
with pytest.raises(TypeErrorSubmission) as excinfo:
raise TypeErrorSubmission("test_error")
assert str(excinfo.value) == "Type error: test_error"

def test_runtime_error_submission():
with pytest.raises(RuntimeErrorSubmission) as excinfo:
raise RuntimeErrorSubmission("test_error")
assert str(excinfo.value) == "Runtime error: test_error"

def test_get_submission_params_error():
with pytest.raises(GetSubmissionParamsError) as excinfo:
raise GetSubmissionParamsError()
assert str(excinfo.value) == "Block instance is not defined!"
45 changes: 0 additions & 45 deletions xmodule/capa/tests/test_inputtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,12 +476,10 @@ def test_rendering(self):
assert context == expected


@pytest.mark.django_db
class MatlabTest(unittest.TestCase):
"""
Test Matlab input types
"""

def setUp(self):
super(MatlabTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
self.rows = '10'
Expand Down Expand Up @@ -930,40 +928,6 @@ def test_matlab_sanitize_msg(self):
expected = ""
assert self.the_input._get_render_context()['msg'] == expected # pylint: disable=protected-access

@patch('xmodule.capa.inputtypes.get_flag_by_name', return_value=True)
@patch('xmodule.capa.inputtypes.datetime')
def test_plot_data_with_flag_active(self, mock_datetime, mock_get_flag_by_name):
"""
Test that the correct date format is used when the flag is active.
"""
mock_datetime.utcnow.return_value.strftime.return_value = 'formatted_date_with_flag'
data = {'submission': 'x = 1234;'}
response = self.the_input.handle_ajax("plot", data)
self.the_input.capa_system.xqueue.interface.send_to_queue.assert_called_with(header=ANY, body=ANY)
assert response['success']
assert self.the_input.input_state['queuekey'] is not None
assert self.the_input.input_state['queuestate'] == 'queued'
assert 'formatted_date_with_flag' in self.the_input.capa_system.xqueue.interface.send_to_queue.call_args[1][
'body'
]

@patch('xmodule.capa.inputtypes.get_flag_by_name', return_value=False)
@patch('xmodule.capa.inputtypes.datetime')
def test_plot_data_with_flag_inactive(self, mock_datetime, mock_get_flag_by_name):
"""
Test that the correct date format is used when the flag is inactive.
"""
mock_datetime.utcnow.return_value.strftime.return_value = 'formatted_date_without_flag'
data = {'submission': 'x = 1234;'}
response = self.the_input.handle_ajax("plot", data)
self.the_input.capa_system.xqueue.interface.send_to_queue.assert_called_with(header=ANY, body=ANY)
assert response['success']
assert self.the_input.input_state['queuekey'] is not None
assert self.the_input.input_state['queuestate'] == 'queued'
assert 'formatted_date_without_flag' in self.the_input.capa_system.xqueue.interface.send_to_queue.call_args[1][
'body'
]


def html_tree_equal(received, expected):
"""
Expand All @@ -983,7 +947,6 @@ class SchematicTest(unittest.TestCase):
"""
Check that schematic inputs work
"""

def test_rendering(self):
height = '12'
width = '33'
Expand Down Expand Up @@ -1039,7 +1002,6 @@ class ImageInputTest(unittest.TestCase):
"""
Check that image inputs work
"""

def check(self, value, egx, egy): # lint-amnesty, pylint: disable=missing-function-docstring
height = '78'
width = '427'
Expand Down Expand Up @@ -1099,7 +1061,6 @@ class CrystallographyTest(unittest.TestCase):
"""
Check that crystallography inputs work
"""

def test_rendering(self):
height = '12'
width = '33'
Expand Down Expand Up @@ -1141,7 +1102,6 @@ class VseprTest(unittest.TestCase):
"""
Check that vsepr inputs work
"""

def test_rendering(self):
height = '12'
width = '33'
Expand Down Expand Up @@ -1189,7 +1149,6 @@ class ChemicalEquationTest(unittest.TestCase):
"""
Check that chemical equation inputs work.
"""

def setUp(self):
super(ChemicalEquationTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
self.size = "42"
Expand Down Expand Up @@ -1285,7 +1244,6 @@ class FormulaEquationTest(unittest.TestCase):
"""
Check that formula equation inputs work.
"""

def setUp(self):
super(FormulaEquationTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
self.size = "42"
Expand Down Expand Up @@ -1434,7 +1392,6 @@ class DragAndDropTest(unittest.TestCase):
"""
Check that drag and drop inputs work
"""

def test_rendering(self):
path_to_images = '/dummy-static/images/'

Expand Down Expand Up @@ -1509,7 +1466,6 @@ class AnnotationInputTest(unittest.TestCase):
"""
Make sure option inputs work
"""

def test_rendering(self):
xml_str = '''
<annotationinput>
Expand Down Expand Up @@ -1670,7 +1626,6 @@ class TestStatus(unittest.TestCase):
"""
Tests for Status class
"""

def test_str(self):
"""
Test stringifing Status objects
Expand Down
11 changes: 2 additions & 9 deletions xmodule/capa/tests/test_responsetypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@
)
from xmodule.capa.util import convert_files_to_filenames
from xmodule.capa.xqueue_interface import dateformat
import xmodule.capa.xqueue_submission as xqueue_submission
from xmodule.capa.xqueue_interface import get_flag_by_name


class ResponseTest(unittest.TestCase):
Expand Down Expand Up @@ -931,7 +929,6 @@ def test_empty_answer_graded_as_incorrect(self):
self.assert_grade(problem, " ", "incorrect")


@pytest.mark.django_db
class CodeResponseTest(ResponseTest): # pylint: disable=missing-class-docstring
xml_factory_class = CodeResponseXMLFactory

Expand All @@ -947,12 +944,8 @@ def setUp(self):
@staticmethod
def make_queuestate(key, time):
"""Create queuestate dict"""
if get_flag_by_name('send_to_submission_course.enable'):
timestr = datetime.strftime(time, xqueue_submission.dateformat)
return {'key': key, 'time': timestr}
else:
timestr = datetime.strftime(time, dateformat)
return {'key': key, 'time': timestr}
timestr = datetime.strftime(time, dateformat)
return {'key': key, 'time': timestr}

def test_is_queued(self):
"""
Expand Down
27 changes: 13 additions & 14 deletions xmodule/capa/tests/test_xqueue_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,30 +46,27 @@ def test_construct_callback_with_flag_enabled(self, mock_flag):
course_id = str(usage_id.course_key)
callback_url = f"courses/{course_id}/xqueue/user1/{usage_id}"

assert self.service.construct_callback() == f"{settings.LMS_ROOT_URL}/{callback_url}/score_update10"
assert self.service.construct_callback() == f"{settings.LMS_ROOT_URL}/{callback_url}/score_update"
assert self.service.construct_callback("alt_dispatch") == (
f"{settings.LMS_ROOT_URL}/{callback_url}/alt_dispatch10"
f"{settings.LMS_ROOT_URL}/{callback_url}/alt_dispatch"
)

custom_callback_url = "http://alt.url"
with override_settings(XQUEUE_INTERFACE={**settings.XQUEUE_INTERFACE, "callback_url": custom_callback_url}):
assert self.service.construct_callback() == f"{custom_callback_url}/{callback_url}/score_update10"
assert self.service.construct_callback() == f"{custom_callback_url}/{callback_url}/score_update"

@patch("xmodule.capa.xqueue_interface.is_flag_active", return_value=False)
def test_construct_callback_with_flag_disabled(self, mock_flag):
"""Test construct_callback when the waffle flag is disabled."""
usage_id = self.block.scope_ids.usage_id
course_id = str(usage_id.course_key)
callback_url = f"courses/{course_id}/xqueue/user1/{usage_id}"
callback_url = f'courses/{usage_id.context_key}/xqueue/user1/{usage_id}'

assert self.service.construct_callback() == f"{settings.LMS_ROOT_URL}/{callback_url}/score_update"
assert self.service.construct_callback("alt_dispatch") == (
f"{settings.LMS_ROOT_URL}/{callback_url}/alt_dispatch"
)
assert self.service.construct_callback() == f'{settings.LMS_ROOT_URL}/{callback_url}/score_update'
assert self.service.construct_callback('alt_dispatch') == f'{settings.LMS_ROOT_URL}/{callback_url}/alt_dispatch'

custom_callback_url = "http://alt.url"
with override_settings(XQUEUE_INTERFACE={**settings.XQUEUE_INTERFACE, "callback_url": custom_callback_url}):
assert self.service.construct_callback() == f"{custom_callback_url}/{callback_url}/score_update"
custom_callback_url = 'http://alt.url'
with override_settings(XQUEUE_INTERFACE={**settings.XQUEUE_INTERFACE, 'callback_url': custom_callback_url}):
assert self.service.construct_callback() == f'{custom_callback_url}/{callback_url}/score_update'

def test_default_queuename(self):
"""Check the format of the default queue name."""
Expand All @@ -90,7 +87,8 @@ def test_send_to_queue_with_flag_enabled(mock_send_to_submission, mock_flag):
"""Test send_to_queue when the waffle flag is enabled."""
url = "http://example.com/xqueue"
django_auth = {"username": "user", "password": "pass"}
xqueue_interface = XQueueInterface(url, django_auth)
block = Mock() # Mock block for the constructor
xqueue_interface = XQueueInterface(url, django_auth, block=block)

header = json.dumps({
"lms_callback_url": (
Expand All @@ -117,7 +115,8 @@ def test_send_to_queue_with_flag_disabled(mock_http_post, mock_flag):
"""Test send_to_queue when the waffle flag is disabled."""
url = "http://example.com/xqueue"
django_auth = {"username": "user", "password": "pass"}
xqueue_interface = XQueueInterface(url, django_auth)
block = Mock() # Mock block for the constructor
xqueue_interface = XQueueInterface(url, django_auth, block=block)

header = json.dumps({
"lms_callback_url": (
Expand Down
Loading

0 comments on commit 4575be5

Please sign in to comment.