Commit 46523b10 by Nimisha Asthagiri Committed by J. Cliff Dyer

Transformer: ProctoredExamTransformer

parent 06980764
"""
Proctored Exams Transformer
"""
from django.conf import settings
from edx_proctoring.api import get_attempt_status_summary
from edx_proctoring.models import ProctoredExamStudentAttemptStatus
from openedx.core.lib.block_cache.transformer import BlockStructureTransformer
class ProctoredExamTransformer(BlockStructureTransformer):
"""
Exclude proctored exams unless the user is not a verified student or has
declined taking the exam.
"""
VERSION = 1
BLOCK_HAS_PROCTORED_EXAM = 'has_proctored_exam'
@classmethod
def name(cls):
return "proctored_exam"
@classmethod
def collect(cls, block_structure):
"""
Computes any information for each XBlock that's necessary to execute
this transformer's transform method.
Arguments:
block_structure (BlockStructureCollectedData)
"""
block_structure.request_xblock_fields('is_proctored_enabled')
block_structure.request_xblock_fields('is_practice_exam')
def transform(self, usage_info, block_structure):
"""
Mutates block_structure based on the given usage_info.
"""
if not settings.FEATURES.get('ENABLE_PROCTORED_EXAMS', False):
return
def is_proctored_exam_for_user(block_key):
"""
Test whether the block is a proctored exam for the user in
question.
"""
if (
block_key.block_type == 'sequential' and (
block_structure.get_xblock_field(block_key, 'is_proctored_enabled') or
block_structure.get_xblock_field(block_key, 'is_practice_exam')
)
):
# This section is an exam. It should be excluded unless the
# user is not a verified student or has declined taking the exam.
user_exam_summary = get_attempt_status_summary(
usage_info.user.id,
unicode(block_key.course_key),
unicode(block_key),
)
return user_exam_summary and user_exam_summary['status'] != ProctoredExamStudentAttemptStatus.declined
block_structure.remove_block_if(is_proctored_exam_for_user)
"""
Tests for ProctoredExamTransformer.
"""
from mock import patch
import ddt
from edx_proctoring.api import (
create_exam,
create_exam_attempt,
update_attempt_status
)
from edx_proctoring.models import ProctoredExamStudentAttemptStatus
from edx_proctoring.runtime import set_runtime_service
from edx_proctoring.tests.test_services import MockCreditService
from lms.djangoapps.course_blocks.transformers.tests.test_helpers import CourseStructureTestCase
from student.tests.factories import CourseEnrollmentFactory
from ..proctored_exam import ProctoredExamTransformer
from ...api import get_course_blocks
@ddt.ddt
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_PROCTORED_EXAMS': True})
class ProctoredExamTransformerTestCase(CourseStructureTestCase):
"""
Test behavior of ProctoredExamTransformer
"""
def setUp(self):
"""
Setup course structure and create user for split test transformer test.
"""
super(ProctoredExamTransformerTestCase, self).setUp()
# Set up proctored exam
# Build course.
self.course_hierarchy = self.get_course_hierarchy()
self.blocks = self.build_course(self.course_hierarchy)
self.course = self.blocks['course']
# Enroll user in course.
CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id, is_active=True)
self.transformer = ProctoredExamTransformer()
def setup_proctored_exam(self, block, attempt_status, user_id):
"""
Test helper to configure the given block as a proctored exam.
"""
exam_id = create_exam(
course_id=unicode(block.location.course_key),
content_id=unicode(block.location),
exam_name='foo',
time_limit_mins=10,
is_proctored=True,
is_practice_exam=block.is_practice_exam,
)
set_runtime_service(
'credit',
MockCreditService(enrollment_mode='verified')
)
create_exam_attempt(exam_id, user_id, taking_as_proctored=True)
update_attempt_status(exam_id, user_id, attempt_status)
ALL_BLOCKS = ('course', 'A', 'B', 'C', 'TimedExam', 'D', 'E', 'PracticeExam', 'F', 'G')
def get_course_hierarchy(self):
"""
Get a course hierarchy to test with.
"""
# course
# / | \
# / | \
# A Exam1 Exam2
# / \ / \ / \
# / \ / \ / \
# B C D E F G
#
return [
{
'org': 'ProctoredExamTransformer',
'course': 'PE101F',
'run': 'test_run',
'#type': 'course',
'#ref': 'course',
},
{
'#type': 'sequential',
'#ref': 'A',
'#children': [
{'#type': 'vertical', '#ref': 'B'},
{'#type': 'vertical', '#ref': 'C'},
],
},
{
'#type': 'sequential',
'#ref': 'TimedExam',
'is_time_limited': True,
'is_proctored_enabled': True,
'is_practice_exam': False,
'#children': [
{'#type': 'vertical', '#ref': 'D'},
{'#type': 'vertical', '#ref': 'E'},
],
},
{
'#type': 'sequential',
'#ref': 'PracticeExam',
'is_time_limited': True,
'is_proctored_enabled': True,
'is_practice_exam': True,
'#children': [
{'#type': 'vertical', '#ref': 'F'},
{'#type': 'vertical', '#ref': 'G'},
],
},
]
def test_exam_not_created(self):
block_structure = get_course_blocks(
self.user,
self.course.location,
transformers={self.transformer},
)
self.assertEqual(
set(block_structure.get_block_keys()),
set(self.get_block_key_set(self.blocks, *self.ALL_BLOCKS)),
)
@ddt.data(
(
'TimedExam',
ProctoredExamStudentAttemptStatus.declined,
ALL_BLOCKS,
),
(
'TimedExam',
ProctoredExamStudentAttemptStatus.submitted,
('course', 'A', 'B', 'C', 'PracticeExam', 'F', 'G'),
),
(
'TimedExam',
ProctoredExamStudentAttemptStatus.rejected,
('course', 'A', 'B', 'C', 'PracticeExam', 'F', 'G'),
),
(
'PracticeExam',
ProctoredExamStudentAttemptStatus.declined,
ALL_BLOCKS,
),
(
'PracticeExam',
ProctoredExamStudentAttemptStatus.rejected,
('course', 'A', 'B', 'C', 'TimedExam', 'D', 'E'),
),
)
@ddt.unpack
def test_exam_created(self, exam_ref, attempt_status, expected_blocks):
self.setup_proctored_exam(self.blocks[exam_ref], attempt_status, self.user.id)
block_structure = get_course_blocks(
self.user,
self.course.location,
transformers={self.transformer},
)
self.assertEqual(
set(block_structure.get_block_keys()),
set(self.get_block_key_set(self.blocks, *expected_blocks)),
)
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