test_access.py 16.3 KB
Newer Older
1
import datetime
2
import pytz
3 4

from django.test import TestCase
5
from django.core.urlresolvers import reverse
6 7
from mock import Mock, patch
from opaque_keys.edx.locations import SlashSeparatedCourseKey
8

9
import courseware.access as access
10
from courseware.masquerade import CourseMasquerade
11
from courseware.tests.factories import UserFactory, StaffFactory, InstructorFactory
12 13
from courseware.tests.helpers import LoginEnrollmentTestCase
from student.tests.factories import AnonymousUserFactory, CourseEnrollmentAllowedFactory, CourseEnrollmentFactory
14 15
from xmodule.course_module import (
    CATALOG_VISIBILITY_CATALOG_AND_ABOUT, CATALOG_VISIBILITY_ABOUT,
16 17
    CATALOG_VISIBILITY_NONE
)
18 19 20 21 22 23 24
from xmodule.modulestore.tests.factories import CourseFactory

from util.milestones_helpers import (
    set_prerequisite_courses,
    fulfill_course_milestone,
    seed_milestone_relationship_types,
)
muhammad-ammar committed
25

26
# pylint: disable=missing-docstring
27
# pylint: disable=protected-access
Julia Hansbrough committed
28

29

30
class AccessTestCase(LoginEnrollmentTestCase):
31 32 33
    """
    Tests for the various access controls on the student dashboard
    """
34
    def setUp(self):
35 36
        course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
        self.course = course_key.make_usage_key('course', course_key.run)
37 38 39
        self.anonymous_user = AnonymousUserFactory()
        self.student = UserFactory()
        self.global_staff = UserFactory(is_staff=True)
40 41
        self.course_staff = StaffFactory(course_key=self.course.course_key)
        self.course_instructor = InstructorFactory(course_key=self.course.course_key)
42

43 44 45
    def test_has_access_to_course(self):
        self.assertFalse(access._has_access_to_course(
            None, 'staff', self.course.course_key
46 47
        ))

48 49
        self.assertFalse(access._has_access_to_course(
            self.anonymous_user, 'staff', self.course.course_key
50
        ))
51 52
        self.assertFalse(access._has_access_to_course(
            self.anonymous_user, 'instructor', self.course.course_key
53 54
        ))

55 56
        self.assertTrue(access._has_access_to_course(
            self.global_staff, 'staff', self.course.course_key
57
        ))
58 59
        self.assertTrue(access._has_access_to_course(
            self.global_staff, 'instructor', self.course.course_key
60
        ))
61 62

        # A user has staff access if they are in the staff group
63 64
        self.assertTrue(access._has_access_to_course(
            self.course_staff, 'staff', self.course.course_key
65
        ))
66 67
        self.assertFalse(access._has_access_to_course(
            self.course_staff, 'instructor', self.course.course_key
68
        ))
69

70
        # A user has staff and instructor access if they are in the instructor group
71 72
        self.assertTrue(access._has_access_to_course(
            self.course_instructor, 'staff', self.course.course_key
73
        ))
74 75
        self.assertTrue(access._has_access_to_course(
            self.course_instructor, 'instructor', self.course.course_key
76
        ))
77

78
        # A user does not have staff or instructor access if they are
79
        # not in either the staff or the the instructor group
80 81
        self.assertFalse(access._has_access_to_course(
            self.student, 'staff', self.course.course_key
82
        ))
83 84
        self.assertFalse(access._has_access_to_course(
            self.student, 'instructor', self.course.course_key
85
        ))
86 87

    def test__has_access_string(self):
88 89
        user = Mock(is_staff=True)
        self.assertFalse(access._has_access_string(user, 'staff', 'not_global', self.course.course_key))
90

91 92
        user._has_global_staff_access.return_value = True
        self.assertTrue(access._has_access_string(user, 'staff', 'global', self.course.course_key))
93

94
        self.assertRaises(ValueError, access._has_access_string, user, 'not_staff', 'global', self.course.course_key)
95

