Commit ff6329fe by Tyler Hallada

Undo grade override on status rejected -> verified

parent e9b43963
......@@ -751,6 +751,7 @@ def update_attempt_status(exam_id, user_id, to_status,
else:
return
from_status = exam_attempt_obj.status
exam = get_exam_by_id(exam_id)
#
......@@ -765,7 +766,7 @@ def update_attempt_status(exam_id, user_id, to_status,
'A status transition from {from_status} to {to_status} was attempted '
'on exam_id {exam_id} for user_id {user_id}. This is not '
'allowed!'.format(
from_status=exam_attempt_obj.status,
from_status=from_status,
to_status=to_status,
exam_id=exam_id,
user_id=user_id
......@@ -780,7 +781,7 @@ def update_attempt_status(exam_id, user_id, to_status,
'A status transition from {from_status} to {to_status} was attempted '
'on exam_id {exam_id} for user_id {user_id}. This is not '
'allowed!'.format(
from_status=exam_attempt_obj.status,
from_status=from_status,
to_status=to_status,
exam_id=exam_id,
user_id=user_id
......@@ -916,6 +917,31 @@ def update_attempt_status(exam_id, user_id, to_status,
earned_graded=REJECTED_GRADE_OVERRIDE_EARNED
)
if (to_status == ProctoredExamStudentAttemptStatus.verified and
ProctoredExamStudentAttemptStatus.needs_grade_override(from_status)):
grades_service = get_runtime_service('grades')
log_msg = (
'Deleting override of exam subsection grade for '
'user_id {user_id} on {course_id} for '
'content_id {content_id}. Override '
'earned_all: {earned_all}, '
'earned_graded: {earned_graded}.'.format(
user_id=exam_attempt_obj.user_id,
course_id=exam['course_id'],
content_id=exam_attempt_obj.proctored_exam.content_id,
earned_all=REJECTED_GRADE_OVERRIDE_EARNED,
earned_graded=REJECTED_GRADE_OVERRIDE_EARNED
)
)
log.info(log_msg)
grades_service.undo_override_subsection_grade(
user_id=exam_attempt_obj.user_id,
course_key_or_id=exam['course_id'],
usage_key_or_id=exam_attempt_obj.proctored_exam.content_id,
)
# call service to get course name.
credit_service = get_runtime_service('credit')
credit_state = credit_service.get_credit_state(
......
......@@ -920,23 +920,87 @@ class ProctoredExamApiTests(ProctoredExamTestCase):
the learner's subsection grade for the exam
"""
set_runtime_service('grades', MockGradesService())
grades_service = get_runtime_service('grades')
exam_attempt = self._create_started_exam_attempt()
# Pretend learner answered 5 graded questions in the exam correctly
grades_service.init_grade(
user_id=self.user.id,
course_key_or_id=exam_attempt.proctored_exam.course_id,
usage_key_or_id=exam_attempt.proctored_exam.content_id,
earned_all=5.0,
earned_graded=5.0
)
update_attempt_status(
exam_attempt.proctored_exam_id,
self.user.id,
ProctoredExamStudentAttemptStatus.rejected
)
grades_service = get_runtime_service('grades')
grades = grades_service.get_subsection_grade(user_id=self.user.id,
course_key_or_id=exam_attempt.proctored_exam.course_id,
usage_key_or_id=exam_attempt.proctored_exam.content_id)
# Rejected exam attempt should override learner's grade to 0
override = grades_service.get_subsection_grade_override(
user_id=self.user.id,
course_key_or_id=exam_attempt.proctored_exam.course_id,
usage_key_or_id=exam_attempt.proctored_exam.content_id
)
self.assertDictEqual({
'earned_all': override.earned_all_override,
'earned_graded': override.earned_graded_override
}, {
'earned_all': 0.0,
'earned_graded': 0.0
})
# The MockGradeService updates the PersistentSubsectionGrade synchronously, but in the real GradesService, this
# would be updated by an asynchronous recalculation celery task.
grade = grades_service.get_subsection_grade(
user_id=self.user.id,
course_key_or_id=exam_attempt.proctored_exam.course_id,
usage_key_or_id=exam_attempt.proctored_exam.content_id
)
self.assertEqual(grades, {
self.assertDictEqual({
'earned_all': grade.earned_all,
'earned_graded': grade.earned_graded
}, {
'earned_all': 0.0,
'earned_graded': 0.0
})
# Verify that transitioning an attempt from the rejected state to the verified state
# will remove the override for the learner's subsection grade on the exam that was created
# when the attempt entered the rejected state.
update_attempt_status(
exam_attempt.proctored_exam_id,
self.user.id,
ProctoredExamStudentAttemptStatus.verified
)
override = grades_service.get_subsection_grade_override(
user_id=self.user.id,
course_key_or_id=exam_attempt.proctored_exam.course_id,
usage_key_or_id=exam_attempt.proctored_exam.content_id
)
self.assertIsNone(override)
grade = grades_service.get_subsection_grade(
user_id=self.user.id,
course_key_or_id=exam_attempt.proctored_exam.course_id,
usage_key_or_id=exam_attempt.proctored_exam.content_id
)
# Grade has returned to original score
self.assertDictEqual({
'earned_all': grade.earned_all,
'earned_graded': grade.earned_graded
}, {
'earned_all': 5.0,
'earned_graded': 5.0
})
@ddt.data(
(ProctoredExamStudentAttemptStatus.declined, ProctoredExamStudentAttemptStatus.eligible),
(ProctoredExamStudentAttemptStatus.timed_out, ProctoredExamStudentAttemptStatus.created),
......
......@@ -175,22 +175,62 @@ class TestProctoringService(unittest.TestCase):
self.assertIs(service1, service2)
class MockGrade(object):
"""Fake PersistentSubsectionGrade instance."""
def __init__(self, earned_all=0.0, earned_graded=0.0):
self.earned_all = earned_all
self.earned_graded = earned_graded
class MockGradeOverride(object):
"""Fake PersistentSubsectionGradeOverride instance."""
def __init__(self, earned_all=0.0, earned_graded=0.0):
self.earned_all_override = earned_all
self.earned_graded_override = earned_graded
class MockGradesService(object):
"""
Simple mock of the Grades Service
"""
def __init__(self):
"""Initialize empty data store for grades (a dict)"""
"""Initialize empty data stores for grades and overrides (just dicts)"""
self.grades = {}
self.overrides = {}
def init_grade(self, user_id, course_key_or_id, usage_key_or_id, earned_all, earned_graded):
"""Initialize a grade in MockGradesService for testing. Actual GradesService does not have this method."""
self.grades[str(user_id) + str(course_key_or_id) + str(usage_key_or_id)] = MockGrade(
earned_all=earned_all,
earned_graded=earned_graded
)
def get_subsection_grade(self, user_id, course_key_or_id, usage_key_or_id):
"""Returns entered grade override for key (user_id + course_key + subsection) or None"""
"""Returns entered grade for key (user_id + course_key + subsection) or None"""
key = str(user_id) + str(course_key_or_id) + str(usage_key_or_id)
if key in self.overrides:
# pretend override was applied
return MockGrade(
earned_all=self.overrides[key].earned_all_override,
earned_graded=self.overrides[key].earned_graded_override
)
return self.grades.get(str(user_id) + str(course_key_or_id) + str(usage_key_or_id))
def get_subsection_grade_override(self, user_id, course_key_or_id, usage_key_or_id):
"""Returns entered grade override for key (user_id + course_key + subsection) or None"""
return self.overrides.get(str(user_id) + str(course_key_or_id) + str(usage_key_or_id))
def override_subsection_grade(self, user_id, course_key_or_id, usage_key_or_id, earned_all=None,
earned_graded=None):
"""Sets grade override earned points for key (user_id + course_key + subsection)"""
self.grades[str(user_id) + str(course_key_or_id) + str(usage_key_or_id)] = {
'earned_all': earned_all,
'earned_graded': earned_graded
}
key = str(user_id) + str(course_key_or_id) + str(usage_key_or_id)
self.overrides[key] = MockGradeOverride(
earned_all=earned_all,
earned_graded=earned_graded
)
def undo_override_subsection_grade(self, user_id, course_key_or_id, usage_key_or_id):
"""Deletes grade override for key (user_id + course_key + subsection)"""
key = str(user_id) + str(course_key_or_id) + str(usage_key_or_id)
if key in self.overrides:
del self.overrides[key]
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