test_enrollment.py 11.6 KB
Newer Older
Miles Steele committed
1 2 3 4 5
"""
Unit tests for instructor.enrollment methods.
"""

import json
6
from abc import ABCMeta
7
from django.contrib.auth.models import User
Miles Steele committed
8 9 10 11 12
from courseware.models import StudentModule
from django.test import TestCase
from student.tests.factories import UserFactory

from student.models import CourseEnrollment, CourseEnrollmentAllowed
13 14 15
from instructor.enrollment import (EmailEnrollmentState,
                                   enroll_email, unenroll_email,
                                   reset_student_attempts)
Miles Steele committed
16 17


18
class TestSettableEnrollmentState(TestCase):
19
    """ Test the basis class for enrollment tests. """
Miles Steele committed
20 21 22
    def setUp(self):
        self.course_id = 'robot:/a/fake/c::rse/id'

23 24
    def test_mes_create(self):
        """
25
        Test SettableEnrollmentState creation of user.
26
        """
27
        mes = SettableEnrollmentState(
28 29 30 31 32
            user=True,
            enrollment=True,
            allowed=False,
            auto_enroll=False
        )
Miles Steele committed
33 34 35
        # enrollment objects
        eobjs = mes.create_user(self.course_id)
        ees = EmailEnrollmentState(self.course_id, eobjs.email)
36
        self.assertEqual(mes, ees)
Miles Steele committed
37 38


39 40 41
class TestEnrollmentChangeBase(TestCase):
    """
    Test instructor enrollment administration against database effects.
Miles Steele committed
42

43 44 45 46
    Test methods in derived classes follow a strict format.
    `action` is a function which is run
    the test will pass if `action` mutates state from `before_ideal` to `after_ideal`
    """
Miles Steele committed
47

48
    __metaclass__ = ABCMeta
Miles Steele committed
49

50 51
    def setUp(self):
        self.course_id = 'robot:/a/fake/c::rse/id'
Miles Steele committed
52

53 54 55 56
    def _run_state_change_test(self, before_ideal, after_ideal, action):
        """
        Runs a state change test.

57
        `before_ideal` and `after_ideal` are SettableEnrollmentState's
58 59 60 61 62 63 64
        `action` is a function which will be run in the middle.
            `action` should transition the world from before_ideal to after_ideal
            `action` will be supplied the following arguments (None-able arguments)
                `email` is an email string
        """
        # initialize & check before
        print "checking initialization..."
Miles Steele committed
65 66
        eobjs = before_ideal.create_user(self.course_id)
        before = EmailEnrollmentState(self.course_id, eobjs.email)
67 68 69 70
        self.assertEqual(before, before_ideal)

        # do action
        print "running action..."
Miles Steele committed
71
        action(eobjs.email)
72 73 74

        # check after
        print "checking effects..."
Miles Steele committed
75
        after = EmailEnrollmentState(self.course_id, eobjs.email)
76 77 78 79 80 81
        self.assertEqual(after, after_ideal)


class TestInstructorEnrollDB(TestEnrollmentChangeBase):
    """ Test instructor.enrollment.enroll_email """
    def test_enroll(self):
82
        before_ideal = SettableEnrollmentState(
83 84 85 86 87 88
            user=True,
            enrollment=False,
            allowed=False,
            auto_enroll=False
        )

89
        after_ideal = SettableEnrollmentState(
90 91 92 93 94 95
            user=True,
            enrollment=True,
            allowed=False,
            auto_enroll=False
        )

Miles Steele committed
96
        action = lambda email: enroll_email(self.course_id, email)
97 98 99 100

        return self._run_state_change_test(before_ideal, after_ideal, action)

    def test_enroll_again(self):
101
        before_ideal = SettableEnrollmentState(
102 103 104 105 106 107
            user=True,
            enrollment=True,
            allowed=False,
            auto_enroll=False,
        )

108
        after_ideal = SettableEnrollmentState(
109 110 111 112 113 114
            user=True,
            enrollment=True,
            allowed=False,
            auto_enroll=False,
        )

Miles Steele committed
115
        action = lambda email: enroll_email(self.course_id, email)
116 117 118 119

        return self._run_state_change_test(before_ideal, after_ideal, action)

    def test_enroll_nouser(self):
