class TestCourseAccess(ModuleStoreTestCase): """ Course-based access (as opposed to access of a non-course xblock) """ def setUp(self): """ Create a staff user and log them in (creating the client). Create a pool of users w/o granting them any permissions """ user_password = super(TestCourseAccess, self).setUp() self.client = AjaxEnabledTestClient() self.client.login(username=self.user.username, password=user_password) # create a course via the view handler which has a different strategy for permissions than the factory self.course_key = SlashSeparatedCourseKey('myu', 'mydept.mycourse', 'myrun') course_url = reverse_url('course_handler') self.client.ajax_post( course_url, { 'org': self.course_key.org, 'number': self.course_key.course, 'display_name': 'My favorite course', 'run': self.course_key.run, }, ) self.users = self._create_users() def _create_users(self): """ Create 8 users and return them """ users = [] for i in range(8): username = "******".format(i) email = "test+user{}@edx.org".format(i) user = User.objects.create_user(username, email, 'foo') user.is_active = True user.save() users.append(user) return users def tearDown(self): """ Reverse the setup """ self.client.logout() ModuleStoreTestCase.tearDown(self) def test_get_all_users(self): """ Test getting all authors for a course where their permissions run the gamut of allowed group types. """ # first check the course creator.has explicit access (don't use has_access as is_staff # will trump the actual test) self.assertTrue( CourseInstructorRole(self.course_key).has_user(self.user), "Didn't add creator as instructor." ) users = copy.copy(self.users) # doesn't use role.users_with_role b/c it's verifying the roles.py behavior user_by_role = {} # add the misc users to the course in different groups for role in [CourseInstructorRole, CourseStaffRole, OrgStaffRole, OrgInstructorRole]: user_by_role[role] = [] # Org-based roles are created via org name, rather than course_key if (role is OrgStaffRole) or (role is OrgInstructorRole): group = role(self.course_key.org) else: group = role(self.course_key) # NOTE: this loop breaks the roles.py abstraction by purposely assigning # users to one of each possible groupname in order to test that has_course_author_access # and remove_user work user = users.pop() group.add_users(user) user_by_role[role].append(user) self.assertTrue(auth.has_course_author_access(user, self.course_key), "{} does not have access".format(user)) course_team_url = reverse_course_url('course_team_handler', self.course_key) response = self.client.get_html(course_team_url) for role in [CourseInstructorRole, CourseStaffRole]: # Global and org-based roles don't appear on this page for user in user_by_role[role]: self.assertContains(response, user.email) # test copying course permissions copy_course_key = SlashSeparatedCourseKey('copyu', 'copydept.mycourse', 'myrun') for role in [CourseInstructorRole, CourseStaffRole, OrgStaffRole, OrgInstructorRole]: if (role is OrgStaffRole) or (role is OrgInstructorRole): auth.add_users( self.user, role(copy_course_key.org), *role(self.course_key.org).users_with_role() ) else: auth.add_users( self.user, role(copy_course_key), *role(self.course_key).users_with_role() ) # verify access in copy course and verify that removal from source course w/ the various # groupnames works for role in [CourseInstructorRole, CourseStaffRole, OrgStaffRole, OrgInstructorRole]: for user in user_by_role[role]: # forcefully decache the groups: premise is that any real request will not have # multiple objects repr the same user but this test somehow uses different instance # in above add_users call if hasattr(user, '_roles'): del user._roles self.assertTrue(auth.has_course_author_access(user, copy_course_key), "{} no copy access".format(user)) if (role is OrgStaffRole) or (role is OrgInstructorRole): auth.remove_users(self.user, role(self.course_key.org), user) else: auth.remove_users(self.user, role(self.course_key), user) self.assertFalse(auth.has_course_author_access(user, self.course_key), "{} remove didn't work".format(user))
class AuthTestCase(ContentStoreTestCase): """Check that various permissions-related things work""" def setUp(self): self.email = '*****@*****.**' self.pw = 'xyz' self.username = '******' self.client = AjaxEnabledTestClient() # clear the cache so ratelimiting won't affect these tests cache.clear() def check_page_get(self, url, expected): resp = self.client.get_html(url) self.assertEqual(resp.status_code, expected) return resp def test_public_pages_load(self): """Make sure pages that don't require login load without error.""" pages = ( reverse('login'), reverse('signup'), ) for page in pages: print("Checking '{0}'".format(page)) self.check_page_get(page, 200) def test_create_account_errors(self): # No post data -- should fail resp = self.client.post('/create_account', {}) self.assertEqual(resp.status_code, 200) data = parse_json(resp) self.assertEqual(data['success'], False) def test_create_account(self): self.create_account(self.username, self.email, self.pw) self.activate_user(self.email) def test_login(self): self.create_account(self.username, self.email, self.pw) # Not activated yet. Login should fail. resp = self._login(self.email, self.pw) data = parse_json(resp) self.assertFalse(data['success']) self.activate_user(self.email) # Now login should work self.login(self.email, self.pw) def test_login_ratelimited(self): # try logging in 30 times, the default limit in the number of failed # login attempts in one 5 minute period before the rate gets limited for i in xrange(30): resp = self._login(self.email, 'wrong_password{0}'.format(i)) self.assertEqual(resp.status_code, 200) resp = self._login(self.email, 'wrong_password') self.assertEqual(resp.status_code, 200) data = parse_json(resp) self.assertFalse(data['success']) self.assertIn('Too many failed login attempts.', data['value']) def test_login_link_on_activation_age(self): self.create_account(self.username, self.email, self.pw) # we want to test the rendering of the activation page when the user isn't logged in self.client.logout() resp = self._activate_user(self.email) self.assertEqual(resp.status_code, 200) # check the the HTML has links to the right login page. Note that this is merely a content # check and thus could be fragile should the wording change on this page expected = 'You can now <a href="' + reverse('login') + '">login</a>.' self.assertIn(expected, resp.content) def test_private_pages_auth(self): """Make sure pages that do require login work.""" auth_pages = ('/course', ) # These are pages that should just load when the user is logged in # (no data needed) simple_auth_pages = ('/course', ) # need an activated user self.test_create_account() # Create a new session self.client = AjaxEnabledTestClient() # Not logged in. Should redirect to login. print('Not logged in') for page in auth_pages: print("Checking '{0}'".format(page)) self.check_page_get(page, expected=302) # Logged in should work. self.login(self.email, self.pw) print('Logged in') for page in simple_auth_pages: print("Checking '{0}'".format(page)) self.check_page_get(page, expected=200) def test_index_auth(self): # not logged in. Should return a redirect. resp = self.client.get_html('/course') self.assertEqual(resp.status_code, 302)
class TestCourseListing(ModuleStoreTestCase): """ Unit tests for getting the list of courses for a logged in user """ def setUp(self): """ Add a user and a course """ super(TestCourseListing, self).setUp() # create and log in a staff user. self.user = UserFactory(is_staff=True) # pylint: disable=no-member self.factory = RequestFactory() self.client = AjaxEnabledTestClient() self.client.login(username=self.user.username, password='******') def _create_course_with_access_groups( self, course_location, group_name_format='group_name_with_dots', user=None): """ Create dummy course with 'CourseFactory' and role (instructor/staff) groups with provided group_name_format """ course_locator = loc_mapper().translate_location( course_location.course_id, course_location, False, True) course = CourseFactory.create(org=course_location.org, number=course_location.course, display_name=course_location.name) for role in [CourseInstructorRole, CourseStaffRole]: # pylint: disable=protected-access groupnames = role(course_locator)._group_names if group_name_format == 'group_name_with_course_name_only': # Create role (instructor/staff) groups with course_name only: 'instructor_run' group, __ = Group.objects.get_or_create(name=groupnames[2]) elif group_name_format == 'group_name_with_slashes': # Create role (instructor/staff) groups with format: 'instructor_edX/Course/Run' # Since "Group.objects.get_or_create(name=groupnames[1])" would have made group with lowercase name # so manually create group name of old type if role == CourseInstructorRole: group, __ = Group.objects.get_or_create( name=u'{}_{}'.format('instructor', course_location.course_id)) else: group, __ = Group.objects.get_or_create( name=u'{}_{}'.format('staff', course_location.course_id)) else: # Create role (instructor/staff) groups with format: 'instructor_edx.course.run' group, __ = Group.objects.get_or_create(name=groupnames[0]) if user is not None: user.groups.add(group) return course def tearDown(self): """ Reverse the setup """ self.client.logout() ModuleStoreTestCase.tearDown(self) def test_get_course_list(self): """ Test getting courses with new access group format e.g. 'instructor_edx.course.run' """ request = self.factory.get('/course') request.user = self.user course_location = Location( ['i4x', 'Org1', 'Course1', 'course', 'Run1']) self._create_course_with_access_groups(course_location, 'group_name_with_dots', self.user) # get courses through iterating all courses courses_list = _accessible_courses_list(request) self.assertEqual(len(courses_list), 1) # get courses by reversing group name formats courses_list_by_groups = _accessible_courses_list_from_groups(request) self.assertEqual(len(courses_list_by_groups), 1) # check both course lists have same courses self.assertEqual(courses_list, courses_list_by_groups) def test_get_course_list_with_old_group_formats(self): """ Test getting all courses with old course role (instructor/staff) groups """ request = self.factory.get('/course') request.user = self.user # create a course with new groups name format e.g. 'instructor_edx.course.run' course_location = Location( ['i4x', 'Org_1', 'Course_1', 'course', 'Run_1']) self._create_course_with_access_groups(course_location, 'group_name_with_dots', self.user) # create a course with old groups name format e.g. 'instructor_edX/Course/Run' old_course_location = Location( ['i4x', 'Org_2', 'Course_2', 'course', 'Run_2']) self._create_course_with_access_groups(old_course_location, 'group_name_with_slashes', self.user) # get courses through iterating all courses courses_list = _accessible_courses_list(request) self.assertEqual(len(courses_list), 2) # get courses by reversing groups name courses_list_by_groups = _accessible_courses_list_from_groups(request) self.assertEqual(len(courses_list_by_groups), 2) # create a new course with older group name format (with dots in names) e.g. 'instructor_edX/Course.name/Run.1' old_course_location = Location( ['i4x', 'Org.Foo.Bar', 'Course.number', 'course', 'Run.name']) self._create_course_with_access_groups(old_course_location, 'group_name_with_slashes', self.user) # get courses through iterating all courses courses_list = _accessible_courses_list(request) self.assertEqual(len(courses_list), 3) # get courses by reversing group name formats courses_list_by_groups = _accessible_courses_list_from_groups(request) self.assertEqual(len(courses_list_by_groups), 3) # create a new course with older group name format e.g. 'instructor_Run' old_course_location = Location( ['i4x', 'Org_3', 'Course_3', 'course', 'Run_3']) self._create_course_with_access_groups( old_course_location, 'group_name_with_course_name_only', self.user) # get courses through iterating all courses courses_list = _accessible_courses_list(request) self.assertEqual(len(courses_list), 4) # should raise an exception for getting courses with older format of access group by reversing django groups with self.assertRaises(ItemNotFoundError): courses_list_by_groups = _accessible_courses_list_from_groups( request) def test_get_course_list_with_invalid_course_location(self): """ Test getting courses with invalid course location (course deleted from modulestore but location exists in loc_mapper). """ request = self.factory.get('/course') request.user = self.user course_location = Location('i4x', 'Org', 'Course', 'course', 'Run') self._create_course_with_access_groups(course_location, 'group_name_with_dots', self.user) # get courses through iterating all courses courses_list = _accessible_courses_list(request) self.assertEqual(len(courses_list), 1) # get courses by reversing group name formats courses_list_by_groups = _accessible_courses_list_from_groups(request) self.assertEqual(len(courses_list_by_groups), 1) # check both course lists have same courses self.assertEqual(courses_list, courses_list_by_groups) # now delete this course and re-add user to instructor group of this course delete_course_and_groups(course_location.course_id, commit=True) course_locator = loc_mapper().translate_location( course_location.course_id, course_location) instructor_group_name = CourseInstructorRole( course_locator)._group_names[0] # pylint: disable=protected-access group, __ = Group.objects.get_or_create(name=instructor_group_name) self.user.groups.add(group) # test that get courses through iterating all courses now returns no course courses_list = _accessible_courses_list(request) self.assertEqual(len(courses_list), 0) # now test that get courses by reversing group name formats gives 'ItemNotFoundError' with self.assertRaises(ItemNotFoundError): _accessible_courses_list_from_groups(request) def test_course_listing_performance(self): """ Create large number of courses and give access of some of these courses to the user and compare the time to fetch accessible courses for the user through traversing all courses and reversing django groups """ # create and log in a non-staff user self.user = UserFactory() request = self.factory.get('/course') request.user = self.user self.client.login(username=self.user.username, password='******') # create list of random course numbers which will be accessible to the user user_course_ids = random.sample(range(TOTAL_COURSES_COUNT), USER_COURSES_COUNT) # create courses and assign those to the user which have their number in user_course_ids for number in range(TOTAL_COURSES_COUNT): org = 'Org{0}'.format(number) course = 'Course{0}'.format(number) run = 'Run{0}'.format(number) course_location = Location(['i4x', org, course, 'course', run]) if number in user_course_ids: self._create_course_with_access_groups(course_location, 'group_name_with_dots', self.user) else: self._create_course_with_access_groups(course_location, 'group_name_with_dots') # time the get courses by iterating through all courses with Timer() as iteration_over_courses_time_1: courses_list = _accessible_courses_list(request) self.assertEqual(len(courses_list), USER_COURSES_COUNT) # time again the get courses by iterating through all courses with Timer() as iteration_over_courses_time_2: courses_list = _accessible_courses_list(request) self.assertEqual(len(courses_list), USER_COURSES_COUNT) # time the get courses by reversing django groups with Timer() as iteration_over_groups_time_1: courses_list = _accessible_courses_list_from_groups(request) self.assertEqual(len(courses_list), USER_COURSES_COUNT) # time again the get courses by reversing django groups with Timer() as iteration_over_groups_time_2: courses_list = _accessible_courses_list_from_groups(request) self.assertEqual(len(courses_list), USER_COURSES_COUNT) # test that the time taken by getting courses through reversing django groups is lower then the time # taken by traversing through all courses (if accessible courses are relatively small) self.assertGreaterEqual(iteration_over_courses_time_1.elapsed, iteration_over_groups_time_1.elapsed) self.assertGreaterEqual(iteration_over_courses_time_2.elapsed, iteration_over_groups_time_2.elapsed) def test_get_course_list_with_same_course_id(self): """ Test getting courses with same id but with different name case. Then try to delete one of them and check that it is properly deleted and other one is accessible """ # create and log in a non-staff user self.user = UserFactory() request = self.factory.get('/course') request.user = self.user self.client.login(username=self.user.username, password='******') course_location_caps = Location( ['i4x', 'Org', 'COURSE', 'course', 'Run']) self._create_course_with_access_groups(course_location_caps, 'group_name_with_dots', self.user) # get courses through iterating all courses courses_list = _accessible_courses_list(request) self.assertEqual(len(courses_list), 1) # get courses by reversing group name formats courses_list_by_groups = _accessible_courses_list_from_groups(request) self.assertEqual(len(courses_list_by_groups), 1) # check both course lists have same courses self.assertEqual(courses_list, courses_list_by_groups) # now create another course with same course_id but different name case course_location_camel = Location( ['i4x', 'Org', 'Course', 'course', 'Run']) self._create_course_with_access_groups(course_location_camel, 'group_name_with_dots', self.user) # test that get courses through iterating all courses returns both courses courses_list = _accessible_courses_list(request) self.assertEqual(len(courses_list), 2) # test that get courses by reversing group name formats returns only one course courses_list_by_groups = _accessible_courses_list_from_groups(request) self.assertEqual(len(courses_list_by_groups), 1) course_locator = loc_mapper().translate_location( course_location_caps.course_id, course_location_caps) outline_url = course_locator.url_reverse('course/') # now delete first course (course_location_caps) and check that it is no longer accessible delete_course_and_groups(course_location_caps.course_id, commit=True) # add user to this course instructor group since he was removed from that group on course delete instructor_group_name = CourseInstructorRole( course_locator)._group_names[0] # pylint: disable=protected-access group, __ = Group.objects.get_or_create(name=instructor_group_name) self.user.groups.add(group) # test viewing the index page which creates missing courses loc_map entries resp = self.client.get_html('/course') self.assertContains(resp, '<h1 class="page-header">My Courses</h1>', status_code=200, html=True) # test that get courses through iterating all courses now returns one course courses_list = _accessible_courses_list(request) self.assertEqual(len(courses_list), 1) # test that get courses by reversing group name formats also returns one course courses_list_by_groups = _accessible_courses_list_from_groups(request) self.assertEqual(len(courses_list_by_groups), 1) # now check that deleted course in not accessible response = self.client.get(outline_url, HTTP_ACCEPT='application/json') self.assertEqual(response.status_code, 403) # now check that other course in accessible course_locator = loc_mapper().translate_location( course_location_camel.course_id, course_location_camel) outline_url = course_locator.url_reverse('course/') response = self.client.get(outline_url, HTTP_ACCEPT='application/json') self.assertEqual(response.status_code, 200)
class AuthTestCase(ContentStoreTestCase): """Check that various permissions-related things work""" def setUp(self): self.email = '*****@*****.**' self.pw = 'xyz' self.username = '******' self.client = AjaxEnabledTestClient() # clear the cache so ratelimiting won't affect these tests cache.clear() def check_page_get(self, url, expected): resp = self.client.get_html(url) self.assertEqual(resp.status_code, expected) return resp def test_public_pages_load(self): """Make sure pages that don't require login load without error.""" pages = ( reverse('login'), reverse('signup'), ) for page in pages: print("Checking '{0}'".format(page)) self.check_page_get(page, 200) def test_create_account_errors(self): # No post data -- should fail resp = self.client.post('/create_account', {}) self.assertEqual(resp.status_code, 400) data = parse_json(resp) self.assertEqual(data['success'], False) def test_create_account(self): self.create_account(self.username, self.email, self.pw) self.activate_user(self.email) def test_create_account_username_already_exists(self): User.objects.create_user(self.username, self.email, self.pw) resp = self._create_account(self.username, "*****@*****.**", "password") # we have a constraint on unique usernames, so this should fail self.assertEqual(resp.status_code, 400) def test_create_account_pw_already_exists(self): User.objects.create_user(self.username, self.email, self.pw) resp = self._create_account("abcdef", "*****@*****.**", self.pw) # we can have two users with the same password, so this should succeed self.assertEqual(resp.status_code, 200) @unittest.skipUnless(settings.SOUTH_TESTS_MIGRATE, "South migrations required") def test_create_account_email_already_exists(self): User.objects.create_user(self.username, self.email, self.pw) resp = self._create_account("abcdef", self.email, "password") # This is tricky. Django's user model doesn't have a constraint on # unique email addresses, but we *add* that constraint during the # migration process: # see common/djangoapps/student/migrations/0004_add_email_index.py # # The behavior we *want* is for this account creation request # to fail, due to this uniqueness constraint, but the request will # succeed if the migrations have not run. self.assertEqual(resp.status_code, 400) def test_login(self): self.create_account(self.username, self.email, self.pw) # Not activated yet. Login should fail. resp = self._login(self.email, self.pw) data = parse_json(resp) self.assertFalse(data['success']) self.activate_user(self.email) # Now login should work self.login(self.email, self.pw) def test_login_ratelimited(self): # try logging in 30 times, the default limit in the number of failed # login attempts in one 5 minute period before the rate gets limited for i in xrange(30): resp = self._login(self.email, 'wrong_password{0}'.format(i)) self.assertEqual(resp.status_code, 200) resp = self._login(self.email, 'wrong_password') self.assertEqual(resp.status_code, 200) data = parse_json(resp) self.assertFalse(data['success']) self.assertIn('Too many failed login attempts.', data['value']) @override_settings(MAX_FAILED_LOGIN_ATTEMPTS_ALLOWED=3) @override_settings(MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS=2) def test_excessive_login_failures(self): # try logging in 3 times, the account should get locked for 3 seconds # note we want to keep the lockout time short, so we don't slow down the tests with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MAX_FAILED_LOGIN_ATTEMPTS': True}): self.create_account(self.username, self.email, self.pw) self.activate_user(self.email) for i in xrange(3): resp = self._login(self.email, 'wrong_password{0}'.format(i)) self.assertEqual(resp.status_code, 200) data = parse_json(resp) self.assertFalse(data['success']) self.assertIn('Email or password is incorrect.', data['value']) # now the account should be locked resp = self._login(self.email, 'wrong_password') self.assertEqual(resp.status_code, 200) data = parse_json(resp) self.assertFalse(data['success']) self.assertIn( 'This account has been temporarily locked due to excessive login failures. Try again later.', data['value']) with freeze_time('2100-01-01'): self.login(self.email, self.pw) # make sure the failed attempt counter gets reset on successful login resp = self._login(self.email, 'wrong_password') self.assertEqual(resp.status_code, 200) data = parse_json(resp) self.assertFalse(data['success']) # account should not be locked out after just one attempt self.login(self.email, self.pw) # do one more login when there is no bad login counter row at all in the database to # test the "ObjectNotFound" case self.login(self.email, self.pw) def test_login_link_on_activation_age(self): self.create_account(self.username, self.email, self.pw) # we want to test the rendering of the activation page when the user isn't logged in self.client.logout() resp = self._activate_user(self.email) self.assertEqual(resp.status_code, 200) # check the the HTML has links to the right login page. Note that this is merely a content # check and thus could be fragile should the wording change on this page expected = 'You can now <a href="' + reverse('login') + '">login</a>.' self.assertIn(expected, resp.content) def test_private_pages_auth(self): """Make sure pages that do require login work.""" auth_pages = ('/course/', ) # These are pages that should just load when the user is logged in # (no data needed) simple_auth_pages = ('/course/', ) # need an activated user self.test_create_account() # Create a new session self.client = AjaxEnabledTestClient() # Not logged in. Should redirect to login. print('Not logged in') for page in auth_pages: print("Checking '{0}'".format(page)) self.check_page_get(page, expected=302) # Logged in should work. self.login(self.email, self.pw) print('Logged in') for page in simple_auth_pages: print("Checking '{0}'".format(page)) self.check_page_get(page, expected=200) def test_index_auth(self): # not logged in. Should return a redirect. resp = self.client.get_html('/course/') self.assertEqual(resp.status_code, 302) # Logged in should work. @override_settings(SESSION_INACTIVITY_TIMEOUT_IN_SECONDS=1) def test_inactive_session_timeout(self): """ Verify that an inactive session times out and redirects to the login page """ self.create_account(self.username, self.email, self.pw) self.activate_user(self.email) self.login(self.email, self.pw) # make sure we can access courseware immediately course_url = '/course/' resp = self.client.get_html(course_url) self.assertEquals(resp.status_code, 200) # then wait a bit and see if we get timed out time.sleep(2) resp = self.client.get_html(course_url) # re-request, and we should get a redirect to login page self.assertRedirects(resp, settings.LOGIN_REDIRECT_URL + '?next=/course/')
class InternationalizationTest(ModuleStoreTestCase): """ Tests to validate Internationalization. """ def setUp(self): """ These tests need a user in the DB so that the django Test Client can log them in. They inherit from the ModuleStoreTestCase class so that the mongodb collection will be cleared out before each test case execution and deleted afterwards. """ super(InternationalizationTest, self).setUp(create_user=False) self.uname = 'testuser' self.email = '*****@*****.**' self.password = '******' # Create the use so we can log them in. self.user = User.objects.create_user(self.uname, self.email, self.password) # Note that we do not actually need to do anything # for registration if we directly mark them active. self.user.is_active = True # Staff has access to view all courses self.user.is_staff = True self.user.save() self.course_data = { 'org': 'MITx', 'number': '999', 'display_name': 'Robot Super Course', } def test_course_plain_english(self): """Test viewing the index page with no courses""" self.client = AjaxEnabledTestClient() self.client.login(username=self.uname, password=self.password) resp = self.client.get_html('/home/') self.assertContains(resp, '<h1 class="page-header">Studio Home</h1>', status_code=200, html=True) def test_course_explicit_english(self): """Test viewing the index page with no courses""" self.client = AjaxEnabledTestClient() self.client.login(username=self.uname, password=self.password) resp = self.client.get_html( '/home/', {}, HTTP_ACCEPT_LANGUAGE='en', ) self.assertContains(resp, '<h1 class="page-header">Studio Home</h1>', status_code=200, html=True) # **** # NOTE: # **** # # This test will break when we replace this fake 'test' language # with actual Esperanto. This test will need to be updated with # actual Esperanto at that time. # Test temporarily disable since it depends on creation of dummy strings @skip def test_course_with_accents(self): """Test viewing the index page with no courses""" self.client = AjaxEnabledTestClient() self.client.login(username=self.uname, password=self.password) resp = self.client.get_html('/home/', {}, HTTP_ACCEPT_LANGUAGE='eo') TEST_STRING = (u'<h1 class="title-1">' u'My \xc7\xf6\xfcrs\xe9s L#' u'</h1>') self.assertContains(resp, TEST_STRING, status_code=200, html=True)
class AuthTestCase(ContentStoreTestCase): """Check that various permissions-related things work""" def setUp(self): self.email = '*****@*****.**' self.pw = 'xyz' self.username = '******' self.client = AjaxEnabledTestClient() # clear the cache so ratelimiting won't affect these tests cache.clear() def check_page_get(self, url, expected): resp = self.client.get_html(url) self.assertEqual(resp.status_code, expected) return resp def test_public_pages_load(self): """Make sure pages that don't require login load without error.""" pages = ( reverse('login'), reverse('signup'), ) for page in pages: print("Checking '{0}'".format(page)) self.check_page_get(page, 200) def test_create_account_errors(self): # No post data -- should fail resp = self.client.post('/create_account', {}) self.assertEqual(resp.status_code, 400) data = parse_json(resp) self.assertEqual(data['success'], False) def test_create_account(self): self.create_account(self.username, self.email, self.pw) self.activate_user(self.email) def test_create_account_username_already_exists(self): User.objects.create_user(self.username, self.email, self.pw) resp = self._create_account(self.username, "*****@*****.**", "password") # we have a constraint on unique usernames, so this should fail self.assertEqual(resp.status_code, 400) def test_create_account_pw_already_exists(self): User.objects.create_user(self.username, self.email, self.pw) resp = self._create_account("abcdef", "*****@*****.**", self.pw) # we can have two users with the same password, so this should succeed self.assertEqual(resp.status_code, 200) @unittest.skipUnless(settings.SOUTH_TESTS_MIGRATE, "South migrations required") def test_create_account_email_already_exists(self): User.objects.create_user(self.username, self.email, self.pw) resp = self._create_account("abcdef", self.email, "password") # This is tricky. Django's user model doesn't have a constraint on # unique email addresses, but we *add* that constraint during the # migration process: # see common/djangoapps/student/migrations/0004_add_email_index.py # # The behavior we *want* is for this account creation request # to fail, due to this uniqueness constraint, but the request will # succeed if the migrations have not run. self.assertEqual(resp.status_code, 400) def test_login(self): self.create_account(self.username, self.email, self.pw) # Not activated yet. Login should fail. resp = self._login(self.email, self.pw) data = parse_json(resp) self.assertFalse(data['success']) self.activate_user(self.email) # Now login should work self.login(self.email, self.pw) def test_login_ratelimited(self): # try logging in 30 times, the default limit in the number of failed # login attempts in one 5 minute period before the rate gets limited for i in xrange(30): resp = self._login(self.email, 'wrong_password{0}'.format(i)) self.assertEqual(resp.status_code, 200) resp = self._login(self.email, 'wrong_password') self.assertEqual(resp.status_code, 200) data = parse_json(resp) self.assertFalse(data['success']) self.assertIn('Too many failed login attempts.', data['value']) @override_settings(MAX_FAILED_LOGIN_ATTEMPTS_ALLOWED=3) @override_settings(MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS=2) def test_excessive_login_failures(self): # try logging in 3 times, the account should get locked for 3 seconds # note we want to keep the lockout time short, so we don't slow down the tests with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MAX_FAILED_LOGIN_ATTEMPTS': True}): self.create_account(self.username, self.email, self.pw) self.activate_user(self.email) for i in xrange(3): resp = self._login(self.email, 'wrong_password{0}'.format(i)) self.assertEqual(resp.status_code, 200) data = parse_json(resp) self.assertFalse(data['success']) self.assertIn( 'Email or password is incorrect.', data['value'] ) # now the account should be locked resp = self._login(self.email, 'wrong_password') self.assertEqual(resp.status_code, 200) data = parse_json(resp) self.assertFalse(data['success']) self.assertIn( 'This account has been temporarily locked due to excessive login failures. Try again later.', data['value'] ) with freeze_time('2100-01-01'): self.login(self.email, self.pw) # make sure the failed attempt counter gets reset on successful login resp = self._login(self.email, 'wrong_password') self.assertEqual(resp.status_code, 200) data = parse_json(resp) self.assertFalse(data['success']) # account should not be locked out after just one attempt self.login(self.email, self.pw) # do one more login when there is no bad login counter row at all in the database to # test the "ObjectNotFound" case self.login(self.email, self.pw) def test_login_link_on_activation_age(self): self.create_account(self.username, self.email, self.pw) # we want to test the rendering of the activation page when the user isn't logged in self.client.logout() resp = self._activate_user(self.email) self.assertEqual(resp.status_code, 200) # check the the HTML has links to the right login page. Note that this is merely a content # check and thus could be fragile should the wording change on this page expected = 'You can now <a href="' + reverse('login') + '">login</a>.' self.assertIn(expected, resp.content) def test_private_pages_auth(self): """Make sure pages that do require login work.""" auth_pages = ( '/course/', ) # These are pages that should just load when the user is logged in # (no data needed) simple_auth_pages = ( '/course/', ) # need an activated user self.test_create_account() # Create a new session self.client = AjaxEnabledTestClient() # Not logged in. Should redirect to login. print('Not logged in') for page in auth_pages: print("Checking '{0}'".format(page)) self.check_page_get(page, expected=302) # Logged in should work. self.login(self.email, self.pw) print('Logged in') for page in simple_auth_pages: print("Checking '{0}'".format(page)) self.check_page_get(page, expected=200) def test_index_auth(self): # not logged in. Should return a redirect. resp = self.client.get_html('/course/') self.assertEqual(resp.status_code, 302) # Logged in should work. @override_settings(SESSION_INACTIVITY_TIMEOUT_IN_SECONDS=1) def test_inactive_session_timeout(self): """ Verify that an inactive session times out and redirects to the login page """ self.create_account(self.username, self.email, self.pw) self.activate_user(self.email) self.login(self.email, self.pw) # make sure we can access courseware immediately course_url = '/course/' resp = self.client.get_html(course_url) self.assertEquals(resp.status_code, 200) # then wait a bit and see if we get timed out time.sleep(2) resp = self.client.get_html(course_url) # re-request, and we should get a redirect to login page self.assertRedirects(resp, settings.LOGIN_REDIRECT_URL + '?next=/course/')
class AuthTestCase(ContentStoreTestCase): """Check that various permissions-related things work""" CREATE_USER = False ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache'] def setUp(self): super(AuthTestCase, self).setUp() self.email = '*****@*****.**' self.pw = 'xyz' self.username = '******' self.client = AjaxEnabledTestClient() # clear the cache so ratelimiting won't affect these tests cache.clear() def check_page_get(self, url, expected): resp = self.client.get_html(url) self.assertEqual(resp.status_code, expected) return resp def test_public_pages_load(self): """Make sure pages that don't require login load without error.""" pages = ( reverse('login'), reverse('signup'), ) for page in pages: print("Checking '{0}'".format(page)) self.check_page_get(page, 200) def test_create_account_errors(self): # No post data -- should fail registration_url = reverse('user_api_registration') resp = self.client.post(registration_url, {}) self.assertEqual(resp.status_code, 400) def test_create_account(self): self.create_account(self.username, self.email, self.pw) self.activate_user(self.email) def test_create_account_username_already_exists(self): User.objects.create_user(self.username, self.email, self.pw) resp = self._create_account(self.username, "*****@*****.**", "password") # we have a constraint on unique usernames, so this should fail self.assertEqual(resp.status_code, 409) def test_create_account_pw_already_exists(self): User.objects.create_user(self.username, self.email, self.pw) resp = self._create_account("abcdef", "*****@*****.**", self.pw) # we can have two users with the same password, so this should succeed self.assertEqual(resp.status_code, 200) def test_login(self): self.create_account(self.username, self.email, self.pw) # Not activated yet. Login should fail. resp = self._login(self.email, self.pw) self.activate_user(self.email) # Now login should work self.login(self.email, self.pw) def test_login_ratelimited(self): # try logging in 30 times, the default limit in the number of failed # login attempts in one 5 minute period before the rate gets limited for i in xrange(30): resp = self._login(self.email, 'wrong_password{0}'.format(i)) self.assertEqual(resp.status_code, 403) resp = self._login(self.email, 'wrong_password') self.assertEqual(resp.status_code, 403) self.assertIn('Too many failed login attempts.', resp.content) @override_settings(MAX_FAILED_LOGIN_ATTEMPTS_ALLOWED=3) @override_settings(MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS=2) def test_excessive_login_failures(self): # try logging in 3 times, the account should get locked for 3 seconds # note we want to keep the lockout time short, so we don't slow down the tests with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MAX_FAILED_LOGIN_ATTEMPTS': True}): self.create_account(self.username, self.email, self.pw) self.activate_user(self.email) for i in xrange(3): resp = self._login(self.email, 'wrong_password{0}'.format(i)) self.assertEqual(resp.status_code, 403) self.assertIn( 'Email or password is incorrect.', resp.content ) # now the account should be locked resp = self._login(self.email, 'wrong_password') self.assertEqual(resp.status_code, 403) self.assertIn( 'This account has been temporarily locked due to excessive login failures.', resp.content ) with freeze_time('2100-01-01'): self.login(self.email, self.pw) # make sure the failed attempt counter gets reset on successful login resp = self._login(self.email, 'wrong_password') self.assertEqual(resp.status_code, 403) self.assertIn( 'Email or password is incorrect.', resp.content ) # account should not be locked out after just one attempt self.login(self.email, self.pw) # do one more login when there is no bad login counter row at all in the database to # test the "ObjectNotFound" case self.login(self.email, self.pw) def test_login_link_on_activation_age(self): self.create_account(self.username, self.email, self.pw) # we want to test the rendering of the activation page when the user isn't logged in self.client.logout() resp = self._activate_user(self.email) self.assertEqual(resp.status_code, 200) # check the the HTML has links to the right login page. Note that this is merely a content # check and thus could be fragile should the wording change on this page expected = 'You can now <a href="' + reverse('login') + '">sign in</a>.' self.assertIn(expected, resp.content.decode('utf-8')) def test_private_pages_auth(self): """Make sure pages that do require login work.""" auth_pages = ( '/home/', ) # These are pages that should just load when the user is logged in # (no data needed) simple_auth_pages = ( '/home/', ) # need an activated user self.test_create_account() # Create a new session self.client = AjaxEnabledTestClient() # Not logged in. Should redirect to login. print('Not logged in') for page in auth_pages: print("Checking '{0}'".format(page)) self.check_page_get(page, expected=302) # Logged in should work. self.login(self.email, self.pw) print('Logged in') for page in simple_auth_pages: print("Checking '{0}'".format(page)) self.check_page_get(page, expected=200) def test_index_auth(self): # not logged in. Should return a redirect. resp = self.client.get_html('/home/') self.assertEqual(resp.status_code, 302) # Logged in should work. @override_settings(SESSION_INACTIVITY_TIMEOUT_IN_SECONDS=1) def test_inactive_session_timeout(self): """ Verify that an inactive session times out and redirects to the login page """ self.create_account(self.username, self.email, self.pw) self.activate_user(self.email) self.login(self.email, self.pw) # make sure we can access courseware immediately course_url = '/home/' resp = self.client.get_html(course_url) self.assertEquals(resp.status_code, 200) # then wait a bit and see if we get timed out time.sleep(2) resp = self.client.get_html(course_url) # re-request, and we should get a redirect to login page self.assertRedirects(resp, settings.LOGIN_URL + '?next=/home/') @mock.patch.dict(settings.FEATURES, {"ALLOW_PUBLIC_ACCOUNT_CREATION": False}) def test_signup_button_index_page(self): """ Navigate to the home page and check the Sign Up button is hidden when ALLOW_PUBLIC_ACCOUNT_CREATION flag is turned off """ response = self.client.get(reverse('homepage')) self.assertNotIn('<a class="action action-signup" href="/signup">Sign Up</a>', response.content) @mock.patch.dict(settings.FEATURES, {"ALLOW_PUBLIC_ACCOUNT_CREATION": False}) def test_signup_button_login_page(self): """ Navigate to the login page and check the Sign Up button is hidden when ALLOW_PUBLIC_ACCOUNT_CREATION flag is turned off """ response = self.client.get(reverse('login')) self.assertNotIn('<a class="action action-signup" href="/signup">Sign Up</a>', response.content) @mock.patch.dict(settings.FEATURES, {"ALLOW_PUBLIC_ACCOUNT_CREATION": False}) def test_signup_link_login_page(self): """ Navigate to the login page and check the Sign Up link is hidden when ALLOW_PUBLIC_ACCOUNT_CREATION flag is turned off """ response = self.client.get(reverse('login')) self.assertNotIn('<a href="/signup" class="action action-signin">Don't have a Studio Account? Sign up!</a>', response.content)
class TestCourseAccess(ModuleStoreTestCase): """ Course-based access (as opposed to access of a non-course xblock) """ def setUp(self): """ Create a staff user and log them in (creating the client). Create a pool of users w/o granting them any permissions """ super(TestCourseAccess, self).setUp() uname = 'testuser' email = '*****@*****.**' password = '******' # Create the use so we can log them in. self.user = User.objects.create_user(uname, email, password) # Note that we do not actually need to do anything # for registration if we directly mark them active. self.user.is_active = True # Staff has access to view all courses self.user.is_staff = True self.user.save() self.client = AjaxEnabledTestClient() self.client.login(username=uname, password=password) # create a course via the view handler which has a different strategy for permissions than the factory self.course_location = Location(['i4x', 'myu', 'mydept.mycourse', 'course', 'myrun']) self.course_locator = loc_mapper().translate_location( self.course_location.course_id, self.course_location, False, True ) self.client.ajax_post( self.course_locator.url_reverse('course'), { 'org': self.course_location.org, 'number': self.course_location.course, 'display_name': 'My favorite course', 'run': self.course_location.name, } ) self.users = self._create_users() def _create_users(self): """ Create 8 users and return them """ users = [] for i in range(8): username = "******".format(i) email = "test+user{}@edx.org".format(i) user = User.objects.create_user(username, email, 'foo') user.is_active = True user.save() users.append(user) return users def tearDown(self): """ Reverse the setup """ self.client.logout() ModuleStoreTestCase.tearDown(self) def test_get_all_users(self): """ Test getting all authors for a course where their permissions run the gamut of allowed group types. """ # first check the groupname for the course creator. self.assertTrue( self.user.groups.filter( name="{}_{}".format(INSTRUCTOR_ROLE_NAME, self.course_locator.package_id) ).exists(), "Didn't add creator as instructor." ) users = copy.copy(self.users) user_by_role = {} # add the misc users to the course in different groups for role in [INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME]: user_by_role[role] = [] groupnames, _ = authz.get_all_course_role_groupnames(self.course_locator, role) for groupname in groupnames: group, _ = Group.objects.get_or_create(name=groupname) user = users.pop() user_by_role[role].append(user) user.groups.add(group) user.save() self.assertTrue(has_access(user, self.course_locator), "{} does not have access".format(user)) self.assertTrue(has_access(user, self.course_location), "{} does not have access".format(user)) response = self.client.get_html(self.course_locator.url_reverse('course_team')) for role in [INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME]: for user in user_by_role[role]: self.assertContains(response, user.email) # test copying course permissions copy_course_location = Location(['i4x', 'copyu', 'copydept.mycourse', 'course', 'myrun']) copy_course_locator = loc_mapper().translate_location( copy_course_location.course_id, copy_course_location, False, True ) # pylint: disable=protected-access authz._copy_course_group(self.course_locator, copy_course_locator) for role in [INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME]: for user in user_by_role[role]: self.assertTrue(has_access(user, copy_course_locator), "{} no copy access".format(user)) self.assertTrue(has_access(user, copy_course_location), "{} no copy access".format(user))
class TestCourseAccess(ModuleStoreTestCase): """ Course-based access (as opposed to access of a non-course xblock) """ def setUp(self): """ Create a staff user and log them in (creating the client). Create a pool of users w/o granting them any permissions """ super(TestCourseAccess, self).setUp() uname = 'testuser' email = '*****@*****.**' password = '******' # Create the use so we can log them in. self.user = User.objects.create_user(uname, email, password) # Note that we do not actually need to do anything # for registration if we directly mark them active. self.user.is_active = True # Staff has access to view all courses self.user.is_staff = True self.user.save() self.client = AjaxEnabledTestClient() self.client.login(username=uname, password=password) # create a course via the view handler which has a different strategy for permissions than the factory self.course_key = SlashSeparatedCourseKey('myu', 'mydept.mycourse', 'myrun') course_url = reverse_url('course_handler') self.client.ajax_post( course_url, { 'org': self.course_key.org, 'number': self.course_key.course, 'display_name': 'My favorite course', 'run': self.course_key.run, }) self.users = self._create_users() def _create_users(self): """ Create 8 users and return them """ users = [] for i in range(8): username = "******".format(i) email = "test+user{}@edx.org".format(i) user = User.objects.create_user(username, email, 'foo') user.is_active = True user.save() users.append(user) return users def tearDown(self): """ Reverse the setup """ self.client.logout() ModuleStoreTestCase.tearDown(self) def test_get_all_users(self): """ Test getting all authors for a course where their permissions run the gamut of allowed group types. """ # first check the course creator.has explicit access (don't use has_access as is_staff # will trump the actual test) self.assertTrue( CourseInstructorRole(self.course_key).has_user(self.user), "Didn't add creator as instructor.") users = copy.copy(self.users) # doesn't use role.users_with_role b/c it's verifying the roles.py behavior user_by_role = {} # add the misc users to the course in different groups for role in [ CourseInstructorRole, CourseStaffRole, OrgStaffRole, OrgInstructorRole ]: user_by_role[role] = [] # Org-based roles are created via org name, rather than course_key if (role is OrgStaffRole) or (role is OrgInstructorRole): group = role(self.course_key.org) else: group = role(self.course_key) # NOTE: this loop breaks the roles.py abstraction by purposely assigning # users to one of each possible groupname in order to test that has_course_access # and remove_user work user = users.pop() group.add_users(user) user_by_role[role].append(user) self.assertTrue(has_course_access(user, self.course_key), "{} does not have access".format(user)) course_team_url = reverse_course_url('course_team_handler', self.course_key) response = self.client.get_html(course_team_url) for role in [CourseInstructorRole, CourseStaffRole ]: # Global and org-based roles don't appear on this page for user in user_by_role[role]: self.assertContains(response, user.email) # test copying course permissions copy_course_key = SlashSeparatedCourseKey('copyu', 'copydept.mycourse', 'myrun') for role in [ CourseInstructorRole, CourseStaffRole, OrgStaffRole, OrgInstructorRole ]: if (role is OrgStaffRole) or (role is OrgInstructorRole): auth.add_users(self.user, role(copy_course_key.org), *role(self.course_key.org).users_with_role()) else: auth.add_users(self.user, role(copy_course_key), *role(self.course_key).users_with_role()) # verify access in copy course and verify that removal from source course w/ the various # groupnames works for role in [ CourseInstructorRole, CourseStaffRole, OrgStaffRole, OrgInstructorRole ]: for user in user_by_role[role]: # forcefully decache the groups: premise is that any real request will not have # multiple objects repr the same user but this test somehow uses different instance # in above add_users call if hasattr(user, '_roles'): del user._roles self.assertTrue(has_course_access(user, copy_course_key), "{} no copy access".format(user)) if (role is OrgStaffRole) or (role is OrgInstructorRole): auth.remove_users(self.user, role(self.course_key.org), user) else: auth.remove_users(self.user, role(self.course_key), user) self.assertFalse(has_course_access(user, self.course_key), "{} remove didn't work".format(user))
class AuthTestCase(ContentStoreTestCase): """Check that various permissions-related things work""" CREATE_USER = False ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache'] def setUp(self): super(AuthTestCase, self).setUp() self.email = '*****@*****.**' self.pw = 'xyz' self.username = '******' self.client = AjaxEnabledTestClient() # clear the cache so ratelimiting won't affect these tests cache.clear() def check_page_get(self, url, expected): resp = self.client.get_html(url) self.assertEqual(resp.status_code, expected) return resp def test_private_pages_auth(self): """Make sure pages that do require login work.""" auth_pages = ('/home/', ) # These are pages that should just load when the user is logged in # (no data needed) simple_auth_pages = ('/home/', ) # need an activated user self.create_account(self.username, self.email, self.pw) self.activate_user(self.email) # Create a new session self.client = AjaxEnabledTestClient() # Not logged in. Should redirect to login. print('Not logged in') for page in auth_pages: print(u"Checking '{0}'".format(page)) self.check_page_get(page, expected=302) # Logged in should work. self.login(self.email, self.pw) print('Logged in') for page in simple_auth_pages: print(u"Checking '{0}'".format(page)) self.check_page_get(page, expected=200) @override_settings(SESSION_INACTIVITY_TIMEOUT_IN_SECONDS=1) def test_inactive_session_timeout(self): """ Verify that an inactive session times out and redirects to the login page """ self.create_account(self.username, self.email, self.pw) self.activate_user(self.email) self.login(self.email, self.pw) # make sure we can access courseware immediately course_url = '/home/' resp = self.client.get_html(course_url) self.assertEqual(resp.status_code, 200) # then wait a bit and see if we get timed out time.sleep(2) resp = self.client.get_html(course_url) # re-request, and we should get a redirect to login page self.assertRedirects(resp, settings.LOGIN_URL + '?next=/home/', target_status_code=302) @data((True, 'assertContains'), (False, 'assertNotContains')) @unpack def test_signin_and_signup_buttons_index_page(self, allow_account_creation, assertion_method_name): """ Navigate to the home page and check the Sign Up button is hidden when ALLOW_PUBLIC_ACCOUNT_CREATION flag is turned off, and not when it is turned on. The Sign In button should always appear. """ with mock.patch.dict( settings.FEATURES, {"ALLOW_PUBLIC_ACCOUNT_CREATION": allow_account_creation}): response = self.client.get(reverse('homepage')) assertion_method = getattr(self, assertion_method_name) assertion_method( response, u'<a class="action action-signup" href="{}/register?next=http%3A%2F%2Ftestserver%2F">Sign Up</a>' .format( # pylint: disable=line-too-long settings.LMS_ROOT_URL)) self.assertContains( response, u'<a class="action action-signin" href="/signin_redirect_to_lms?next=http%3A%2F%2Ftestserver%2F">Sign In</a>' # pylint: disable=line-too-long )
class AuthTestCase(ContentStoreTestCase): """Check that various permissions-related things work""" CREATE_USER = False ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache'] def setUp(self): super(AuthTestCase, self).setUp() self.email = '*****@*****.**' self.pw = 'xyz' self.username = '******' self.client = AjaxEnabledTestClient() # clear the cache so ratelimiting won't affect these tests cache.clear() def check_page_get(self, url, expected): resp = self.client.get_html(url) self.assertEqual(resp.status_code, expected) return resp def test_public_pages_load(self): """Make sure pages that don't require login load without error.""" pages = ( reverse('login'), reverse('signup'), ) for page in pages: print("Checking '{0}'".format(page)) self.check_page_get(page, 200) def test_create_account_errors(self): # No post data -- should fail resp = self.client.post('/create_account', {}) self.assertEqual(resp.status_code, 400) data = parse_json(resp) self.assertEqual(data['success'], False) def test_create_account(self): self.create_account(self.username, self.email, self.pw) self.activate_user(self.email) def test_create_account_username_already_exists(self): User.objects.create_user(self.username, self.email, self.pw) resp = self._create_account(self.username, "*****@*****.**", "password") # we have a constraint on unique usernames, so this should fail self.assertEqual(resp.status_code, 400) def test_create_account_pw_already_exists(self): User.objects.create_user(self.username, self.email, self.pw) resp = self._create_account("abcdef", "*****@*****.**", self.pw) # we can have two users with the same password, so this should succeed self.assertEqual(resp.status_code, 200) def test_login(self): self.create_account(self.username, self.email, self.pw) # Not activated yet. Login should fail. resp = self._login(self.email, self.pw) data = parse_json(resp) self.assertFalse(data['success']) self.activate_user(self.email) # Now login should work self.login(self.email, self.pw) def test_login_ratelimited(self): # try logging in 30 times, the default limit in the number of failed # login attempts in one 5 minute period before the rate gets limited for i in xrange(30): resp = self._login(self.email, 'wrong_password{0}'.format(i)) self.assertEqual(resp.status_code, 200) resp = self._login(self.email, 'wrong_password') self.assertEqual(resp.status_code, 200) data = parse_json(resp) self.assertFalse(data['success']) self.assertIn('Too many failed login attempts.', data['value']) @override_settings(MAX_FAILED_LOGIN_ATTEMPTS_ALLOWED=3) @override_settings(MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS=2) def test_excessive_login_failures(self): # try logging in 3 times, the account should get locked for 3 seconds # note we want to keep the lockout time short, so we don't slow down the tests with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MAX_FAILED_LOGIN_ATTEMPTS': True}): self.create_account(self.username, self.email, self.pw) self.activate_user(self.email) for i in xrange(3): resp = self._login(self.email, 'wrong_password{0}'.format(i)) self.assertEqual(resp.status_code, 200) data = parse_json(resp) self.assertFalse(data['success']) self.assertIn( 'Email or password is incorrect.', data['value'] ) # now the account should be locked resp = self._login(self.email, 'wrong_password') self.assertEqual(resp.status_code, 200) data = parse_json(resp) self.assertFalse(data['success']) self.assertIn( 'This account has been temporarily locked due to excessive login failures.', data['value'] ) with freeze_time('2100-01-01'): self.login(self.email, self.pw) # make sure the failed attempt counter gets reset on successful login resp = self._login(self.email, 'wrong_password') self.assertEqual(resp.status_code, 200) data = parse_json(resp) self.assertFalse(data['success']) # account should not be locked out after just one attempt self.login(self.email, self.pw) # do one more login when there is no bad login counter row at all in the database to # test the "ObjectNotFound" case self.login(self.email, self.pw) def test_login_link_on_activation_age(self): self.create_account(self.username, self.email, self.pw) # we want to test the rendering of the activation page when the user isn't logged in self.client.logout() resp = self._activate_user(self.email) self.assertEqual(resp.status_code, 200) # check the the HTML has links to the right login page. Note that this is merely a content # check and thus could be fragile should the wording change on this page expected = 'You can now <a href="' + reverse('login') + '">sign in</a>.' self.assertIn(expected, resp.content.decode('utf-8')) def test_private_pages_auth(self): """Make sure pages that do require login work.""" auth_pages = ( '/home/', ) # These are pages that should just load when the user is logged in # (no data needed) simple_auth_pages = ( '/home/', ) # need an activated user self.test_create_account() # Create a new session self.client = AjaxEnabledTestClient() # Not logged in. Should redirect to login. print('Not logged in') for page in auth_pages: print("Checking '{0}'".format(page)) self.check_page_get(page, expected=302) # Logged in should work. self.login(self.email, self.pw) print('Logged in') for page in simple_auth_pages: print("Checking '{0}'".format(page)) self.check_page_get(page, expected=200) def test_index_auth(self): # not logged in. Should return a redirect. resp = self.client.get_html('/home/') self.assertEqual(resp.status_code, 302) # Logged in should work. @override_settings(SESSION_INACTIVITY_TIMEOUT_IN_SECONDS=1) def test_inactive_session_timeout(self): """ Verify that an inactive session times out and redirects to the login page """ self.create_account(self.username, self.email, self.pw) self.activate_user(self.email) self.login(self.email, self.pw) # make sure we can access courseware immediately course_url = '/home/' resp = self.client.get_html(course_url) self.assertEquals(resp.status_code, 200) # then wait a bit and see if we get timed out time.sleep(2) resp = self.client.get_html(course_url) # re-request, and we should get a redirect to login page self.assertRedirects(resp, settings.LOGIN_REDIRECT_URL + '?next=/home/') @mock.patch.dict(settings.FEATURES, {"ALLOW_PUBLIC_ACCOUNT_CREATION": False}) def test_signup_button_index_page(self): """ Navigate to the home page and check the Sign Up button is hidden when ALLOW_PUBLIC_ACCOUNT_CREATION flag is turned off """ response = self.client.get(reverse('homepage')) self.assertNotIn('<a class="action action-signup" href="/signup">Sign Up</a>', response.content) @mock.patch.dict(settings.FEATURES, {"ALLOW_PUBLIC_ACCOUNT_CREATION": False}) def test_signup_button_login_page(self): """ Navigate to the login page and check the Sign Up button is hidden when ALLOW_PUBLIC_ACCOUNT_CREATION flag is turned off """ response = self.client.get(reverse('login')) self.assertNotIn('<a class="action action-signup" href="/signup">Sign Up</a>', response.content) @mock.patch.dict(settings.FEATURES, {"ALLOW_PUBLIC_ACCOUNT_CREATION": False}) def test_signup_link_login_page(self): """ Navigate to the login page and check the Sign Up link is hidden when ALLOW_PUBLIC_ACCOUNT_CREATION flag is turned off """ response = self.client.get(reverse('login')) self.assertNotIn('<a href="/signup" class="action action-signin">Don't have a Studio Account? Sign up!</a>', response.content)
class InternationalizationTest(ModuleStoreTestCase): """ Tests to validate Internationalization. """ def setUp(self): """ These tests need a user in the DB so that the django Test Client can log them in. They inherit from the ModuleStoreTestCase class so that the mongodb collection will be cleared out before each test case execution and deleted afterwards. """ self.uname = 'testuser' self.email = '*****@*****.**' self.password = '******' # Create the use so we can log them in. self.user = User.objects.create_user(self.uname, self.email, self.password) # Note that we do not actually need to do anything # for registration if we directly mark them active. self.user.is_active = True # Staff has access to view all courses self.user.is_staff = True self.user.save() self.course_data = { 'org': 'MITx', 'number': '999', 'display_name': 'Robot Super Course', } def test_course_plain_english(self): """Test viewing the index page with no courses""" self.client = AjaxEnabledTestClient() self.client.login(username=self.uname, password=self.password) resp = self.client.get_html('/course') self.assertContains(resp, '<h1 class="page-header">My Courses</h1>', status_code=200, html=True) def test_course_explicit_english(self): """Test viewing the index page with no courses""" self.client = AjaxEnabledTestClient() self.client.login(username=self.uname, password=self.password) resp = self.client.get_html('/course', {}, HTTP_ACCEPT_LANGUAGE='en' ) self.assertContains(resp, '<h1 class="page-header">My Courses</h1>', status_code=200, html=True) # **** # NOTE: # **** # # This test will break when we replace this fake 'test' language # with actual Esperanto. This test will need to be updated with # actual Esperanto at that time. # Test temporarily disable since it depends on creation of dummy strings @skip def test_course_with_accents(self): """Test viewing the index page with no courses""" self.client = AjaxEnabledTestClient() self.client.login(username=self.uname, password=self.password) resp = self.client.get_html( '/course', {}, HTTP_ACCEPT_LANGUAGE='eo' ) TEST_STRING = ( u'<h1 class="title-1">' u'My \xc7\xf6\xfcrs\xe9s L#' u'</h1>' ) self.assertContains(resp, TEST_STRING, status_code=200, html=True)
class AuthTestCase(ContentStoreTestCase): """Check that various permissions-related things work""" def setUp(self): self.email = '*****@*****.**' self.pw = 'xyz' self.username = '******' self.client = AjaxEnabledTestClient() # clear the cache so ratelimiting won't affect these tests cache.clear() def check_page_get(self, url, expected): resp = self.client.get_html(url) self.assertEqual(resp.status_code, expected) return resp def test_public_pages_load(self): """Make sure pages that don't require login load without error.""" pages = ( reverse('login'), reverse('signup'), ) for page in pages: print("Checking '{0}'".format(page)) self.check_page_get(page, 200) def test_create_account_errors(self): # No post data -- should fail resp = self.client.post('/create_account', {}) self.assertEqual(resp.status_code, 200) data = parse_json(resp) self.assertEqual(data['success'], False) def test_create_account(self): self.create_account(self.username, self.email, self.pw) self.activate_user(self.email) def test_login(self): self.create_account(self.username, self.email, self.pw) # Not activated yet. Login should fail. resp = self._login(self.email, self.pw) data = parse_json(resp) self.assertFalse(data['success']) self.activate_user(self.email) # Now login should work self.login(self.email, self.pw) def test_login_ratelimited(self): # try logging in 30 times, the default limit in the number of failed # login attempts in one 5 minute period before the rate gets limited for i in xrange(30): resp = self._login(self.email, 'wrong_password{0}'.format(i)) self.assertEqual(resp.status_code, 200) resp = self._login(self.email, 'wrong_password') self.assertEqual(resp.status_code, 200) data = parse_json(resp) self.assertFalse(data['success']) self.assertIn('Too many failed login attempts.', data['value']) def test_login_link_on_activation_age(self): self.create_account(self.username, self.email, self.pw) # we want to test the rendering of the activation page when the user isn't logged in self.client.logout() resp = self._activate_user(self.email) self.assertEqual(resp.status_code, 200) # check the the HTML has links to the right login page. Note that this is merely a content # check and thus could be fragile should the wording change on this page expected = 'You can now <a href="' + reverse('login') + '">login</a>.' self.assertIn(expected, resp.content) def test_private_pages_auth(self): """Make sure pages that do require login work.""" auth_pages = ( '/course', ) # These are pages that should just load when the user is logged in # (no data needed) simple_auth_pages = ( '/course', ) # need an activated user self.test_create_account() # Create a new session self.client = AjaxEnabledTestClient() # Not logged in. Should redirect to login. print('Not logged in') for page in auth_pages: print("Checking '{0}'".format(page)) self.check_page_get(page, expected=302) # Logged in should work. self.login(self.email, self.pw) print('Logged in') for page in simple_auth_pages: print("Checking '{0}'".format(page)) self.check_page_get(page, expected=200) def test_index_auth(self): # not logged in. Should return a redirect. resp = self.client.get_html('/course') self.assertEqual(resp.status_code, 302)
class TestCourseAccess(ModuleStoreTestCase): """ Course-based access (as opposed to access of a non-course xblock) """ def setUp(self): """ Create a staff user and log them in (creating the client). Create a pool of users w/o granting them any permissions """ super(TestCourseAccess, self).setUp() uname = 'testuser' email = '*****@*****.**' password = '******' # Create the use so we can log them in. self.user = User.objects.create_user(uname, email, password) # Note that we do not actually need to do anything # for registration if we directly mark them active. self.user.is_active = True # Staff has access to view all courses self.user.is_staff = True self.user.save() self.client = AjaxEnabledTestClient() self.client.login(username=uname, password=password) # create a course via the view handler which has a different strategy for permissions than the factory self.course_location = Location(['i4x', 'myu', 'mydept.mycourse', 'course', 'myrun']) self.course_locator = loc_mapper().translate_location( self.course_location.course_id, self.course_location, False, True ) self.client.ajax_post( self.course_locator.url_reverse('course'), { 'org': self.course_location.org, 'number': self.course_location.course, 'display_name': 'My favorite course', 'run': self.course_location.name, } ) self.users = self._create_users() def _create_users(self): """ Create 8 users and return them """ users = [] for i in range(8): username = "******".format(i) email = "test+user{}@edx.org".format(i) user = User.objects.create_user(username, email, 'foo') user.is_active = True user.save() users.append(user) return users def tearDown(self): """ Reverse the setup """ self.client.logout() ModuleStoreTestCase.tearDown(self) def test_get_all_users(self): """ Test getting all authors for a course where their permissions run the gamut of allowed group types. """ # first check the course creator.has explicit access (don't use has_access as is_staff # will trump the actual test) self.assertTrue( CourseInstructorRole(self.course_locator).has_user(self.user), "Didn't add creator as instructor." ) users = copy.copy(self.users) # doesn't use role.users_with_role b/c it's verifying the roles.py behavior user_by_role = {} # add the misc users to the course in different groups for role in [CourseInstructorRole, CourseStaffRole]: user_by_role[role] = [] # pylint: disable=protected-access groupnames = role(self.course_locator)._group_names self.assertGreater(len(groupnames), 1, "Only 0 or 1 groupname for {}".format(role.ROLE)) # NOTE: this loop breaks the roles.py abstraction by purposely assigning # users to one of each possible groupname in order to test that has_course_access # and remove_user work for groupname in groupnames: group, _ = Group.objects.get_or_create(name=groupname) user = users.pop() user_by_role[role].append(user) user.groups.add(group) user.save() self.assertTrue(has_course_access(user, self.course_locator), "{} does not have access".format(user)) self.assertTrue(has_course_access(user, self.course_location), "{} does not have access".format(user)) response = self.client.get_html(self.course_locator.url_reverse('course_team')) for role in [CourseInstructorRole, CourseStaffRole]: for user in user_by_role[role]: self.assertContains(response, user.email) # test copying course permissions copy_course_location = Location(['i4x', 'copyu', 'copydept.mycourse', 'course', 'myrun']) copy_course_locator = loc_mapper().translate_location( copy_course_location.course_id, copy_course_location, False, True ) for role in [CourseInstructorRole, CourseStaffRole]: auth.add_users( self.user, role(copy_course_locator), *role(self.course_locator).users_with_role() ) # verify access in copy course and verify that removal from source course w/ the various # groupnames works for role in [CourseInstructorRole, CourseStaffRole]: for user in user_by_role[role]: # forcefully decache the groups: premise is that any real request will not have # multiple objects repr the same user but this test somehow uses different instance # in above add_users call if hasattr(user, '_groups'): del user._groups self.assertTrue(has_course_access(user, copy_course_locator), "{} no copy access".format(user)) self.assertTrue(has_course_access(user, copy_course_location), "{} no copy access".format(user)) auth.remove_users(self.user, role(self.course_locator), user) self.assertFalse(has_course_access(user, self.course_locator), "{} remove didn't work".format(user))
class TestCourseAccess(ModuleStoreTestCase): """ Course-based access (as opposed to access of a non-course xblock) """ def setUp(self): """ Create a staff user and log them in (creating the client). Create a pool of users w/o granting them any permissions """ super(TestCourseAccess, self).setUp() uname = 'testuser' email = '*****@*****.**' password = '******' # Create the use so we can log them in. self.user = User.objects.create_user(uname, email, password) # Note that we do not actually need to do anything # for registration if we directly mark them active. self.user.is_active = True # Staff has access to view all courses self.user.is_staff = True self.user.save() self.client = AjaxEnabledTestClient() self.client.login(username=uname, password=password) # create a course via the view handler which has a different strategy for permissions than the factory self.course_location = Location(['i4x', 'myu', 'mydept.mycourse', 'course', 'myrun']) self.course_locator = loc_mapper().translate_location( self.course_location.course_id, self.course_location, False, True ) self.client.ajax_post( self.course_locator.url_reverse('course'), { 'org': self.course_location.org, 'number': self.course_location.course, 'display_name': 'My favorite course', 'run': self.course_location.name, } ) self.users = self._create_users() def _create_users(self): """ Create 8 users and return them """ users = [] for i in range(8): username = "******".format(i) email = "test+user{}@edx.org".format(i) user = User.objects.create_user(username, email, 'foo') user.is_active = True user.save() users.append(user) return users def tearDown(self): """ Reverse the setup """ self.client.logout() ModuleStoreTestCase.tearDown(self) def test_get_all_users(self): """ Test getting all authors for a course where their permissions run the gamut of allowed group types. """ # first check the groupname for the course creator. self.assertTrue( self.user.groups.filter( name="{}_{}".format(INSTRUCTOR_ROLE_NAME, self.course_locator.course_id) ).exists(), "Didn't add creator as instructor." ) users = copy.copy(self.users) user_by_role = {} # add the misc users to the course in different groups for role in [INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME]: user_by_role[role] = [] groupnames, _ = authz.get_all_course_role_groupnames(self.course_locator, role) for groupname in groupnames: group, _ = Group.objects.get_or_create(name=groupname) user = users.pop() user_by_role[role].append(user) user.groups.add(group) user.save() self.assertTrue(has_access(user, self.course_locator), "{} does not have access".format(user)) self.assertTrue(has_access(user, self.course_location), "{} does not have access".format(user)) response = self.client.get_html(self.course_locator.url_reverse('course_team')) for role in [INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME]: for user in user_by_role[role]: self.assertContains(response, user.email) # test copying course permissions copy_course_location = Location(['i4x', 'copyu', 'copydept.mycourse', 'course', 'myrun']) copy_course_locator = loc_mapper().translate_location( copy_course_location.course_id, copy_course_location, False, True ) # pylint: disable=protected-access authz._copy_course_group(self.course_locator, copy_course_locator) for role in [INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME]: for user in user_by_role[role]: self.assertTrue(has_access(user, copy_course_locator), "{} no copy access".format(user)) self.assertTrue(has_access(user, copy_course_location), "{} no copy access".format(user))
class TestCourseListing(ModuleStoreTestCase): """ Unit tests for getting the list of courses for a logged in user """ def setUp(self): """ Add a user and a course """ super(TestCourseListing, self).setUp() # create and log in a staff user. self.user = UserFactory(is_staff=True) # pylint: disable=no-member self.factory = RequestFactory() self.client = AjaxEnabledTestClient() self.client.login(username=self.user.username, password='******') def _create_course_with_access_groups(self, course_location, group_name_format='group_name_with_dots', user=None): """ Create dummy course with 'CourseFactory' and role (instructor/staff) groups with provided group_name_format """ course_locator = loc_mapper().translate_location( course_location.course_id, course_location, False, True ) course = CourseFactory.create( org=course_location.org, number=course_location.course, display_name=course_location.name ) for role in [CourseInstructorRole, CourseStaffRole]: # pylint: disable=protected-access groupnames = role(course_locator)._group_names if group_name_format == 'group_name_with_course_name_only': # Create role (instructor/staff) groups with course_name only: 'instructor_run' group, __ = Group.objects.get_or_create(name=groupnames[2]) elif group_name_format == 'group_name_with_slashes': # Create role (instructor/staff) groups with format: 'instructor_edX/Course/Run' # Since "Group.objects.get_or_create(name=groupnames[1])" would have made group with lowercase name # so manually create group name of old type if role == CourseInstructorRole: group, __ = Group.objects.get_or_create(name=u'{}_{}'.format('instructor', course_location.course_id)) else: group, __ = Group.objects.get_or_create(name=u'{}_{}'.format('staff', course_location.course_id)) else: # Create role (instructor/staff) groups with format: 'instructor_edx.course.run' group, __ = Group.objects.get_or_create(name=groupnames[0]) if user is not None: user.groups.add(group) return course def tearDown(self): """ Reverse the setup """ self.client.logout() ModuleStoreTestCase.tearDown(self) def test_get_course_list(self): """ Test getting courses with new access group format e.g. 'instructor_edx.course.run' """ request = self.factory.get('/course') request.user = self.user course_location = Location(['i4x', 'Org1', 'Course1', 'course', 'Run1']) self._create_course_with_access_groups(course_location, 'group_name_with_dots', self.user) # get courses through iterating all courses courses_list = _accessible_courses_list(request) self.assertEqual(len(courses_list), 1) # get courses by reversing group name formats courses_list_by_groups = _accessible_courses_list_from_groups(request) self.assertEqual(len(courses_list_by_groups), 1) # check both course lists have same courses self.assertEqual(courses_list, courses_list_by_groups) def test_get_course_list_with_old_group_formats(self): """ Test getting all courses with old course role (instructor/staff) groups """ request = self.factory.get('/course') request.user = self.user # create a course with new groups name format e.g. 'instructor_edx.course.run' course_location = Location(['i4x', 'Org_1', 'Course_1', 'course', 'Run_1']) self._create_course_with_access_groups(course_location, 'group_name_with_dots', self.user) # create a course with old groups name format e.g. 'instructor_edX/Course/Run' old_course_location = Location(['i4x', 'Org_2', 'Course_2', 'course', 'Run_2']) self._create_course_with_access_groups(old_course_location, 'group_name_with_slashes', self.user) # get courses through iterating all courses courses_list = _accessible_courses_list(request) self.assertEqual(len(courses_list), 2) # get courses by reversing groups name courses_list_by_groups = _accessible_courses_list_from_groups(request) self.assertEqual(len(courses_list_by_groups), 2) # create a new course with older group name format (with dots in names) e.g. 'instructor_edX/Course.name/Run.1' old_course_location = Location(['i4x', 'Org.Foo.Bar', 'Course.number', 'course', 'Run.name']) self._create_course_with_access_groups(old_course_location, 'group_name_with_slashes', self.user) # get courses through iterating all courses courses_list = _accessible_courses_list(request) self.assertEqual(len(courses_list), 3) # get courses by reversing group name formats courses_list_by_groups = _accessible_courses_list_from_groups(request) self.assertEqual(len(courses_list_by_groups), 3) # create a new course with older group name format e.g. 'instructor_Run' old_course_location = Location(['i4x', 'Org_3', 'Course_3', 'course', 'Run_3']) self._create_course_with_access_groups(old_course_location, 'group_name_with_course_name_only', self.user) # get courses through iterating all courses courses_list = _accessible_courses_list(request) self.assertEqual(len(courses_list), 4) # should raise an exception for getting courses with older format of access group by reversing django groups with self.assertRaises(ItemNotFoundError): courses_list_by_groups = _accessible_courses_list_from_groups(request) def test_get_course_list_with_invalid_course_location(self): """ Test getting courses with invalid course location (course deleted from modulestore but location exists in loc_mapper). """ request = self.factory.get('/course') request.user = self.user course_location = Location('i4x', 'Org', 'Course', 'course', 'Run') self._create_course_with_access_groups(course_location, 'group_name_with_dots', self.user) # get courses through iterating all courses courses_list = _accessible_courses_list(request) self.assertEqual(len(courses_list), 1) # get courses by reversing group name formats courses_list_by_groups = _accessible_courses_list_from_groups(request) self.assertEqual(len(courses_list_by_groups), 1) # check both course lists have same courses self.assertEqual(courses_list, courses_list_by_groups) # now delete this course and re-add user to instructor group of this course delete_course_and_groups(course_location.course_id, commit=True) course_locator = loc_mapper().translate_location(course_location.course_id, course_location) instructor_group_name = CourseInstructorRole(course_locator)._group_names[0] # pylint: disable=protected-access group, __ = Group.objects.get_or_create(name=instructor_group_name) self.user.groups.add(group) # test that get courses through iterating all courses now returns no course courses_list = _accessible_courses_list(request) self.assertEqual(len(courses_list), 0) # now test that get courses by reversing group name formats gives 'ItemNotFoundError' with self.assertRaises(ItemNotFoundError): _accessible_courses_list_from_groups(request) def test_course_listing_performance(self): """ Create large number of courses and give access of some of these courses to the user and compare the time to fetch accessible courses for the user through traversing all courses and reversing django groups """ # create and log in a non-staff user self.user = UserFactory() request = self.factory.get('/course') request.user = self.user self.client.login(username=self.user.username, password='******') # create list of random course numbers which will be accessible to the user user_course_ids = random.sample(range(TOTAL_COURSES_COUNT), USER_COURSES_COUNT) # create courses and assign those to the user which have their number in user_course_ids for number in range(TOTAL_COURSES_COUNT): org = 'Org{0}'.format(number) course = 'Course{0}'.format(number) run = 'Run{0}'.format(number) course_location = Location(['i4x', org, course, 'course', run]) if number in user_course_ids: self._create_course_with_access_groups(course_location, 'group_name_with_dots', self.user) else: self._create_course_with_access_groups(course_location, 'group_name_with_dots') # time the get courses by iterating through all courses with Timer() as iteration_over_courses_time_1: courses_list = _accessible_courses_list(request) self.assertEqual(len(courses_list), USER_COURSES_COUNT) # time again the get courses by iterating through all courses with Timer() as iteration_over_courses_time_2: courses_list = _accessible_courses_list(request) self.assertEqual(len(courses_list), USER_COURSES_COUNT) # time the get courses by reversing django groups with Timer() as iteration_over_groups_time_1: courses_list = _accessible_courses_list_from_groups(request) self.assertEqual(len(courses_list), USER_COURSES_COUNT) # time again the get courses by reversing django groups with Timer() as iteration_over_groups_time_2: courses_list = _accessible_courses_list_from_groups(request) self.assertEqual(len(courses_list), USER_COURSES_COUNT) # test that the time taken by getting courses through reversing django groups is lower then the time # taken by traversing through all courses (if accessible courses are relatively small) self.assertGreaterEqual(iteration_over_courses_time_1.elapsed, iteration_over_groups_time_1.elapsed) self.assertGreaterEqual(iteration_over_courses_time_2.elapsed, iteration_over_groups_time_2.elapsed) def test_get_course_list_with_same_course_id(self): """ Test getting courses with same id but with different name case. Then try to delete one of them and check that it is properly deleted and other one is accessible """ # create and log in a non-staff user self.user = UserFactory() request = self.factory.get('/course') request.user = self.user self.client.login(username=self.user.username, password='******') course_location_caps = Location(['i4x', 'Org', 'COURSE', 'course', 'Run']) self._create_course_with_access_groups(course_location_caps, 'group_name_with_dots', self.user) # get courses through iterating all courses courses_list = _accessible_courses_list(request) self.assertEqual(len(courses_list), 1) # get courses by reversing group name formats courses_list_by_groups = _accessible_courses_list_from_groups(request) self.assertEqual(len(courses_list_by_groups), 1) # check both course lists have same courses self.assertEqual(courses_list, courses_list_by_groups) # now create another course with same course_id but different name case course_location_camel = Location(['i4x', 'Org', 'Course', 'course', 'Run']) self._create_course_with_access_groups(course_location_camel, 'group_name_with_dots', self.user) # test that get courses through iterating all courses returns both courses courses_list = _accessible_courses_list(request) self.assertEqual(len(courses_list), 2) # test that get courses by reversing group name formats returns only one course courses_list_by_groups = _accessible_courses_list_from_groups(request) self.assertEqual(len(courses_list_by_groups), 1) course_locator = loc_mapper().translate_location(course_location_caps.course_id, course_location_caps) outline_url = course_locator.url_reverse('course/') # now delete first course (course_location_caps) and check that it is no longer accessible delete_course_and_groups(course_location_caps.course_id, commit=True) # add user to this course instructor group since he was removed from that group on course delete instructor_group_name = CourseInstructorRole(course_locator)._group_names[0] # pylint: disable=protected-access group, __ = Group.objects.get_or_create(name=instructor_group_name) self.user.groups.add(group) # test viewing the index page which creates missing courses loc_map entries resp = self.client.get_html('/course') self.assertContains( resp, '<h1 class="page-header">My Courses</h1>', status_code=200, html=True ) # test that get courses through iterating all courses now returns one course courses_list = _accessible_courses_list(request) self.assertEqual(len(courses_list), 1) # test that get courses by reversing group name formats also returns one course courses_list_by_groups = _accessible_courses_list_from_groups(request) self.assertEqual(len(courses_list_by_groups), 1) # now check that deleted course in not accessible response = self.client.get(outline_url, HTTP_ACCEPT='application/json') self.assertEqual(response.status_code, 403) # now check that other course in accessible course_locator = loc_mapper().translate_location(course_location_camel.course_id, course_location_camel) outline_url = course_locator.url_reverse('course/') response = self.client.get(outline_url, HTTP_ACCEPT='application/json') self.assertEqual(response.status_code, 200)