This abstract class contains most of the fields (aside from the id and foreign key to StudentModule that the subclasses need to manage). It also provides a get_history method that abstracts searching across multiple backends. Move router code to openedx/core We need to use it from cms and lms. Ensure aws_migrate can be used for migrating both the lms and cms. Handle queries directed to student_module_history vs default and the extra queries generated by Django 1.8 (SAVEPOINTS, etc). Additionally, flag testing classes as multi_db so that Django will flush the non-default database between unit tests. Further decouple the foreignkey relation between csm and csmhe When calling StudentModule().delete() Django will try to delete CSMHE objects, but naively does so in the database, not by consulting the database router. Instead, we disable django cascading deletes and listen for post_delete signals and clean up CSMHE by hand. Add feature flags for CSMHE One to turn it on/off so we can control the deploy. The other will control whether or not we read from two database tables or one when searching. Update tests to explicitly use this get_history method rather than looking directly into StudentModuleHistory or StudentModuleHistoryExtended. Inform lettuce to avoid the coursewarehistoryextended app Otherwise it fails when it can't find features/ in that app. Add Pg support, this is not tested automatically.
693 lines
28 KiB
Python
693 lines
28 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
Test the access control framework
|
|
"""
|
|
import datetime
|
|
import ddt
|
|
import itertools
|
|
import pytz
|
|
|
|
from django.contrib.auth.models import User
|
|
from ccx_keys.locator import CCXLocator
|
|
from django.http import Http404
|
|
from django.test.client import RequestFactory
|
|
from django.core.urlresolvers import reverse
|
|
from django.test import TestCase
|
|
from mock import Mock, patch
|
|
from nose.plugins.attrib import attr
|
|
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
|
|
|
import courseware.access as access
|
|
import courseware.access_response as access_response
|
|
from courseware.masquerade import CourseMasquerade
|
|
from courseware.tests.factories import (
|
|
BetaTesterFactory,
|
|
GlobalStaffFactory,
|
|
InstructorFactory,
|
|
StaffFactory,
|
|
UserFactory,
|
|
)
|
|
import courseware.views as views
|
|
from courseware.tests.helpers import LoginEnrollmentTestCase
|
|
from edxmako.tests import mako_middleware_process_request
|
|
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
|
from student.models import CourseEnrollment
|
|
from student.roles import CourseCcxCoachRole
|
|
from student.tests.factories import (
|
|
AdminFactory,
|
|
AnonymousUserFactory,
|
|
CourseEnrollmentAllowedFactory,
|
|
CourseEnrollmentFactory,
|
|
)
|
|
from xmodule.course_module import (
|
|
CATALOG_VISIBILITY_CATALOG_AND_ABOUT,
|
|
CATALOG_VISIBILITY_ABOUT,
|
|
CATALOG_VISIBILITY_NONE,
|
|
)
|
|
from xmodule.modulestore.tests.factories import CourseFactory
|
|
from xmodule.modulestore.tests.django_utils import (
|
|
ModuleStoreTestCase,
|
|
SharedModuleStoreTestCase,
|
|
TEST_DATA_SPLIT_MODULESTORE
|
|
)
|
|
|
|
from util.milestones_helpers import (
|
|
set_prerequisite_courses,
|
|
fulfill_course_milestone,
|
|
)
|
|
from milestones.tests.utils import MilestonesTestCaseMixin
|
|
|
|
from lms.djangoapps.ccx.models import CustomCourseForEdX
|
|
|
|
# pylint: disable=protected-access
|
|
|
|
|
|
class CoachAccessTestCaseCCX(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
|
|
"""
|
|
Test if user is coach on ccx.
|
|
"""
|
|
MODULESTORE = TEST_DATA_SPLIT_MODULESTORE
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
"""
|
|
Set up course for tests
|
|
"""
|
|
super(CoachAccessTestCaseCCX, cls).setUpClass()
|
|
cls.course = CourseFactory.create()
|
|
|
|
def setUp(self):
|
|
"""
|
|
Set up tests
|
|
"""
|
|
super(CoachAccessTestCaseCCX, self).setUp()
|
|
|
|
# Create ccx coach account
|
|
self.coach = AdminFactory.create(password="test")
|
|
self.client.login(username=self.coach.username, password="test")
|
|
|
|
# assign role to coach
|
|
role = CourseCcxCoachRole(self.course.id)
|
|
role.add_users(self.coach)
|
|
self.request_factory = RequestFactory()
|
|
|
|
def make_ccx(self):
|
|
"""
|
|
create ccx
|
|
"""
|
|
ccx = CustomCourseForEdX(
|
|
course_id=self.course.id,
|
|
coach=self.coach,
|
|
display_name="Test CCX"
|
|
)
|
|
ccx.save()
|
|
|
|
ccx_locator = CCXLocator.from_course_locator(self.course.id, unicode(ccx.id))
|
|
role = CourseCcxCoachRole(ccx_locator)
|
|
role.add_users(self.coach)
|
|
CourseEnrollment.enroll(self.coach, ccx_locator)
|
|
return ccx_locator
|
|
|
|
def test_has_ccx_coach_role(self):
|
|
"""
|
|
Assert that user has coach access on ccx.
|
|
"""
|
|
ccx_locator = self.make_ccx()
|
|
|
|
# user have access as coach on ccx
|
|
self.assertTrue(access.has_ccx_coach_role(self.coach, ccx_locator))
|
|
|
|
# user dont have access as coach on ccx
|
|
self.setup_user()
|
|
self.assertFalse(access.has_ccx_coach_role(self.user, ccx_locator))
|
|
|
|
def test_access_student_progress_ccx(self):
|
|
"""
|
|
Assert that only a coach can see progress of student.
|
|
"""
|
|
ccx_locator = self.make_ccx()
|
|
student = UserFactory()
|
|
|
|
# Enroll user
|
|
CourseEnrollment.enroll(student, ccx_locator)
|
|
|
|
# Test for access of a coach
|
|
request = self.request_factory.get(reverse('about_course', args=[unicode(ccx_locator)]))
|
|
request.user = self.coach
|
|
mako_middleware_process_request(request)
|
|
resp = views.progress(request, course_id=unicode(ccx_locator), student_id=student.id)
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
# Assert access of a student
|
|
request = self.request_factory.get(reverse('about_course', args=[unicode(ccx_locator)]))
|
|
request.user = student
|
|
mako_middleware_process_request(request)
|
|
|
|
with self.assertRaises(Http404) as context:
|
|
views.progress(request, course_id=unicode(ccx_locator), student_id=self.coach.id)
|
|
|
|
self.assertIsNotNone(context.exception)
|
|
|
|
|
|
@attr('shard_1')
|
|
@ddt.ddt
|
|
class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTestCaseMixin):
|
|
"""
|
|
Tests for the various access controls on the student dashboard
|
|
"""
|
|
TOMORROW = datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1)
|
|
YESTERDAY = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1)
|
|
|
|
def setUp(self):
|
|
super(AccessTestCase, self).setUp()
|
|
course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
|
|
self.course = course_key.make_usage_key('course', course_key.run)
|
|
self.anonymous_user = AnonymousUserFactory()
|
|
self.beta_user = BetaTesterFactory(course_key=self.course.course_key)
|
|
self.student = UserFactory()
|
|
self.global_staff = UserFactory(is_staff=True)
|
|
self.course_staff = StaffFactory(course_key=self.course.course_key)
|
|
self.course_instructor = InstructorFactory(course_key=self.course.course_key)
|
|
self.staff = GlobalStaffFactory()
|
|
|
|
def verify_access(self, mock_unit, student_should_have_access, expected_error_type=None):
|
|
""" Verify the expected result from _has_access_descriptor """
|
|
response = access._has_access_descriptor(self.anonymous_user, 'load',
|
|
mock_unit, course_key=self.course.course_key)
|
|
self.assertEqual(student_should_have_access, bool(response))
|
|
|
|
if expected_error_type is not None:
|
|
self.assertIsInstance(response, expected_error_type)
|
|
self.assertIsNotNone(response.to_json()['error_code'])
|
|
|
|
self.assertTrue(
|
|
access._has_access_descriptor(self.course_staff, 'load', mock_unit, course_key=self.course.course_key)
|
|
)
|
|
|
|
def test_has_access_to_course(self):
|
|
self.assertFalse(access._has_access_to_course(
|
|
None, 'staff', self.course.course_key
|
|
))
|
|
|
|
self.assertFalse(access._has_access_to_course(
|
|
self.anonymous_user, 'staff', self.course.course_key
|
|
))
|
|
self.assertFalse(access._has_access_to_course(
|
|
self.anonymous_user, 'instructor', self.course.course_key
|
|
))
|
|
|
|
self.assertTrue(access._has_access_to_course(
|
|
self.global_staff, 'staff', self.course.course_key
|
|
))
|
|
self.assertTrue(access._has_access_to_course(
|
|
self.global_staff, 'instructor', self.course.course_key
|
|
))
|
|
|
|
# A user has staff access if they are in the staff group
|
|
self.assertTrue(access._has_access_to_course(
|
|
self.course_staff, 'staff', self.course.course_key
|
|
))
|
|
self.assertFalse(access._has_access_to_course(
|
|
self.course_staff, 'instructor', self.course.course_key
|
|
))
|
|
|
|
# A user has staff and instructor access if they are in the instructor group
|
|
self.assertTrue(access._has_access_to_course(
|
|
self.course_instructor, 'staff', self.course.course_key
|
|
))
|
|
self.assertTrue(access._has_access_to_course(
|
|
self.course_instructor, 'instructor', self.course.course_key
|
|
))
|
|
|
|
# A user does not have staff or instructor access if they are
|
|
# not in either the staff or the the instructor group
|
|
self.assertFalse(access._has_access_to_course(
|
|
self.student, 'staff', self.course.course_key
|
|
))
|
|
self.assertFalse(access._has_access_to_course(
|
|
self.student, 'instructor', self.course.course_key
|
|
))
|
|
|
|
self.assertFalse(access._has_access_to_course(
|
|
self.student, 'not_staff_or_instructor', self.course.course_key
|
|
))
|
|
|
|
def test__has_access_string(self):
|
|
user = Mock(is_staff=True)
|
|
self.assertFalse(access._has_access_string(user, 'staff', 'not_global'))
|
|
|
|
user._has_global_staff_access.return_value = True
|
|
self.assertTrue(access._has_access_string(user, 'staff', 'global'))
|
|
|
|
self.assertRaises(ValueError, access._has_access_string, user, 'not_staff', 'global')
|
|
|
|
@ddt.data(
|
|
('load', False, True, True),
|
|
('staff', False, True, True),
|
|
('instructor', False, False, True)
|
|
)
|
|
@ddt.unpack
|
|
def test__has_access_error_desc(self, action, expected_student, expected_staff, expected_instructor):
|
|
descriptor = Mock()
|
|
|
|
for (user, expected_response) in (
|
|
(self.student, expected_student),
|
|
(self.course_staff, expected_staff),
|
|
(self.course_instructor, expected_instructor)
|
|
):
|
|
self.assertEquals(
|
|
bool(access._has_access_error_desc(user, action, descriptor, self.course.course_key)),
|
|
expected_response
|
|
)
|
|
|
|
with self.assertRaises(ValueError):
|
|
access._has_access_error_desc(self.course_instructor, 'not_load_or_staff', descriptor, self.course.course_key)
|
|
|
|
def test__has_access_descriptor(self):
|
|
# TODO: override DISABLE_START_DATES and test the start date branch of the method
|
|
user = Mock()
|
|
descriptor = Mock(user_partitions=[])
|
|
descriptor._class_tags = {}
|
|
|
|
# Always returns true because DISABLE_START_DATES is set in test.py
|
|
self.assertTrue(access._has_access_descriptor(user, 'load', descriptor))
|
|
self.assertTrue(access._has_access_descriptor(user, 'instructor', descriptor))
|
|
with self.assertRaises(ValueError):
|
|
access._has_access_descriptor(user, 'not_load_or_staff', descriptor)
|
|
|
|
@ddt.data(
|
|
(True, None, access_response.VisibilityError),
|
|
(False, None),
|
|
(True, YESTERDAY, access_response.VisibilityError),
|
|
(False, YESTERDAY),
|
|
(True, TOMORROW, access_response.VisibilityError),
|
|
(False, TOMORROW, access_response.StartDateError)
|
|
)
|
|
@ddt.unpack
|
|
@patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
|
|
def test__has_access_descriptor_staff_lock(self, visible_to_staff_only, start, expected_error_type=None):
|
|
"""
|
|
Tests that "visible_to_staff_only" overrides start date.
|
|
"""
|
|
expected_access = expected_error_type is None
|
|
mock_unit = Mock(user_partitions=[])
|
|
mock_unit._class_tags = {} # Needed for detached check in _has_access_descriptor
|
|
mock_unit.visible_to_staff_only = visible_to_staff_only
|
|
mock_unit.start = start
|
|
self.verify_access(mock_unit, expected_access, expected_error_type)
|
|
|
|
def test__has_access_descriptor_beta_user(self):
|
|
mock_unit = Mock(user_partitions=[])
|
|
mock_unit._class_tags = {}
|
|
mock_unit.days_early_for_beta = 2
|
|
mock_unit.start = self.TOMORROW
|
|
mock_unit.visible_to_staff_only = False
|
|
|
|
self.assertTrue(bool(access._has_access_descriptor(
|
|
self.beta_user, 'load', mock_unit, course_key=self.course.course_key)))
|
|
|
|
@ddt.data(None, YESTERDAY, TOMORROW)
|
|
@patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
|
|
@patch('courseware.access_utils.get_current_request_hostname', Mock(return_value='preview.localhost'))
|
|
def test__has_access_descriptor_in_preview_mode(self, start):
|
|
"""
|
|
Tests that descriptor has access in preview mode.
|
|
"""
|
|
mock_unit = Mock(user_partitions=[])
|
|
mock_unit._class_tags = {} # Needed for detached check in _has_access_descriptor
|
|
mock_unit.visible_to_staff_only = False
|
|
mock_unit.start = start
|
|
self.verify_access(mock_unit, True)
|
|
|
|
@ddt.data(
|
|
(TOMORROW, access_response.StartDateError),
|
|
(None, None),
|
|
(YESTERDAY, None)
|
|
) # ddt throws an error if I don't put the None argument there
|
|
@ddt.unpack
|
|
@patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
|
|
@patch('courseware.access_utils.get_current_request_hostname', Mock(return_value='localhost'))
|
|
def test__has_access_descriptor_when_not_in_preview_mode(self, start, expected_error_type):
|
|
"""
|
|
Tests that descriptor has no access when start date in future & without preview.
|
|
"""
|
|
expected_access = expected_error_type is None
|
|
mock_unit = Mock(user_partitions=[])
|
|
mock_unit._class_tags = {} # Needed for detached check in _has_access_descriptor
|
|
mock_unit.visible_to_staff_only = False
|
|
mock_unit.start = start
|
|
self.verify_access(mock_unit, expected_access, expected_error_type)
|
|
|
|
def test__has_access_course_can_enroll(self):
|
|
yesterday = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1)
|
|
tomorrow = datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1)
|
|
|
|
# Non-staff can enroll if authenticated and specifically allowed for that course
|
|
# even outside the open enrollment period
|
|
user = UserFactory.create()
|
|
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(user, 'enroll', course))
|
|
|
|
# Staff can always enroll even outside the open enrollment period
|
|
user = StaffFactory.create(course_key=course.id)
|
|
self.assertTrue(access._has_access_course(user, 'enroll', course))
|
|
|
|
# Non-staff cannot enroll if it is between the start and end dates and invitation only
|
|
# and not specifically allowed
|
|
course = Mock(
|
|
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(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
|
|
)
|
|
self.assertTrue(access._has_access_course(user, 'enroll', course))
|
|
|
|
# Non-staff cannot enroll outside the open enrollment period if not specifically allowed
|
|
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(user, 'enroll', course))
|
|
|
|
def test__user_passed_as_none(self):
|
|
"""Ensure has_access handles a user being passed as null"""
|
|
access.has_access(None, 'staff', 'global', None)
|
|
|
|
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(user, 'see_in_catalog', course))
|
|
self.assertTrue(access._has_access_course(user, 'see_about_page', course))
|
|
self.assertTrue(access._has_access_course(staff, 'see_in_catalog', course))
|
|
self.assertTrue(access._has_access_course(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(user, 'see_in_catalog', course))
|
|
self.assertTrue(access._has_access_course(user, 'see_about_page', course))
|
|
self.assertTrue(access._has_access_course(staff, 'see_in_catalog', course))
|
|
self.assertTrue(access._has_access_course(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(user, 'see_in_catalog', course))
|
|
self.assertFalse(access._has_access_course(user, 'see_about_page', course))
|
|
self.assertTrue(access._has_access_course(staff, 'see_in_catalog', course))
|
|
self.assertTrue(access._has_access_course(staff, 'see_about_page', course))
|
|
|
|
@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
|
|
"""
|
|
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)
|
|
response = access._has_access_course(user, 'view_courseware_with_prerequisites', course)
|
|
self.assertFalse(response)
|
|
self.assertIsInstance(response, access_response.MilestoneError)
|
|
# Staff can always access course
|
|
staff = StaffFactory.create(course_key=course.id)
|
|
self.assertTrue(access._has_access_course(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(user, 'view_courseware_with_prerequisites', course))
|
|
|
|
@ddt.data(
|
|
(True, True, True),
|
|
(False, False, True)
|
|
)
|
|
@ddt.unpack
|
|
def test__access_on_mobile(self, mobile_available, student_expected, staff_expected):
|
|
"""
|
|
Test course access on mobile for staff and students.
|
|
"""
|
|
descriptor = Mock(user_partitions=[])
|
|
descriptor._class_tags = {}
|
|
descriptor.visible_to_staff_only = False
|
|
descriptor.mobile_available = mobile_available
|
|
|
|
self.assertEqual(
|
|
bool(access._has_access_course(self.student, 'load_mobile', descriptor)),
|
|
student_expected
|
|
)
|
|
self.assertEqual(bool(access._has_access_course(self.staff, 'load_mobile', descriptor)), staff_expected)
|
|
|
|
@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
|
|
"""
|
|
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)
|
|
|
|
|
|
@attr('shard_1')
|
|
class UserRoleTestCase(TestCase):
|
|
"""
|
|
Tests for user roles.
|
|
"""
|
|
|
|
def setUp(self):
|
|
super(UserRoleTestCase, self).setUp()
|
|
self.course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
|
|
self.anonymous_user = AnonymousUserFactory()
|
|
self.student = UserFactory()
|
|
self.global_staff = UserFactory(is_staff=True)
|
|
self.course_staff = StaffFactory(course_key=self.course_key)
|
|
self.course_instructor = InstructorFactory(course_key=self.course_key)
|
|
|
|
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)
|
|
}
|
|
|
|
def test_user_role_staff(self):
|
|
"""Ensure that user role is student for staff masqueraded as student."""
|
|
self.assertEqual(
|
|
'staff',
|
|
access.get_user_role(self.course_staff, self.course_key)
|
|
)
|
|
# Masquerade staff
|
|
self._install_masquerade(self.course_staff)
|
|
self.assertEqual(
|
|
'student',
|
|
access.get_user_role(self.course_staff, self.course_key)
|
|
)
|
|
|
|
def test_user_role_instructor(self):
|
|
"""Ensure that user role is student for instructor masqueraded as student."""
|
|
self.assertEqual(
|
|
'instructor',
|
|
access.get_user_role(self.course_instructor, self.course_key)
|
|
)
|
|
# Masquerade instructor
|
|
self._install_masquerade(self.course_instructor)
|
|
self.assertEqual(
|
|
'student',
|
|
access.get_user_role(self.course_instructor, self.course_key)
|
|
)
|
|
|
|
def test_user_role_anonymous(self):
|
|
"""Ensure that user role is student for anonymous user."""
|
|
self.assertEqual(
|
|
'student',
|
|
access.get_user_role(self.anonymous_user, self.course_key)
|
|
)
|
|
|
|
|
|
@ddt.ddt
|
|
class CourseOverviewAccessTestCase(ModuleStoreTestCase):
|
|
"""
|
|
Tests confirming that has_access works equally on CourseDescriptors and
|
|
CourseOverviews.
|
|
"""
|
|
|
|
def setUp(self):
|
|
super(CourseOverviewAccessTestCase, self).setUp()
|
|
|
|
today = datetime.datetime.now(pytz.UTC)
|
|
last_week = today - datetime.timedelta(days=7)
|
|
next_week = today + datetime.timedelta(days=7)
|
|
|
|
self.course_default = CourseFactory.create()
|
|
self.course_started = CourseFactory.create(start=last_week)
|
|
self.course_not_started = CourseFactory.create(start=next_week, days_early_for_beta=10)
|
|
self.course_staff_only = CourseFactory.create(visible_to_staff_only=True)
|
|
self.course_mobile_available = CourseFactory.create(mobile_available=True)
|
|
self.course_with_pre_requisite = CourseFactory.create(
|
|
pre_requisite_courses=[str(self.course_started.id)]
|
|
)
|
|
self.course_with_pre_requisites = CourseFactory.create(
|
|
pre_requisite_courses=[str(self.course_started.id), str(self.course_not_started.id)]
|
|
)
|
|
|
|
self.user_normal = UserFactory.create()
|
|
self.user_beta_tester = BetaTesterFactory.create(course_key=self.course_not_started.id)
|
|
self.user_completed_pre_requisite = UserFactory.create()
|
|
fulfill_course_milestone(self.user_completed_pre_requisite, self.course_started.id)
|
|
self.user_staff = UserFactory.create(is_staff=True)
|
|
self.user_anonymous = AnonymousUserFactory.create()
|
|
|
|
COURSE_TEST_DATA = list(itertools.product(
|
|
['user_normal', 'user_staff', 'user_anonymous'],
|
|
['enroll', 'load', 'staff', 'instructor', 'see_exists', 'see_in_catalog', 'see_about_page'],
|
|
['course_default', 'course_started', 'course_not_started', 'course_staff_only'],
|
|
))
|
|
|
|
LOAD_MOBILE_TEST_DATA = list(itertools.product(
|
|
['user_normal', 'user_staff'],
|
|
['load_mobile'],
|
|
['course_default', 'course_mobile_available'],
|
|
))
|
|
|
|
PREREQUISITES_TEST_DATA = list(itertools.product(
|
|
['user_normal', 'user_completed_pre_requisite', 'user_staff', 'user_anonymous'],
|
|
['view_courseware_with_prerequisites'],
|
|
['course_default', 'course_with_pre_requisite', 'course_with_pre_requisites'],
|
|
))
|
|
|
|
@ddt.data(*(COURSE_TEST_DATA + LOAD_MOBILE_TEST_DATA + PREREQUISITES_TEST_DATA))
|
|
@ddt.unpack
|
|
@patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
|
|
def test_course_overview_access(self, user_attr_name, action, course_attr_name):
|
|
"""
|
|
Check that a user's access to a course is equal to the user's access to
|
|
the corresponding course overview.
|
|
|
|
Instead of taking a user and course directly as arguments, we have to
|
|
take their attribute names, as ddt doesn't allow us to reference self.
|
|
|
|
Arguments:
|
|
user_attr_name (str): the name of the attribute on self that is the
|
|
User to test with.
|
|
action (str): action to test with.
|
|
course_attr_name (str): the name of the attribute on self that is
|
|
the CourseDescriptor to test with.
|
|
"""
|
|
user = getattr(self, user_attr_name)
|
|
course = getattr(self, course_attr_name)
|
|
|
|
course_overview = CourseOverview.get_from_id(course.id)
|
|
self.assertEqual(
|
|
bool(access.has_access(user, action, course, course_key=course.id)),
|
|
bool(access.has_access(user, action, course_overview, course_key=course.id))
|
|
)
|
|
|
|
def test_course_overview_unsupported_action(self):
|
|
"""
|
|
Check that calling has_access with an unsupported action raises a
|
|
ValueError.
|
|
"""
|
|
overview = CourseOverview.get_from_id(self.course_default.id)
|
|
with self.assertRaises(ValueError):
|
|
access.has_access(self.user, '_non_existent_action', overview)
|
|
|
|
@ddt.data(
|
|
*itertools.product(
|
|
['user_normal', 'user_staff', 'user_anonymous'],
|
|
['see_exists', 'see_in_catalog', 'see_about_page'],
|
|
['course_default', 'course_started', 'course_not_started'],
|
|
)
|
|
)
|
|
@ddt.unpack
|
|
@patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
|
|
def test_course_catalog_access_num_queries(self, user_attr_name, action, course_attr_name):
|
|
course = getattr(self, course_attr_name)
|
|
|
|
# get a fresh user object that won't have any cached role information
|
|
if user_attr_name == 'user_anonymous':
|
|
user = AnonymousUserFactory()
|
|
else:
|
|
user = getattr(self, user_attr_name)
|
|
user = User.objects.get(id=user.id)
|
|
|
|
if user_attr_name == 'user_staff' and action == 'see_exists' and course_attr_name == 'course_not_started':
|
|
# checks staff role
|
|
num_queries = 1
|
|
elif user_attr_name == 'user_normal' and action == 'see_exists' and course_attr_name != 'course_started':
|
|
# checks staff role and enrollment data
|
|
num_queries = 2
|
|
else:
|
|
num_queries = 0
|
|
|
|
course_overview = CourseOverview.get_from_id(course.id)
|
|
with self.assertNumQueries(num_queries):
|
|
bool(access.has_access(user, action, course_overview, course_key=course.id))
|