96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
    def test__has_access_error_desc(self):
        descriptor = Mock()

        self.assertFalse(access._has_access_error_desc(self.student, 'load', descriptor, self.course.course_key))
        self.assertTrue(access._has_access_error_desc(self.course_staff, 'load', descriptor, self.course.course_key))
        self.assertTrue(access._has_access_error_desc(self.course_instructor, 'load', descriptor, self.course.course_key))

        self.assertFalse(access._has_access_error_desc(self.student, 'staff', descriptor, self.course.course_key))
        self.assertTrue(access._has_access_error_desc(self.course_staff, 'staff', descriptor, self.course.course_key))
        self.assertTrue(access._has_access_error_desc(self.course_instructor, 'staff', descriptor, self.course.course_key))

        self.assertFalse(access._has_access_error_desc(self.student, 'instructor', descriptor, self.course.course_key))
        self.assertFalse(access._has_access_error_desc(self.course_staff, 'instructor', descriptor, self.course.course_key))
        self.assertTrue(access._has_access_error_desc(self.course_instructor, 'instructor', descriptor, self.course.course_key))

        with self.assertRaises(ValueError):
            access._has_access_error_desc(self.course_instructor, 'not_load_or_staff', descriptor, self.course.course_key)

114
    def test__has_access_descriptor(self):
115
        # TODO: override DISABLE_START_DATES and test the start date branch of the method
116
        user = Mock()
117
        descriptor = Mock(user_partitions=[])
118 119

        # Always returns true because DISABLE_START_DATES is set in test.py
120 121
        self.assertTrue(access._has_access_descriptor(user, 'load', descriptor))
        self.assertTrue(access._has_access_descriptor(user, 'instructor', descriptor))
122
        with self.assertRaises(ValueError):
123
            access._has_access_descriptor(user, 'not_load_or_staff', descriptor)
124

125
    @patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
126 127 128 129
    def test__has_access_descriptor_staff_lock(self):
        """
        Tests that "visible_to_staff_only" overrides start date.
        """
130
        mock_unit = Mock(user_partitions=[])
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
        mock_unit._class_tags = {}  # Needed for detached check in _has_access_descriptor

        def verify_access(student_should_have_access):
            """ Verify the expected result from _has_access_descriptor """
            self.assertEqual(student_should_have_access, access._has_access_descriptor(
                self.anonymous_user, 'load', mock_unit, course_key=self.course.course_key)
            )
            # staff always has access
            self.assertTrue(access._has_access_descriptor(
                self.course_staff, 'load', mock_unit, course_key=self.course.course_key)
            )

        # No start date, staff lock on
        mock_unit.visible_to_staff_only = True
        verify_access(False)

        # No start date, staff lock off.
        mock_unit.visible_to_staff_only = False
        verify_access(True)

        # Start date in the past, staff lock on.
        mock_unit.start = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1)
        mock_unit.visible_to_staff_only = True
        verify_access(False)

        # Start date in the past, staff lock off.
        mock_unit.visible_to_staff_only = False
        verify_access(True)

        # Start date in the future, staff lock on.
        mock_unit.start = datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1)  # release date in the future
        mock_unit.visible_to_staff_only = True
        verify_access(False)

        # Start date in the future, staff lock off.
        mock_unit.visible_to_staff_only = False
        verify_access(False)

169
    def test__has_access_course_desc_can_enroll(self):
170 171
        yesterday = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1)
        tomorrow = datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1)
172

173
        # Non-staff can enroll if authenticated and specifically allowed for that course
174
        # even outside the open enrollment period
175
        user = UserFactory.create()
176 177 178 179 180 181
        course = Mock(
            enrollment_start=tomorrow, enrollment_end=tomorrow,
            id=SlashSeparatedCourseKey('edX', 'test', '2012_Fall'), enrollment_domain=''
        )
        CourseEnrollmentAllowedFactory(email=user.email, course_id=course.id)
        self.assertTrue(access._has_access_course_desc(user, 'enroll', course))
182 183

        # Staff can always enroll even outside the open enrollment period