120
        before_ideal = SettableEnrollmentState(
121 122 123 124 125 126
            user=False,
            enrollment=False,
            allowed=False,
            auto_enroll=False,
        )

127
        after_ideal = SettableEnrollmentState(
128 129 130 131 132 133
            user=False,
            enrollment=False,
            allowed=True,
            auto_enroll=False,
        )

Miles Steele committed
134
        action = lambda email: enroll_email(self.course_id, email)
135 136 137 138

        return self._run_state_change_test(before_ideal, after_ideal, action)

    def test_enroll_nouser_again(self):
139
        before_ideal = SettableEnrollmentState(
140 141 142 143 144 145
            user=False,
            enrollment=False,
            allowed=True,
            auto_enroll=False
        )

146
        after_ideal = SettableEnrollmentState(
147 148 149 150 151 152
            user=False,
            enrollment=False,
            allowed=True,
            auto_enroll=False,
        )

Miles Steele committed
153
        action = lambda email: enroll_email(self.course_id, email)
154 155 156 157

        return self._run_state_change_test(before_ideal, after_ideal, action)

    def test_enroll_nouser_autoenroll(self):
158
        before_ideal = SettableEnrollmentState(
159 160 161 162 163 164
            user=False,
            enrollment=False,
            allowed=False,
            auto_enroll=False,
        )

165
        after_ideal = SettableEnrollmentState(
166 167 168 169 170 171
            user=False,
            enrollment=False,
            allowed=True,
            auto_enroll=True,
        )

Miles Steele committed
172
        action = lambda email: enroll_email(self.course_id, email, auto_enroll=True)
173 174 175 176

        return self._run_state_change_test(before_ideal, after_ideal, action)

    def test_enroll_nouser_change_autoenroll(self):
177
        before_ideal = SettableEnrollmentState(
178 179 180 181 182 183
            user=False,
            enrollment=False,
            allowed=True,
            auto_enroll=True,
        )

184
        after_ideal = SettableEnrollmentState(
185 186 187 188 189 190
            user=False,
            enrollment=False,
            allowed=True,
            auto_enroll=False,
        )

Miles Steele committed
191
        action = lambda email: enroll_email(self.course_id, email, auto_enroll=False)
192 193 194 195 196 197 198

        return self._run_state_change_test(before_ideal, after_ideal, action)


class TestInstructorUnenrollDB(TestEnrollmentChangeBase):
    """ Test instructor.enrollment.unenroll_email """
    def test_unenroll(self):
199
        before_ideal = SettableEnrollmentState(
200 201 202 203 204 205
            user=True,
            enrollment=True,
            allowed=False,
            auto_enroll=False
        )

206
        after_ideal = SettableEnrollmentState(
207 208 209 210 211 212
            user=True,
            enrollment=False,
            allowed=False,
            auto_enroll=False
        )

Miles Steele committed
213
        action = lambda email: unenroll_email(self.course_id, email)
214 215

        return self._run_state_change_test(before_ideal, after_ideal, action)
Miles Steele committed
216 217

    def test_unenroll_notenrolled(self):
218
        before_ideal = SettableEnrollmentState(
219 220 221 222 223 224
            user=True,
            enrollment=False,
            allowed=False,
            auto_enroll=False
        )

225
        after_ideal = SettableEnrollmentState(
226 227 228 229 230 231
            user=True,
            enrollment=False,
            allowed=False,
            auto_enroll=False
        )

Miles Steele committed
232
        action = lambda email: unenroll_email(self.course_id, email)
233 234 235 236

        return self._run_state_change_test(before_ideal, after_ideal, action)

    def test_unenroll_disallow(self):
237
        before_ideal = SettableEnrollmentState(
238 239 240 241 242 243
            user=False,
            enrollment=False,
            allowed=True,
            auto_enroll=True
        )

244
        after_ideal = SettableEnrollmentState(
245 246 247 248 249 250
            user=False,
            enrollment=False,
            allowed=False,
            auto_enroll=False
        )

Miles Steele committed
251
        action = lambda email: unenroll_email(self.course_id, email)
252 253 254 255

        return self._run_state_change_test(before_ideal, after_ideal, action)

    def test_unenroll_norecord(self):
256
        before_ideal = SettableEnrollmentState(
257 258 259 260 261 262
            user=False,
            enrollment=False,
            allowed=False,
            auto_enroll=False
        )

