def test_get_course_list_with_invalid_course_location(self): """ Test getting courses with invalid course location (course deleted from modulestore). """ course_key = self.store.make_course_key('Org', 'Course', 'Run') self._create_course_with_access_groups(course_key, self.user) # get courses through iterating all courses courses_list, __ = _accessible_courses_list(self.request) self.assertEqual(len(courses_list), 1) # get courses by reversing group name formats courses_list_by_groups, __ = _accessible_courses_list_from_groups( self.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_key, self.user.id) CourseInstructorRole(course_key).add_users(self.user) # test that get courses through iterating all courses now returns no course courses_list, __ = _accessible_courses_list(self.request) self.assertEqual(len(courses_list), 0)
def test_get_course_list_with_invalid_course_location(self): """ Test getting courses with invalid course location (course deleted from modulestore). """ request = self.factory.get('/course') request.user = self.user course_key = SlashSeparatedCourseKey('Org', 'Course', 'Run') self._create_course_with_access_groups(course_key, 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_key, commit=True) CourseInstructorRole(course_key).add_users(self.user) # 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_get_course_list_with_invalid_course_location(self): """ Test getting courses with invalid course location (course deleted from modulestore). """ course_key = SlashSeparatedCourseKey('Org', 'Course', 'Run') self._create_course_with_access_groups(course_key, self.user) # get courses through iterating all courses courses_list = _accessible_courses_list(self.request) self.assertEqual(len(courses_list), 1) # get courses by reversing group name formats courses_list_by_groups = _accessible_courses_list_from_groups(self.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_key, commit=True) CourseInstructorRole(course_key).add_users(self.user) # test that get courses through iterating all courses now returns no course courses_list = _accessible_courses_list(self.request) self.assertEqual(len(courses_list), 0)
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 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 = SlashSeparatedCourseKey(org, course, run) if number in user_course_ids: self._create_course_with_access_groups(course_location, self.user) else: self._create_course_with_access_groups(course_location) # time the get courses by iterating through all courses with Timer() as iteration_over_courses_time_1: courses_list, __ = _accessible_courses_list(self.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(self.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( self.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( self.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) # Now count the db queries store = modulestore()._get_modulestore_by_type( ModuleStoreEnum.Type.mongo) with check_mongo_calls(USER_COURSES_COUNT): _accessible_courses_list_from_groups(self.request) # TODO: LMS-11220: Document why this takes 6 calls with check_mongo_calls(6): _accessible_courses_list(self.request)
def test_get_course_list_when_ccx(self): """ Assert that courses with CCXLocator are filter in course listing. """ course_location = self.store.make_course_key('Org1', 'Course1', 'Run1') self._create_course_with_access_groups(course_location, self.user) # get courses through iterating all courses courses_list, __ = _accessible_courses_list(self.request) self.assertEqual(len(courses_list), 1) # get courses by reversing group name formats courses_list_by_groups, __ = _accessible_courses_list_from_groups(self.request) self.assertEqual(len(courses_list_by_groups), 1) # assert no course in listing with ccx id ccx_course = Mock() course_key = CourseKey.from_string('course-v1:FakeOrg+CN1+CR-FALLNEVER1') ccx_course.id = CCXLocator.from_course_locator(course_key, u"1") with patch( 'xmodule.modulestore.mixed.MixedModuleStore.get_course', return_value=ccx_course ), patch( 'xmodule.modulestore.mixed.MixedModuleStore.get_courses', Mock(return_value=[ccx_course]) ): courses_list, __ = _accessible_courses_list_from_groups(self.request) self.assertEqual(len(courses_list), 0) courses_list, __ = _accessible_courses_list(self.request) self.assertEqual(len(courses_list), 0)
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 """ request = self.factory.get('/course') request.user = self.user 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 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)
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_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_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 = SlashSeparatedCourseKey('Org', 'COURSE', 'Run') self._create_course_with_access_groups(course_location_caps, 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 = SlashSeparatedCourseKey('Org', 'Course', 'Run') self._create_course_with_access_groups(course_location_camel, 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 both courses courses_list_by_groups = _accessible_courses_list_from_groups(request) self.assertEqual(len(courses_list_by_groups), 2) # now delete first course (course_location_caps) and check that it is no longer accessible delete_course_and_groups(course_location_caps, commit=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 is not accessible outline_url = reverse_course_url('course_handler', course_location_caps) response = self.client.get(outline_url, HTTP_ACCEPT='application/json') self.assertEqual(response.status_code, 403) # now check that other course is accessible outline_url = reverse_course_url('course_handler', course_location_camel) response = self.client.get(outline_url, HTTP_ACCEPT='application/json') self.assertEqual(response.status_code, 200)
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 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 = SlashSeparatedCourseKey(org, course, run) if number in user_course_ids: self._create_course_with_access_groups(course_location, self.user) else: self._create_course_with_access_groups(course_location) # time the get courses by iterating through all courses with Timer() as iteration_over_courses_time_1: courses_list, __ = _accessible_courses_list(self.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(self.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(self.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(self.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) # Now count the db queries with check_mongo_calls(USER_COURSES_COUNT): _accessible_courses_list_from_groups(self.request) # Calls: # 1) query old mongo # 2) get_more on old mongo # 3) query split (but no courses so no fetching of data) with check_mongo_calls(3): _accessible_courses_list(self.request)
def test_get_course_list_with_invalid_course_location(self, store): """ Test getting courses with invalid course location (course deleted from modulestore). """ with self.store.default_store(store): course_key = self.store.make_course_key('Org', 'Course', 'Run') self._create_course_with_access_groups(course_key, self.user, store) # get courses through iterating all courses courses_list, __ = _accessible_courses_list(self.request) self.assertEqual(len(courses_list), 1) courses_summary_list, __ = _accessible_courses_summary_list( self.request) # Verify fetched accessible courses list is a list of CourseSummery instances and only one course # is returned self.assertTrue( all( isinstance(course, CourseSummary) for course in courses_summary_list)) self.assertEqual(len(courses_summary_list), 1) # get courses by reversing group name formats courses_list_by_groups, __ = _accessible_courses_list_from_groups( self.request) self.assertEqual(len(courses_list_by_groups), 1) # check 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_key, self.user.id) CourseInstructorRole(course_key).add_users(self.user) # Get courses through iterating all courses courses_list, __ = _accessible_courses_list(self.request) # Get course summaries by iterating all courses courses_summary_list, __ = _accessible_courses_summary_list( self.request) # Get courses by reversing group name formats courses_list_by_groups, __ = _accessible_courses_list_from_groups( self.request) # Test that course list returns no course self.assertEqual([ len(courses_list), len(courses_list_by_groups), len(courses_summary_list) ], [0, 0, 0])
def test_course_listing_performance(self, store, courses_list_from_group_calls, courses_list_calls): """ 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 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 with self.store.default_store(store): for number in range(TOTAL_COURSES_COUNT): org = 'Org{0}'.format(number) course = 'Course{0}'.format(number) run = 'Run{0}'.format(number) course_location = self.store.make_course_key(org, course, run) if number in user_course_ids: self._create_course_with_access_groups(course_location, self.user, store=store) else: self._create_course_with_access_groups(course_location, store=store) # time the get courses by iterating through all courses with Timer() as iteration_over_courses_time_1: courses_list, __ = _accessible_courses_list(self.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(self.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(self.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(self.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) # Now count the db queries with check_mongo_calls(courses_list_from_group_calls): _accessible_courses_list_from_groups(self.request) with check_mongo_calls(courses_list_calls): _accessible_courses_list(self.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 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 = SlashSeparatedCourseKey(org, course, run) if number in user_course_ids: self._create_course_with_access_groups(course_location, self.user) else: self._create_course_with_access_groups(course_location) # time the get courses by iterating through all courses with Timer() as iteration_over_courses_time_1: courses_list = _accessible_courses_list(self.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(self.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(self.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(self.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) # Now count the db queries store = modulestore()._get_modulestore_by_type(ModuleStoreEnum.Type.mongo) with check_mongo_calls(store.collection, USER_COURSES_COUNT): courses_list = _accessible_courses_list_from_groups(self.request) with check_mongo_calls(store.collection, 1): courses_list = _accessible_courses_list(self.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 = SlashSeparatedCourseKey(org, course, run) if number in user_course_ids: self._create_course_with_access_groups(course_location, self.user) else: self._create_course_with_access_groups(course_location) # 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 """ course_location_caps = SlashSeparatedCourseKey("Org", "COURSE", "Run") self._create_course_with_access_groups(course_location_caps, self.user) # get courses through iterating all courses courses_list = _accessible_courses_list(self.request) self.assertEqual(len(courses_list), 1) # get courses by reversing group name formats courses_list_by_groups = _accessible_courses_list_from_groups(self.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 = SlashSeparatedCourseKey("Org", "Course", "Run") self._create_course_with_access_groups(course_location_camel, self.user) # test that get courses through iterating all courses returns both courses courses_list = _accessible_courses_list(self.request) self.assertEqual(len(courses_list), 2) # test that get courses by reversing group name formats returns both courses courses_list_by_groups = _accessible_courses_list_from_groups(self.request) self.assertEqual(len(courses_list_by_groups), 2) # now delete first course (course_location_caps) and check that it is no longer accessible delete_course_and_groups(course_location_caps, self.user.id) # test that get courses through iterating all courses now returns one course courses_list = _accessible_courses_list(self.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(self.request) self.assertEqual(len(courses_list_by_groups), 1) # now check that deleted course is not accessible outline_url = reverse_course_url("course_handler", course_location_caps) response = self.client.get(outline_url, HTTP_ACCEPT="application/json") self.assertEqual(response.status_code, 403) # now check that other course is accessible outline_url = reverse_course_url("course_handler", course_location_camel) response = self.client.get(outline_url, HTTP_ACCEPT="application/json") self.assertEqual(response.status_code, 200)
def test_course_listing_org_permissions(self, role): """ Create multiple courses within the same org. Verify that someone with org-wide permissions can access all of them. """ org_course_one = SlashSeparatedCourseKey('AwesomeOrg', 'Course1', 'RunBabyRun') CourseFactory.create( org=org_course_one.org, number=org_course_one.course, run=org_course_one.run ) org_course_two = SlashSeparatedCourseKey('AwesomeOrg', 'Course2', 'RunRunRun') CourseFactory.create( org=org_course_two.org, number=org_course_two.course, run=org_course_two.run ) # Two types of org-wide roles have edit permissions: staff and instructor. We test both role.add_users(self.user) with self.assertRaises(AccessListFallback): _accessible_courses_list_from_groups(self.request) courses_list, __ = _accessible_courses_list(self.request) self.assertEqual(len(courses_list), 2)
def test_errored_course_regular_access(self): """ Test the course list for regular staff when get_course returns an ErrorDescriptor """ GlobalStaff().remove_users(self.user) CourseStaffRole(self.store.make_course_key( 'Non', 'Existent', 'Course')).add_users(self.user) course_key = self.store.make_course_key('Org1', 'Course1', 'Run1') self._create_course_with_access_groups(course_key, self.user) with patch('xmodule.modulestore.mongo.base.MongoKeyValueStore', Mock(side_effect=Exception)): self.assertIsInstance(modulestore().get_course(course_key), ErrorDescriptor) # get courses through iterating all courses courses_list, __ = _accessible_courses_list(self.request) self.assertEqual(courses_list, []) # get courses by reversing group name formats courses_list_by_groups, __ = _accessible_courses_list_from_groups( self.request) self.assertEqual(courses_list_by_groups, []) self.assertEqual(courses_list, courses_list_by_groups)
def test_course_listing_org_permissions(self, role): """ Create multiple courses within the same org. Verify that someone with org-wide permissions can access all of them. """ org_course_one = SlashSeparatedCourseKey('AwesomeOrg', 'Course1', 'RunBabyRun') CourseFactory.create( org=org_course_one.org, number=org_course_one.course, run=org_course_one.run ) org_course_two = SlashSeparatedCourseKey('AwesomeOrg', 'Course2', 'RunRunRun') CourseFactory.create( org=org_course_two.org, number=org_course_two.course, run=org_course_two.run ) # Two types of org-wide roles have edit permissions: staff and instructor. We test both role.add_users(self.user) with self.assertRaises(AccessListFallback): _accessible_courses_list_from_groups(self.request) courses_list = _accessible_courses_list(self.request) self.assertEqual(len(courses_list), 2)
def test_get_course_list_with_invalid_course_location(self, store): """ Test getting courses with invalid course location (course deleted from modulestore). """ with self.store.default_store(store): course_key = self.store.make_course_key('Org', 'Course', 'Run') self._create_course_with_access_groups(course_key, self.user, store) # get courses through iterating all courses courses_list, __ = _accessible_courses_list(self.request) self.assertEqual(len(courses_list), 1) courses_summary_list, __ = _accessible_courses_summary_list(self.request) # Verify fetched accessible courses list is a list of CourseSummery instances and only one course # is returned self.assertTrue(all(isinstance(course, CourseSummary) for course in courses_summary_list)) self.assertEqual(len(courses_summary_list), 1) # get courses by reversing group name formats courses_list_by_groups, __ = _accessible_courses_list_from_groups(self.request) self.assertEqual(len(courses_list_by_groups), 1) # check 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_key, self.user.id) CourseInstructorRole(course_key).add_users(self.user) # Get courses through iterating all courses courses_list, __ = _accessible_courses_list(self.request) # Get course summaries by iterating all courses courses_summary_list, __ = _accessible_courses_summary_list(self.request) # Get courses by reversing group name formats courses_list_by_groups, __ = _accessible_courses_list_from_groups(self.request) # Test that course list returns no course self.assertEqual( [len(courses_list), len(courses_list_by_groups), len(courses_summary_list)], [0, 0, 0] )
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_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_get_course_list(self): """ Test getting courses with new access group format e.g. 'instructor_edx.course.run' """ course_location = SlashSeparatedCourseKey('Org1', 'Course1', 'Run1') self._create_course_with_access_groups(course_location, self.user) # get courses through iterating all courses courses_list, __ = _accessible_courses_list(self.request) self.assertEqual(len(courses_list), 1) # get courses by reversing group name formats courses_list_by_groups, __ = _accessible_courses_list_from_groups(self.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(self): """ Test getting courses with new access group format e.g. 'instructor_edx.course.run' """ course_location = SlashSeparatedCourseKey('Org1', 'Course1', 'Run1') self._create_course_with_access_groups(course_location, self.user) # get courses through iterating all courses courses_list = _accessible_courses_list(self.request) self.assertEqual(len(courses_list), 1) # get courses by reversing group name formats courses_list_by_groups = _accessible_courses_list_from_groups(self.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_errored_course_global_staff(self): """ Test the course list for global staff when get_course returns an ErrorDescriptor """ GlobalStaff().add_users(self.user) course_key = SlashSeparatedCourseKey('Org1', 'Course1', 'Run1') self._create_course_with_access_groups(course_key, self.user) with patch('xmodule.modulestore.mongo.base.MongoKeyValueStore', Mock(side_effect=Exception)): self.assertIsInstance(modulestore().get_course(course_key), ErrorDescriptor) # get courses through iterating all courses courses_list = _accessible_courses_list(self.request) self.assertEqual(courses_list, []) # get courses by reversing group name formats courses_list_by_groups = _accessible_courses_list_from_groups(self.request) self.assertEqual(courses_list_by_groups, [])
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_errored_course_global_staff(self, store, path_to_patch): """ Test the course list for global staff when get_course returns an ErrorDescriptor """ GlobalStaff().add_users(self.user) with self.store.default_store(store): course_key = self.store.make_course_key('Org1', 'Course1', 'Run1') self._create_course_with_access_groups(course_key, self.user, store=store) with patch(path_to_patch, Mock(side_effect=Exception)): self.assertIsInstance(self.store.get_course(course_key), ErrorDescriptor) # get courses through iterating all courses courses_list, __ = _accessible_courses_list(self.request) self.assertEqual(courses_list, []) # get courses by reversing group name formats courses_list_by_groups, __ = _accessible_courses_list_from_groups(self.request) self.assertEqual(courses_list_by_groups, [])
def test_get_course_list(self): """ Test getting courses with new access group format e.g. 'instructor_edx.course.run' """ course_location = self.store.make_course_key("Org1", "Course1", "Run1") self._create_course_with_access_groups(course_location, self.user) # get courses through iterating all courses courses_list, __ = _accessible_courses_list(self.request) self.assertEqual(len(courses_list), 1) courses_summary_list, __ = _accessible_courses_summary_list(self.request) self.assertEqual(len(courses_summary_list), 1) # get courses by reversing group name formats courses_list_by_groups, __ = _accessible_courses_list_from_groups(self.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_errored_course_regular_access(self): """ Test the course list for regular staff when get_course returns an ErrorDescriptor """ GlobalStaff().remove_users(self.user) CourseStaffRole(SlashSeparatedCourseKey("Non", "Existent", "Course")).add_users(self.user) course_key = SlashSeparatedCourseKey("Org1", "Course1", "Run1") self._create_course_with_access_groups(course_key, self.user) with patch("xmodule.modulestore.mongo.base.MongoKeyValueStore", Mock(side_effect=Exception)): self.assertIsInstance(modulestore().get_course(course_key), ErrorDescriptor) # get courses through iterating all courses courses_list = _accessible_courses_list(self.request) self.assertEqual(courses_list, []) # get courses by reversing group name formats courses_list_by_groups = _accessible_courses_list_from_groups(self.request) self.assertEqual(courses_list_by_groups, []) self.assertEqual(courses_list, courses_list_by_groups)
def test_errored_course_regular_access(self): """ Test the course list for regular staff when get_course returns an ErrorDescriptor """ GlobalStaff().remove_users(self.user) CourseStaffRole(self.store.make_course_key('Non', 'Existent', 'Course')).add_users(self.user) course_key = self.store.make_course_key('Org1', 'Course1', 'Run1') self._create_course_with_access_groups(course_key, self.user) with patch('xmodule.modulestore.mongo.base.MongoKeyValueStore', Mock(side_effect=Exception)): self.assertIsInstance(modulestore().get_course(course_key), ErrorDescriptor) # get courses through iterating all courses courses_list, __ = _accessible_courses_list(self.request) self.assertEqual(courses_list, []) # get courses by reversing group name formats courses_list_by_groups, __ = _accessible_courses_list_from_groups(self.request) self.assertEqual(courses_list_by_groups, []) self.assertEqual(courses_list, courses_list_by_groups)
def test_errored_course_global_staff(self): """ Test the course list for global staff when get_course returns an ErrorDescriptor """ GlobalStaff().add_users(self.user) course_key = SlashSeparatedCourseKey('Org1', 'Course1', 'Run1') self._create_course_with_access_groups(course_key, self.user) with patch('xmodule.modulestore.mongo.base.MongoKeyValueStore', Mock(side_effect=Exception)): self.assertIsInstance(modulestore().get_course(course_key), ErrorDescriptor) # get courses through iterating all courses courses_list = _accessible_courses_list(self.request) self.assertEqual(courses_list, []) # get courses by reversing group name formats courses_list_by_groups = _accessible_courses_list_from_groups( self.request) self.assertEqual(courses_list_by_groups, [])
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_errored_course_regular_access(self, store, path_to_patch): """ Test the course list for regular staff when get_course returns an ErrorDescriptor """ GlobalStaff().remove_users(self.user) with self.store.default_store(store): CourseStaffRole(self.store.make_course_key("Non", "Existent", "Course")).add_users(self.user) course_key = self.store.make_course_key("Org1", "Course1", "Run1") self._create_course_with_access_groups(course_key, self.user, store) with patch(path_to_patch, Mock(side_effect=Exception)): self.assertIsInstance(self.store.get_course(course_key), ErrorDescriptor) # get courses through iterating all courses courses_list, __ = _accessible_courses_list(self.request) self.assertEqual(courses_list, []) # get courses by reversing group name formats courses_list_by_groups, __ = _accessible_courses_list_from_groups(self.request) self.assertEqual(courses_list_by_groups, []) self.assertEqual(courses_list, courses_list_by_groups)
def test_courses_list_with_ccx_courses(self): """ Tests that CCX courses are filtered in course listing. """ # Create a course and assign access roles to user. course_location = self.store.make_course_key('Org1', 'Course1', 'Run1') course = self._create_course_with_access_groups(course_location, self.user) # Create a ccx course key and add assign access roles to user. ccx_course_key = CCXLocator.from_course_locator(course.id, '1') self._add_role_access_to_user(self.user, ccx_course_key) # Test that CCX courses are filtered out. courses_list, __ = _accessible_courses_list_from_groups(self.request) self.assertEqual(len(courses_list), 1) self.assertNotIn( ccx_course_key, [course.id for course in courses_list] ) # Get all courses which user has access. instructor_courses = UserBasedRole(self.user, CourseInstructorRole.ROLE).courses_with_role() staff_courses = UserBasedRole(self.user, CourseStaffRole.ROLE).courses_with_role() all_courses = (instructor_courses | staff_courses) # Verify that CCX course exists in access but filtered by `_accessible_courses_list_from_groups`. self.assertIn( ccx_course_key, [access.course_id for access in all_courses] ) # Verify that CCX courses are filtered out while iterating over all courses mocked_ccx_course = Mock(id=ccx_course_key) with patch('xmodule.modulestore.mixed.MixedModuleStore.get_courses', return_value=[mocked_ccx_course]): courses_list, __ = _accessible_courses_list(self.request) self.assertEqual(len(courses_list), 0)
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)