184 185
        user = StaffFactory.create(course_key=course.id)
        self.assertTrue(access._has_access_course_desc(user, 'enroll', course))
186

187 188
        # Non-staff cannot enroll if it is between the start and end dates and invitation only
        # and not specifically allowed
189
        course = Mock(
190 191 192 193 194 195 196 197 198 199 200 201
            enrollment_start=yesterday, enrollment_end=tomorrow,
            id=SlashSeparatedCourseKey('edX', 'test', '2012_Fall'), enrollment_domain='',
            invitation_only=True
        )
        user = UserFactory.create()
        self.assertFalse(access._has_access_course_desc(user, 'enroll', course))

        # Non-staff can enroll if it is between the start and end dates and not invitation only
        course = Mock(
            enrollment_start=yesterday, enrollment_end=tomorrow,
            id=SlashSeparatedCourseKey('edX', 'test', '2012_Fall'), enrollment_domain='',
            invitation_only=False
202 203
        )
        self.assertTrue(access._has_access_course_desc(user, 'enroll', course))
204 205

        # Non-staff cannot enroll outside the open enrollment period if not specifically allowed
206 207 208 209 210 211
        course = Mock(
            enrollment_start=tomorrow, enrollment_end=tomorrow,
            id=SlashSeparatedCourseKey('edX', 'test', '2012_Fall'), enrollment_domain='',
            invitation_only=False
        )
        self.assertFalse(access._has_access_course_desc(user, 'enroll', course))
212 213 214

    def test__user_passed_as_none(self):
        """Ensure has_access handles a user being passed as null"""
215 216
        access.has_access(None, 'staff', 'global', None)

217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
    def test__catalog_visibility(self):
        """
        Tests the catalog visibility tri-states
        """
        user = UserFactory.create()
        course_id = SlashSeparatedCourseKey('edX', 'test', '2012_Fall')
        staff = StaffFactory.create(course_key=course_id)

        course = Mock(
            id=course_id,
            catalog_visibility=CATALOG_VISIBILITY_CATALOG_AND_ABOUT
        )
        self.assertTrue(access._has_access_course_desc(user, 'see_in_catalog', course))
        self.assertTrue(access._has_access_course_desc(user, 'see_about_page', course))
        self.assertTrue(access._has_access_course_desc(staff, 'see_in_catalog', course))
        self.assertTrue(access._has_access_course_desc(staff, 'see_about_page', course))

        # Now set visibility to just about page
        course = Mock(
            id=SlashSeparatedCourseKey('edX', 'test', '2012_Fall'),
            catalog_visibility=CATALOG_VISIBILITY_ABOUT
        )
        self.assertFalse(access._has_access_course_desc(user, 'see_in_catalog', course))
        self.assertTrue(access._has_access_course_desc(user, 'see_about_page', course))
        self.assertTrue(access._has_access_course_desc(staff, 'see_in_catalog', course))
        self.assertTrue(access._has_access_course_desc(staff, 'see_about_page', course))

        # Now set visibility to none, which means neither in catalog nor about pages
        course = Mock(
            id=SlashSeparatedCourseKey('edX', 'test', '2012_Fall'),
            catalog_visibility=CATALOG_VISIBILITY_NONE
        )
        self.assertFalse(access._has_access_course_desc(user, 'see_in_catalog', course))
        self.assertFalse(access._has_access_course_desc(user, 'see_about_page', course))
        self.assertTrue(access._has_access_course_desc(staff, 'see_in_catalog', course))
        self.assertTrue(access._has_access_course_desc(staff, 'see_about_page', course))

