test_proctoring.py 7.66 KB
Newer Older
1 2 3 4 5 6
"""
Tests for the edx_proctoring integration into Studio
"""

from mock import patch
import ddt
7 8
from datetime import datetime, timedelta
from pytz import UTC
9 10 11 12 13
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory

from contentstore.signals import listen_for_course_publish

Muhammad Shoaib committed
14 15 16 17
from edx_proctoring.api import (
    get_all_exams_for_course,
    get_review_policy_by_exam_id
)
18 19 20


@ddt.ddt
21
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_SPECIAL_EXAMS': True})
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
class TestProctoredExams(ModuleStoreTestCase):
    """
    Tests for the publishing of proctored exams
    """

    def setUp(self):
        """
        Initial data setup
        """
        super(TestProctoredExams, self).setUp()

        self.course = CourseFactory.create(
            org='edX',
            course='900',
            run='test_run',
            enable_proctored_exams=True
        )

    def _verify_exam_data(self, sequence, expected_active):
        """
        Helper method to compare the sequence with the stored exam,
        which should just be a single one
        """
        exams = get_all_exams_for_course(unicode(self.course.id))

        self.assertEqual(len(exams), 1)

        exam = exams[0]
Muhammad Shoaib committed
50 51 52 53 54 55

        if exam['is_proctored'] and not exam['is_practice_exam']:
            # get the review policy object
            exam_review_policy = get_review_policy_by_exam_id(exam['id'])
            self.assertEqual(exam_review_policy['review_policy'], sequence.exam_review_rules)

56 57 58 59
        self.assertEqual(exam['course_id'], unicode(self.course.id))
        self.assertEqual(exam['content_id'], unicode(sequence.location))
        self.assertEqual(exam['exam_name'], sequence.display_name)
        self.assertEqual(exam['time_limit_mins'], sequence.default_time_limit_minutes)
60
        self.assertEqual(exam['is_proctored'], sequence.is_proctored_exam)
Muhammad Shoaib committed
61
        self.assertEqual(exam['is_practice_exam'], sequence.is_practice_exam)
62 63 64
        self.assertEqual(exam['is_active'], expected_active)

    @ddt.data(
Muhammad Shoaib committed
65 66 67
        (True, 10, True, False, True, False),
        (True, 10, False, False, True, False),
        (True, 10, True, True, True, True),
68 69 70
    )
    @ddt.unpack
    def test_publishing_exam(self, is_time_limited, default_time_limit_minutes,
Muhammad Shoaib committed
71
                             is_proctored_exam, is_practice_exam, expected_active, republish):
72 73 74 75 76 77 78 79 80 81 82 83 84
        """
        Happy path testing to see that when a course is published which contains
        a proctored exam, it will also put an entry into the exam tables
        """

        chapter = ItemFactory.create(parent=self.course, category='chapter', display_name='Test Section')
        sequence = ItemFactory.create(
            parent=chapter,
            category='sequential',
            display_name='Test Proctored Exam',
            graded=True,
            is_time_limited=is_time_limited,
            default_time_limit_minutes=default_time_limit_minutes,
85
            is_proctored_exam=is_proctored_exam,
Muhammad Shoaib committed
86 87 88
            is_practice_exam=is_practice_exam,
            due=datetime.now(UTC) + timedelta(minutes=default_time_limit_minutes + 1),
            exam_review_rules="allow_use_of_paper"
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
        )

        listen_for_course_publish(self, self.course.id)

        self._verify_exam_data(sequence, expected_active)

        if republish:
            # update the sequence
            sequence.default_time_limit_minutes += sequence.default_time_limit_minutes
            self.store.update_item(sequence, self.user.id)

            # simulate a publish
            listen_for_course_publish(self, self.course.id)

            # reverify
            self._verify_exam_data(sequence, expected_active)

    def test_unpublishing_proctored_exam(self):
        """
        Make sure that if we publish and then unpublish a proctored exam,
        the exam record stays, but is marked as is_active=False
        """

        chapter = ItemFactory.create(parent=self.course, category='chapter', display_name='Test Section')
        sequence = ItemFactory.create(
            parent=chapter,
            category='sequential',
            display_name='Test Proctored Exam',
            graded=True,
            is_time_limited=True,
            default_time_limit_minutes=10,
120
            is_proctored_exam=True
121 122 123 124 125 126 127 128
        )

        listen_for_course_publish(self, self.course.id)

        exams = get_all_exams_for_course(unicode(self.course.id))
        self.assertEqual(len(exams), 1)

        sequence.is_time_limited = False
