Files
edx-platform/xmodule/capa/tests/test_xqueue_submission.py
Leonardo Beroes 70ea641c99 feat: Improve robust score rendering with event-based architecture
This commit implements a comprehensive solution for test score integration in the
enhancement system along with improvements to the score rendering mechanism. Key
changes include:

- Add event handler for rendering blocks with edx-submissions scores
- Implement event-based mechanism to render XBlocks with scoring data
- Create signal handlers in handlers.py to process external grader scores
- Develop specialized XBlock loader for rendering without HTTP requests
- Add queue_key propagation across the submission pipeline
- Register submission URLs in LMS routing configuration
- Add complete docstrings to score render module for better code maintainability
- Add ADR for XBlock rendering with external grader integration
- Add openedx-events fork branch as a dependency in testing.in
- Upgrade edx submission dependency

These changes support the migration from traditional XQueue callback HTTP requests
to a more robust event-based architecture, improving performance and reliability
when processing submission scores. The included ADR documents the architectural
decision and implementation approach for this significant improvement to the
external grading workflow.
2025-12-15 09:01:40 -05:00

124 lines
4.0 KiB
Python

"""
Unit tests for the XQueueInterfaceSubmission class.
"""
import json
from unittest.mock import Mock, patch
import pytest
from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator
from xblock.fields import ScopeIds
from xmodule.capa.xqueue_submission import XQueueInterfaceSubmission
@pytest.fixture
def xqueue_service():
"""
Fixture that returns an instance of XQueueInterfaceSubmission.
"""
location = BlockUsageLocator(CourseLocator("test_org", "test_course", "test_run"), "problem", "ExampleProblem")
block = Mock(scope_ids=ScopeIds("user1", "problem", location, location))
block.max_score = Mock(return_value=10)
return XQueueInterfaceSubmission(block)
def test_get_submission_params(xqueue_service):
"""
Test extracting item data from an xqueue submission.
"""
header = json.dumps({"lms_callback_url": "http://example.com/callback", "queue_name": "default"})
payload = json.dumps(
{
"student_info": json.dumps({"anonymous_student_id": "student_id"}),
"student_response": "student_answer",
"grader_payload": json.dumps({"grader": "test.py"}),
}
)
student_item, student_answer, queue_name, grader_file_name, points_possible = xqueue_service.get_submission_params(
header, payload
)
assert student_item == {
"item_id": "block-v1:test_org+test_course+test_run+type@problem+block@ExampleProblem",
"item_type": "problem",
"course_id": "course-v1:test_org+test_course+test_run",
"student_id": "student_id",
}
assert student_answer == "student_answer"
assert queue_name == "default"
assert grader_file_name == "test.py"
assert points_possible == 10
@pytest.mark.django_db
@patch("submissions.api.create_external_grader_detail")
def test_send_to_submission(mock_create_external_grader_detail, xqueue_service):
"""
Test sending a submission to the grading system.
"""
header = json.dumps(
{
"lms_callback_url": (
"http://example.com/courses/course-v1:test_org+test_course+test_run/xqueue/5/"
"block-v1:test_org+test_course+test_run+type@problem+block@ExampleProblem/"
),
}
)
body = json.dumps(
{
"student_info": json.dumps({"anonymous_student_id": "student_id"}),
"student_response": "student_answer",
"grader_payload": json.dumps({"grader": "test.py"}),
}
)
mock_response = {"submission": "mock_submission"}
mock_create_external_grader_detail.return_value = mock_response
result = xqueue_service.send_to_submission(header, body, queue_key="default")
assert result == mock_response
mock_create_external_grader_detail.assert_called_once_with(
{
"item_id": "block-v1:test_org+test_course+test_run+type@problem+block@ExampleProblem",
"item_type": "problem",
"course_id": "course-v1:test_org+test_course+test_run",
"student_id": "student_id",
},
'student_answer',
queue_name='default',
queue_key='default',
grader_file_name='test.py',
points_possible=10,
files=None,
)
@pytest.mark.django_db
@patch("submissions.api.create_external_grader_detail")
def test_send_to_submission_with_missing_fields(mock_create_external_grader_detail, xqueue_service):
"""
Test send_to_submission with missing required fields.
"""
header = json.dumps(
{
"lms_callback_url": (
"http://example.com/courses/course-v1:test_org+test_course+test_run/xqueue/5/" "block@item_id/"
)
}
)
body = json.dumps(
{
"student_info": json.dumps({"anonymous_student_id": "student_id"}),
"grader_payload": json.dumps({"grader": "test.py"}),
}
)
result = xqueue_service.send_to_submission(header, body, queue_key="default")
assert "error" in result
assert "Validation error" in result["error"]
mock_create_external_grader_detail.assert_not_called()