254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
    @patch.dict("django.conf.settings.FEATURES", {'ENABLE_PREREQUISITE_COURSES': True, 'MILESTONES_APP': True})
    def test_access_on_course_with_pre_requisites(self):
        """
        Test course access when a course has pre-requisite course yet to be completed
        """
        seed_milestone_relationship_types()
        user = UserFactory.create()

        pre_requisite_course = CourseFactory.create(
            org='test_org', number='788', run='test_run'
        )

        pre_requisite_courses = [unicode(pre_requisite_course.id)]
        course = CourseFactory.create(
            org='test_org', number='786', run='test_run', pre_requisite_courses=pre_requisite_courses
        )
        set_prerequisite_courses(course.id, pre_requisite_courses)

        #user should not be able to load course even if enrolled
        CourseEnrollmentFactory(user=user, course_id=course.id)
        self.assertFalse(access._has_access_course_desc(user, 'view_courseware_with_prerequisites', course))

        # Staff can always access course
        staff = StaffFactory.create(course_key=course.id)
        self.assertTrue(access._has_access_course_desc(staff, 'view_courseware_with_prerequisites', course))

        # User should be able access after completing required course
        fulfill_course_milestone(pre_requisite_course.id, user)
        self.assertTrue(access._has_access_course_desc(user, 'view_courseware_with_prerequisites', course))

    @patch.dict("django.conf.settings.FEATURES", {'ENABLE_PREREQUISITE_COURSES': True, 'MILESTONES_APP': True})
    def test_courseware_page_unfulfilled_prereqs(self):
        """
        Test courseware access when a course has pre-requisite course yet to be completed
        """
        seed_milestone_relationship_types()
        pre_requisite_course = CourseFactory.create(
            org='edX',
            course='900',
            run='test_run',
        )

        pre_requisite_courses = [unicode(pre_requisite_course.id)]
        course = CourseFactory.create(
            org='edX',
            course='1000',
            run='test_run',
            pre_requisite_courses=pre_requisite_courses,
        )
        set_prerequisite_courses(course.id, pre_requisite_courses)

        test_password = 't3stp4ss.!'
        user = UserFactory.create()
        user.set_password(test_password)
        user.save()
        self.login(user.email, test_password)
        CourseEnrollmentFactory(user=user, course_id=course.id)

        url = reverse('courseware', args=[unicode(course.id)])
        response = self.client.get(url)
        self.assertRedirects(
            response,
            reverse(
                'dashboard'
            )
        )
        self.assertEqual(response.status_code, 302)

        fulfill_course_milestone(pre_requisite_course.id, user)
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)

326 327 328 329 330 331

class UserRoleTestCase(TestCase):
    """
    Tests for user roles.
    """
    def setUp(self):
332
        self.course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
333 334 335
        self.anonymous_user = AnonymousUserFactory()
        self.student = UserFactory()
        self.global_staff = UserFactory(is_staff=True)
336 337
        self.course_staff = StaffFactory(course_key=self.course_key)
        self.course_instructor = InstructorFactory(course_key=self.course_key)
338

339 340 341 342 343 344 345 346
    def _install_masquerade(self, user, role='student'):
        """
        Installs a masquerade for the specified user.
        """
        user.masquerade_settings = {
            self.course_key: CourseMasquerade(self.course_key, role=role)
        }

347 348 349 350
    def test_user_role_staff(self):
        """Ensure that user role is student for staff masqueraded as student."""
        self.assertEqual(
            'staff',
351
            access.get_user_role(self.course_staff, self.course_key)
352 353
        )
        # Masquerade staff
354
        self._install_masquerade(self.course_staff)
355 356
        self.assertEqual(
            'student',
357
            access.get_user_role(self.course_staff, self.course_key)
358 359 360 361 362 363
        )

    def test_user_role_instructor(self):
        """Ensure that user role is student for instructor masqueraded as student."""
        self.assertEqual(
            'instructor',
364
            access.get_user_role(self.course_instructor, self.course_key)
365 366
        )
        # Masquerade instructor
367
        self._install_masquerade(self.course_instructor)
368 369
        self.assertEqual(
            'student',
370
            access.get_user_role(self.course_instructor, self.course_key)
371 372 373 374 375 376
        )

    def test_user_role_anonymous(self):
        """Ensure that user role is student for anonymous user."""
        self.assertEqual(
            'student',
377
            access.get_user_role(self.anonymous_user, self.course_key)
378
        )