129
        sequence.is_proctored_exam = False
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149

        self.store.update_item(sequence, self.user.id)

        listen_for_course_publish(self, self.course.id)

        self._verify_exam_data(sequence, False)

    def test_dangling_exam(self):
        """
        Make sure we filter out all dangling items
        """

        chapter = ItemFactory.create(parent=self.course, category='chapter', display_name='Test Section')
        ItemFactory.create(
            parent=chapter,
            category='sequential',
            display_name='Test Proctored Exam',
            graded=True,
            is_time_limited=True,
            default_time_limit_minutes=10,
150
            is_proctored_exam=True
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
        )

        listen_for_course_publish(self, self.course.id)

        exams = get_all_exams_for_course(unicode(self.course.id))
        self.assertEqual(len(exams), 1)

        self.store.delete_item(chapter.location, self.user.id)

        # republish course
        listen_for_course_publish(self, self.course.id)

        # look through exam table, the dangling exam
        # should be disabled
        exams = get_all_exams_for_course(unicode(self.course.id))
        self.assertEqual(len(exams), 1)

        exam = exams[0]
        self.assertEqual(exam['is_active'], False)

171
    @patch.dict('django.conf.settings.FEATURES', {'ENABLE_SPECIAL_EXAMS': False})
172 173 174 175 176 177 178 179 180 181 182 183 184
    def test_feature_flag_off(self):
        """
        Make sure the feature flag is honored
        """

        chapter = ItemFactory.create(parent=self.course, category='chapter', display_name='Test Section')
        ItemFactory.create(
            parent=chapter,
            category='sequential',
            display_name='Test Proctored Exam',
            graded=True,
            is_time_limited=True,
            default_time_limit_minutes=10,
185
            is_proctored_exam=True
186 187 188 189 190 191 192
        )

        listen_for_course_publish(self, self.course.id)

        exams = get_all_exams_for_course(unicode(self.course.id))
        self.assertEqual(len(exams), 0)

193 194 195 196 197 198 199
    @ddt.data(
        (True, False, 1),
        (False, True, 1),
        (False, False, 0),
    )
    @ddt.unpack
    def test_advanced_settings(self, enable_timed_exams, enable_proctored_exams, expected_count):
200 201 202 203 204 205 206 207
        """
        Make sure the feature flag is honored
        """

        self.course = CourseFactory.create(
            org='edX',
            course='901',
            run='test_run2',
208 209
            enable_proctored_exams=enable_proctored_exams,
            enable_timed_exams=enable_timed_exams
210 211 212 213 214 215 216 217 218 219
        )

        chapter = ItemFactory.create(parent=self.course, category='chapter', display_name='Test Section')
        ItemFactory.create(
            parent=chapter,
            category='sequential',
            display_name='Test Proctored Exam',
            graded=True,
            is_time_limited=True,
            default_time_limit_minutes=10,
Muhammad Shoaib committed
220 221
            is_proctored_exam=True,
            exam_review_rules="allow_use_of_paper"
222 223 224 225 226 227 228
        )

        listen_for_course_publish(self, self.course.id)

        # there shouldn't be any exams because we haven't enabled that
        # advanced setting flag
        exams = get_all_exams_for_course(unicode(self.course.id))
229
        self.assertEqual(len(exams), expected_count)