handles exam events from event bus that impact the 'instructor' app. These apis deal with learner completion and managing problem attempt state.
190 lines
8.5 KiB
Python
190 lines
8.5 KiB
Python
"""
|
|
Tests for tasks.py
|
|
"""
|
|
import json
|
|
from unittest import mock
|
|
|
|
from completion.waffle import ENABLE_COMPLETION_TRACKING_SWITCH
|
|
from edx_toggles.toggles.testutils import override_waffle_switch
|
|
|
|
from common.djangoapps.student.models import CourseEnrollment
|
|
from common.djangoapps.student.tests.factories import UserFactory
|
|
from lms.djangoapps.courseware.models import StudentModule
|
|
from lms.djangoapps.instructor.tasks import update_exam_completion_task
|
|
from xmodule.modulestore.tests.django_utils import \
|
|
SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
|
|
from xmodule.modulestore.tests.factories import ( # lint-amnesty, pylint: disable=wrong-import-order
|
|
BlockFactory,
|
|
CourseFactory
|
|
)
|
|
from xmodule.partitions.partitions import Group, UserPartition # lint-amnesty, pylint: disable=wrong-import-order
|
|
|
|
|
|
class UpdateCompletionTests(SharedModuleStoreTestCase):
|
|
"""
|
|
Test the update_exam_completion_task
|
|
"""
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.email = 'escalation@test.com'
|
|
cls.course = CourseFactory.create(proctoring_escalation_email=cls.email)
|
|
cls.section = BlockFactory.create(parent=cls.course, category='chapter')
|
|
cls.subsection = BlockFactory.create(parent=cls.section, category='sequential')
|
|
cls.unit = BlockFactory.create(parent=cls.subsection, category='vertical')
|
|
cls.problem = BlockFactory.create(parent=cls.unit, category='problem')
|
|
cls.unit_2 = BlockFactory.create(parent=cls.subsection, category='vertical')
|
|
cls.problem_2 = BlockFactory.create(parent=cls.unit_2, category='problem')
|
|
cls.complete_error_prefix = ('Error occurred while attempting to complete student attempt for '
|
|
'user {user} for content_id {content_id}. ')
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
|
|
self.student = UserFactory()
|
|
CourseEnrollment.enroll(self.student, self.course.id)
|
|
|
|
self.module_to_reset = StudentModule.objects.create(
|
|
student=self.student,
|
|
course_id=self.course.id,
|
|
module_state_key=self.problem.location,
|
|
state=json.dumps({'attempts': 2}),
|
|
)
|
|
|
|
@mock.patch('completion.handlers.BlockCompletion.objects.submit_completion')
|
|
def test_update_completion_success(self, mock_submit):
|
|
"""
|
|
Assert correctly publishes completion for all
|
|
completable children of the given content_id
|
|
"""
|
|
# Section, subsection, and unit are all aggregators and not completable so should
|
|
# not be submitted.
|
|
section = BlockFactory.create(parent=self.course, category='chapter')
|
|
subsection = BlockFactory.create(parent=section, category='sequential')
|
|
unit = BlockFactory.create(parent=subsection, category='vertical')
|
|
|
|
# should both be submitted
|
|
video = BlockFactory.create(parent=unit, category='video')
|
|
problem = BlockFactory.create(parent=unit, category='problem')
|
|
|
|
# Not a completable block
|
|
BlockFactory.create(parent=unit, category='discussion')
|
|
|
|
with override_waffle_switch(ENABLE_COMPLETION_TRACKING_SWITCH, True):
|
|
update_exam_completion_task(self.student.username, str(subsection.location), 1.0)
|
|
|
|
# Only Completable leaf blocks should have completion published
|
|
assert mock_submit.call_count == 2
|
|
mock_submit.assert_any_call(user=self.student, block_key=video.location, completion=1.0)
|
|
mock_submit.assert_any_call(user=self.student, block_key=problem.location, completion=1.0)
|
|
|
|
@mock.patch('completion.handlers.BlockCompletion.objects.submit_completion')
|
|
def test_update_completion_delete(self, mock_submit):
|
|
"""
|
|
Test update completion with a value of 0.0
|
|
"""
|
|
with override_waffle_switch(ENABLE_COMPLETION_TRACKING_SWITCH, True):
|
|
update_exam_completion_task(self.student.username, str(self.subsection.location), 0.0)
|
|
|
|
# Assert we send completion == 0.0 for both problems
|
|
assert mock_submit.call_count == 2
|
|
mock_submit.assert_any_call(user=self.student, block_key=self.problem.location, completion=0.0)
|
|
mock_submit.assert_any_call(user=self.student, block_key=self.problem_2.location, completion=0.0)
|
|
|
|
@mock.patch('completion.handlers.BlockCompletion.objects.submit_completion')
|
|
def test_update_completion_split_test(self, mock_submit):
|
|
"""
|
|
Asserts correctly publishes completion when a split test is involved
|
|
|
|
This test case exists because we ran into a bug about the user_service not existing
|
|
when a split_test existed inside of a subsection. Associated with this change was adding
|
|
in the user state into the module before attempting completion and this ensures that is
|
|
working properly.
|
|
"""
|
|
partition = UserPartition(
|
|
0,
|
|
'first_partition',
|
|
'First Partition',
|
|
[
|
|
Group(0, 'alpha'),
|
|
Group(1, 'beta')
|
|
]
|
|
)
|
|
course = CourseFactory.create(user_partitions=[partition])
|
|
section = BlockFactory.create(parent=course, category='chapter')
|
|
subsection = BlockFactory.create(parent=section, category='sequential')
|
|
|
|
c0_url = course.id.make_usage_key('vertical', 'split_test_cond0')
|
|
c1_url = course.id.make_usage_key('vertical', 'split_test_cond1')
|
|
split_test = BlockFactory.create(
|
|
parent=subsection,
|
|
category='split_test',
|
|
user_partition_id=0,
|
|
group_id_to_child={'0': c0_url, '1': c1_url},
|
|
)
|
|
|
|
cond0vert = BlockFactory.create(parent=split_test, category='vertical', location=c0_url)
|
|
BlockFactory.create(parent=cond0vert, category='video')
|
|
BlockFactory.create(parent=cond0vert, category='problem')
|
|
|
|
cond1vert = BlockFactory.create(parent=split_test, category='vertical', location=c1_url)
|
|
BlockFactory.create(parent=cond1vert, category='video')
|
|
BlockFactory.create(parent=cond1vert, category='html')
|
|
|
|
with override_waffle_switch(ENABLE_COMPLETION_TRACKING_SWITCH, True):
|
|
update_exam_completion_task(self.student.username, str(subsection.location), 1.0)
|
|
|
|
# Only the group the user was assigned to should have completion published.
|
|
# Either cond0vert's children or cond1vert's children
|
|
assert mock_submit.call_count == 2
|
|
|
|
@mock.patch('lms.djangoapps.instructor.tasks.log.error')
|
|
def test_update_completion_bad_user(self, mock_logger):
|
|
"""
|
|
Assert a bad user raises error and returns None
|
|
"""
|
|
username = 'bad_user'
|
|
block_id = str(self.problem.location)
|
|
update_exam_completion_task(username, block_id, 1.0)
|
|
mock_logger.assert_called_once_with(
|
|
self.complete_error_prefix.format(user=username, content_id=block_id) + 'User does not exist!'
|
|
)
|
|
|
|
@mock.patch('lms.djangoapps.instructor.tasks.log.error')
|
|
def test_update_completion_bad_content_id(self, mock_logger):
|
|
"""
|
|
Assert a bad content_id raises error and returns None
|
|
"""
|
|
username = self.student.username
|
|
update_exam_completion_task(username, 'foo/bar/baz', 1.0)
|
|
mock_logger.assert_called_once_with(
|
|
self.complete_error_prefix.format(user=username, content_id='foo/bar/baz') + 'Invalid content_id!'
|
|
)
|
|
|
|
@mock.patch('lms.djangoapps.instructor.tasks.log.error')
|
|
def test_update_completion_nonexisting_item(self, mock_logger):
|
|
"""
|
|
Assert nonexisting item in the modulestore
|
|
raises error and returns None
|
|
"""
|
|
username = self.student.username
|
|
block = 'i4x://org.0/course_0/problem/fake_problem'
|
|
update_exam_completion_task(username, block, 1.0)
|
|
mock_logger.assert_called_once_with(
|
|
self.complete_error_prefix.format(user=username, content_id=block) + 'Block not found in the modulestore!'
|
|
)
|
|
|
|
@mock.patch('lms.djangoapps.instructor.tasks.log.error')
|
|
def test_update_completion_failed_module(self, mock_logger):
|
|
"""
|
|
Assert failed get_block raises error and returns None
|
|
"""
|
|
username = self.student.username
|
|
with mock.patch('lms.djangoapps.instructor.tasks.get_block_for_descriptor', return_value=None):
|
|
update_exam_completion_task(username, str(self.course.location), 1.0)
|
|
mock_logger.assert_called_once_with(
|
|
self.complete_error_prefix.format(user=username, content_id=self.course.location) +
|
|
'Block unable to be created from descriptor!'
|
|
)
|