263
        after_ideal = SettableEnrollmentState(
264 265 266 267 268 269
            user=False,
            enrollment=False,
            allowed=False,
            auto_enroll=False
        )

Miles Steele committed
270
        action = lambda email: unenroll_email(self.course_id, email)
271 272 273 274 275 276 277 278

        return self._run_state_change_test(before_ideal, after_ideal, action)


class TestInstructorEnrollmentStudentModule(TestCase):
    """ Test student module manipulations. """
    def setUp(self):
        self.course_id = 'robot:/a/fake/c::rse/id'
Miles Steele committed
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298

    def test_reset_student_attempts(self):
        user = UserFactory()
        msk = 'robot/module/state/key'
        original_state = json.dumps({'attempts': 32, 'otherstuff': 'alsorobots'})
        module = StudentModule.objects.create(student=user, course_id=self.course_id, module_state_key=msk, state=original_state)
        # lambda to reload the module state from the database
        module = lambda: StudentModule.objects.get(student=user, course_id=self.course_id, module_state_key=msk)
        self.assertEqual(json.loads(module().state)['attempts'], 32)
        reset_student_attempts(self.course_id, user, msk)
        self.assertEqual(json.loads(module().state)['attempts'], 0)

    def test_delete_student_attempts(self):
        user = UserFactory()
        msk = 'robot/module/state/key'
        original_state = json.dumps({'attempts': 32, 'otherstuff': 'alsorobots'})
        StudentModule.objects.create(student=user, course_id=self.course_id, module_state_key=msk, state=original_state)
        self.assertEqual(StudentModule.objects.filter(student=user, course_id=self.course_id, module_state_key=msk).count(), 1)
        reset_student_attempts(self.course_id, user, msk, delete_module=True)
        self.assertEqual(StudentModule.objects.filter(student=user, course_id=self.course_id, module_state_key=msk).count(), 0)
299 300


Miles Steele committed
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
class EnrollmentObjects(object):
    """
    Container for enrollment objects.

    `email` - student email
    `user` - student User object
    `cenr` - CourseEnrollment object
    `cea` - CourseEnrollmentAllowed object

    Any of the objects except email can be None.
    """
    def __init__(self, email, user, cenr, cea):
        self.email = email
        self.user = user
        self.cenr = cenr
        self.cea = cea


319
class SettableEnrollmentState(EmailEnrollmentState):
320 321
    """
    Settable enrollment state.
322 323 324 325
    Used for testing state changes.
    SettableEnrollmentState can be constructed and then
        a call to create_user will make objects which
        correspond to the state represented in the SettableEnrollmentState.
326
    """
Miles Steele committed
327
    def __init__(self, user=False, enrollment=False, allowed=False, auto_enroll=False):  # pylint: disable=W0231
328 329 330 331 332 333 334 335 336 337 338 339 340 341
        self.user = user
        self.enrollment = enrollment
        self.allowed = allowed
        self.auto_enroll = auto_enroll

    def __eq__(self, other):
        return self.to_dict() == other.to_dict()

    def __neq__(self, other):
        return not self == other

    def create_user(self, course_id=None):
        """
        Utility method to possibly create and possibly enroll a user.
342
        Creates a state matching the SettableEnrollmentState properties.
343 344 345 346 347 348 349 350 351 352 353 354 355
        Returns a tuple of (
            email,
            User, (optionally None)
            CourseEnrollment, (optionally None)
            CourseEnrollmentAllowed, (optionally None)
        )
        """
        # if self.user=False, then this will just be used to generate an email.
        email = "robot_no_user_exists_with_this_email@edx.org"
        if self.user:
            user = UserFactory()
            email = user.email
            if self.enrollment:
356
                cenr = CourseEnrollment.enroll(user, course_id)
Miles Steele committed
357
                return EnrollmentObjects(email, user, cenr, None)
358
            else:
Miles Steele committed
359
                return EnrollmentObjects(email, user, None, None)
360 361 362 363 364 365
        elif self.allowed:
            cea = CourseEnrollmentAllowed.objects.create(
                email=email,
                course_id=course_id,
                auto_enroll=self.auto_enroll,
            )
Miles Steele committed
366
            return EnrollmentObjects(email, None, None, cea)
367
        else:
Miles Steele committed
368
            return EnrollmentObjects(email, None, None, None)