class TestInstructorEnrollmentStudentModule(TestCase): """ Test student module manipulations. """ def setUp(self): super(TestInstructorEnrollmentStudentModule, self).setUp() self.course_key = SlashSeparatedCourseKey("fake", "course", "id") def test_reset_student_attempts(self): user = UserFactory() msk = self.course_key.make_usage_key("dummy", "module") original_state = json.dumps({"attempts": 32, "otherstuff": "alsorobots"}) StudentModule.objects.create( student=user, course_id=self.course_key, module_state_key=msk, state=original_state ) # lambda to reload the module state from the database module = lambda: StudentModule.objects.get(student=user, course_id=self.course_key, module_state_key=msk) self.assertEqual(json.loads(module().state)["attempts"], 32) reset_student_attempts(self.course_key, user, msk) self.assertEqual(json.loads(module().state)["attempts"], 0) def test_delete_student_attempts(self): user = UserFactory() msk = self.course_key.make_usage_key("dummy", "module") original_state = json.dumps({"attempts": 32, "otherstuff": "alsorobots"}) StudentModule.objects.create( student=user, course_id=self.course_key, module_state_key=msk, state=original_state ) self.assertEqual( StudentModule.objects.filter(student=user, course_id=self.course_key, module_state_key=msk).count(), 1 ) reset_student_attempts(self.course_key, user, msk, delete_module=True) self.assertEqual( StudentModule.objects.filter(student=user, course_id=self.course_key, module_state_key=msk).count(), 0 ) def test_delete_submission_scores(self): user = UserFactory() problem_location = self.course_key.make_usage_key("dummy", "module") # Create a student module for the user StudentModule.objects.create( student=user, course_id=self.course_key, module_state_key=problem_location, state=json.dumps({}) ) # Create a submission and score for the student using the submissions API student_item = { "student_id": anonymous_id_for_user(user, self.course_key), "course_id": self.course_key.to_deprecated_string(), "item_id": problem_location.to_deprecated_string(), "item_type": "openassessment", } submission = sub_api.create_submission(student_item, "test answer") sub_api.set_score(submission["uuid"], 1, 2) # Delete student state using the instructor dash reset_student_attempts(self.course_key, user, problem_location, delete_module=True) # Verify that the student's scores have been reset in the submissions API score = sub_api.get_score(student_item) self.assertIs(score, None)
def lms_link_test(self): """ Tests get_lms_link_for_item. """ course_key = SlashSeparatedCourseKey('mitX', '101', 'test') location = course_key.make_usage_key('vertical', 'contacting_us') link = utils.get_lms_link_for_item(location, False) self.assertEquals( link, "//localhost:8000/courses/mitX/101/test/jump_to/i4x://mitX/101/vertical/contacting_us" ) # test preview # for edunext the second parameter is False, its the same test that before # link = utils.get_lms_link_for_item(location, True) # self.assertEquals( # link, # "//localhost:8000/courses/mitX/101/test/jump_to/i4x://mitX/101/vertical/contacting_us" # ) # now test with the course' location location = course_key.make_usage_key('course', 'test') link = utils.get_lms_link_for_item(location) self.assertEquals( link, "//localhost:8000/courses/mitX/101/test/jump_to/i4x://mitX/101/course/test" )
def test_rewrite_reference_list(self): module_store = modulestore() target_course_id = SlashSeparatedCourseKey('testX', 'conditional_copy', 'copy_run') import_from_xml( module_store, self.user.id, 'common/test/data/', ['conditional'], target_course_id=target_course_id ) conditional_module = module_store.get_item( target_course_id.make_usage_key('conditional', 'condone') ) self.assertIsNotNone(conditional_module) different_course_id = SlashSeparatedCourseKey('edX', 'different_course', None) self.assertListEqual( [ target_course_id.make_usage_key('problem', 'choiceprob'), different_course_id.make_usage_key('html', 'for_testing_import_rewrites') ], conditional_module.sources_list ) self.assertListEqual( [ target_course_id.make_usage_key('html', 'congrats'), target_course_id.make_usage_key('html', 'secret_page') ], conditional_module.show_tag_list )
class TestJumpTo(TestCase): """ Check the jumpto link for a course. """ def setUp(self): # Use toy course from XML self.course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') def test_jumpto_invalid_location(self): location = self.course_key.make_usage_key(None, 'NoSuchPlace') # This is fragile, but unfortunately the problem is that within the LMS we # can't use the reverse calls from the CMS jumpto_url = '{0}/{1}/jump_to/{2}'.format('/courses', self.course_key.to_deprecated_string(), location.to_deprecated_string()) response = self.client.get(jumpto_url) self.assertEqual(response.status_code, 404) def test_jumpto_from_chapter(self): location = self.course_key.make_usage_key('chapter', 'Overview') jumpto_url = '{0}/{1}/jump_to/{2}'.format('/courses', self.course_key.to_deprecated_string(), location.to_deprecated_string()) expected = 'courses/edX/toy/2012_Fall/courseware/Overview/' response = self.client.get(jumpto_url) self.assertRedirects(response, expected, status_code=302, target_status_code=302) def test_jumpto_id(self): jumpto_url = '{0}/{1}/jump_to_id/{2}'.format('/courses', self.course_key.to_deprecated_string(), 'Overview') expected = 'courses/edX/toy/2012_Fall/courseware/Overview/' response = self.client.get(jumpto_url) self.assertRedirects(response, expected, status_code=302, target_status_code=302) def test_jumpto_id_invalid_location(self): location = Location('edX', 'toy', 'NoSuchPlace', None, None, None) jumpto_url = '{0}/{1}/jump_to_id/{2}'.format('/courses', self.course_key.to_deprecated_string(), location.to_deprecated_string()) response = self.client.get(jumpto_url) self.assertEqual(response.status_code, 404)
class TestInstructorEnrollmentStudentModule(TestCase): """ Test student module manipulations. """ def setUp(self): self.course_key = SlashSeparatedCourseKey('fake', 'course', 'id') def test_reset_student_attempts(self): user = UserFactory() msk = self.course_key.make_usage_key('dummy', 'module') original_state = json.dumps({'attempts': 32, 'otherstuff': 'alsorobots'}) StudentModule.objects.create(student=user, course_id=self.course_key, module_state_key=msk, state=original_state) # lambda to reload the module state from the database module = lambda: StudentModule.objects.get(student=user, course_id=self.course_key, module_state_key=msk) self.assertEqual(json.loads(module().state)['attempts'], 32) reset_student_attempts(self.course_key, user, msk) self.assertEqual(json.loads(module().state)['attempts'], 0) def test_delete_student_attempts(self): user = UserFactory() msk = self.course_key.make_usage_key('dummy', 'module') original_state = json.dumps({'attempts': 32, 'otherstuff': 'alsorobots'}) StudentModule.objects.create(student=user, course_id=self.course_key, module_state_key=msk, state=original_state) self.assertEqual(StudentModule.objects.filter(student=user, course_id=self.course_key, module_state_key=msk).count(), 1) reset_student_attempts(self.course_key, user, msk, delete_module=True) self.assertEqual(StudentModule.objects.filter(student=user, course_id=self.course_key, module_state_key=msk).count(), 0) def test_delete_submission_scores(self): user = UserFactory() problem_location = self.course_key.make_usage_key('dummy', 'module') # Create a student module for the user StudentModule.objects.create( student=user, course_id=self.course_key, module_state_key=problem_location, state=json.dumps({}) ) # Create a submission and score for the student using the submissions API student_item = { 'student_id': anonymous_id_for_user(user, self.course_key), 'course_id': self.course_key.to_deprecated_string(), 'item_id': problem_location.to_deprecated_string(), 'item_type': 'openassessment' } submission = sub_api.create_submission(student_item, 'test answer') sub_api.set_score(submission['uuid'], 1, 2) # Delete student state using the instructor dash reset_student_attempts( self.course_key, user, problem_location, delete_module=True ) # Verify that the student's scores have been reset in the submissions API score = sub_api.get_score(student_item) self.assertIs(score, None)
def import_and_populate_course(self): """ Imports the test toy course and populates it with additional test data """ content_store = contentstore() import_from_xml(self.store, self.user.id, 'common/test/data/', ['toy'], static_content_store=content_store) course_id = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') # create an Orphan # We had a bug where orphaned draft nodes caused export to fail. This is here to cover that case. vertical = self.store.get_item(course_id.make_usage_key('vertical', self.TEST_VERTICAL), depth=1) vertical.location = vertical.location.replace(name='no_references') self.store.update_item(vertical, self.user.id, allow_not_found=True) orphan_vertical = self.store.get_item(vertical.location) self.assertEqual(orphan_vertical.location.name, 'no_references') self.assertEqual(len(orphan_vertical.children), len(vertical.children)) # create a Draft vertical vertical = self.store.get_item(course_id.make_usage_key('vertical', self.TEST_VERTICAL), depth=1) draft_vertical = self.store.convert_to_draft(vertical.location, self.user.id) self.assertEqual(self.store.compute_publish_state(draft_vertical), PublishState.draft) # create a Private (draft only) vertical private_vertical = self.store.create_item(self.user.id, course_id, 'vertical', self.PRIVATE_VERTICAL) self.assertEqual(self.store.compute_publish_state(private_vertical), PublishState.private) # create a Published (no draft) vertical public_vertical = self.store.create_item(self.user.id, course_id, 'vertical', self.PUBLISHED_VERTICAL) public_vertical = self.store.publish(public_vertical.location, self.user.id) self.assertEqual(self.store.compute_publish_state(public_vertical), PublishState.public) # add the new private and new public as children of the sequential sequential = self.store.get_item(course_id.make_usage_key('sequential', self.SEQUENTIAL)) sequential.children.append(private_vertical.location) sequential.children.append(public_vertical.location) self.store.update_item(sequential, self.user.id) # lock an asset content_store.set_attr(self.LOCKED_ASSET_KEY, 'locked', True) # create a non-portable link - should be rewritten in new courses html_module = self.store.get_item(course_id.make_usage_key('html', 'nonportable')) new_data = html_module.data = html_module.data.replace( '/static/', '/c4x/{0}/{1}/asset/'.format(course_id.org, course_id.course) ) self.store.update_item(html_module, self.user.id) html_module = self.store.get_item(html_module.location) self.assertEqual(new_data, html_module.data) return course_id
def import_and_populate_course(self): """ Imports the test toy course and populates it with additional test data """ content_store = contentstore() import_from_xml(self.store, self.user.id, 'common/test/data/', ['toy'], static_content_store=content_store) course_id = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') # create an Orphan # We had a bug where orphaned draft nodes caused export to fail. This is here to cover that case. vertical = self.store.get_item(course_id.make_usage_key('vertical', self.TEST_VERTICAL), depth=1) vertical.location = vertical.location.replace(name='no_references') self.store.update_item(vertical, self.user.id, allow_not_found=True) orphan_vertical = self.store.get_item(vertical.location) self.assertEqual(orphan_vertical.location.name, 'no_references') self.assertEqual(len(orphan_vertical.children), len(vertical.children)) # create a Draft vertical vertical = self.store.get_item(course_id.make_usage_key('vertical', self.TEST_VERTICAL), depth=1) draft_vertical = self.store.convert_to_draft(vertical.location, self.user.id) self.assertTrue(self.store.has_published_version(draft_vertical)) # create a Private (draft only) vertical private_vertical = self.store.create_item(self.user.id, course_id, 'vertical', self.PRIVATE_VERTICAL) self.assertFalse(self.store.has_published_version(private_vertical)) # create a Published (no draft) vertical public_vertical = self.store.create_item(self.user.id, course_id, 'vertical', self.PUBLISHED_VERTICAL) public_vertical = self.store.publish(public_vertical.location, self.user.id) self.assertTrue(self.store.has_published_version(public_vertical)) # add the new private and new public as children of the sequential sequential = self.store.get_item(course_id.make_usage_key('sequential', self.SEQUENTIAL)) sequential.children.append(private_vertical.location) sequential.children.append(public_vertical.location) self.store.update_item(sequential, self.user.id) # lock an asset content_store.set_attr(self.LOCKED_ASSET_KEY, 'locked', True) # create a non-portable link - should be rewritten in new courses html_module = self.store.get_item(course_id.make_usage_key('html', 'nonportable')) new_data = html_module.data = html_module.data.replace( '/static/', '/c4x/{0}/{1}/asset/'.format(course_id.org, course_id.course) ) self.store.update_item(html_module, self.user.id) html_module = self.store.get_item(html_module.location) self.assertEqual(new_data, html_module.data) return course_id
def lms_link_test(self): """ Tests get_lms_link_for_item. """ course_key = SlashSeparatedCourseKey("mitX", "101", "test") location = course_key.make_usage_key("vertical", "contacting_us") link = utils.get_lms_link_for_item(location, False) self.assertEquals(link, "//localhost:8000/courses/mitX/101/test/jump_to/i4x://mitX/101/vertical/contacting_us") # test preview link = utils.get_lms_link_for_item(location, True) self.assertEquals(link, "//preview/courses/mitX/101/test/jump_to/i4x://mitX/101/vertical/contacting_us") # now test with the course' location location = course_key.make_usage_key("course", "test") link = utils.get_lms_link_for_item(location) self.assertEquals(link, "//localhost:8000/courses/mitX/101/test/jump_to/i4x://mitX/101/course/test")
def test_rewrite_reference(self): module_store = modulestore('direct') target_course_id = SlashSeparatedCourseKey('testX', 'peergrading_copy', 'copy_run') import_from_xml(module_store, 'common/test/data/', ['open_ended'], target_course_id=target_course_id) peergrading_module = module_store.get_item( target_course_id.make_usage_key('peergrading', 'PeerGradingLinked')) self.assertIsNotNone(peergrading_module) self.assertEqual( target_course_id.make_usage_key('combinedopenended', 'SampleQuestion'), peergrading_module.link_to_location)
def test_delete_course_map(self): """ Test that course location is properly remove from loc_mapper and cache when course is deleted """ org = u'foo_org' course = u'bar_course' run = u'baz_run' course_location = SlashSeparatedCourseKey(org, course, run) loc_mapper().create_map_entry(course_location) # pylint: disable=protected-access entry = loc_mapper().location_map.find_one({ '_id': loc_mapper()._construct_course_son(course_location) }) self.assertIsNotNone(entry, 'Entry not found in loc_mapper') self.assertEqual(entry['offering'], u'{1}.{2}'.format(org, course, run)) # now delete course location from loc_mapper and cache and test that course location no longer # exists in loca_mapper and cache loc_mapper().delete_course_mapping(course_location) # pylint: disable=protected-access entry = loc_mapper().location_map.find_one({ '_id': loc_mapper()._construct_course_son(course_location) }) self.assertIsNone(entry, 'Entry found in loc_mapper') # pylint: disable=protected-access cached_value = loc_mapper()._get_location_from_cache(course_location.make_usage_key('course', run)) self.assertIsNone(cached_value, 'course_locator found in cache') # pylint: disable=protected-access cached_value = loc_mapper()._get_course_location_from_cache(course_location) self.assertIsNone(cached_value, 'Entry found in cache')
class SetupTestErrorModules(): def setUp(self): self.system = get_test_system() self.course_id = SlashSeparatedCourseKey('org', 'course', 'run') self.location = self.course_id.make_usage_key('foo', 'bar') self.valid_xml = u"<problem>ABC \N{SNOWMAN}</problem>" self.error_msg = "Error"
def test_delete_course_map(self): """ Test that course location is properly remove from loc_mapper and cache when course is deleted """ org = u"foo_org" course = u"bar_course" run = u"baz_run" course_location = SlashSeparatedCourseKey(org, course, run) loc_mapper().create_map_entry(course_location) # pylint: disable=protected-access entry = loc_mapper().location_map.find_one({"_id": loc_mapper()._construct_course_son(course_location)}) self.assertIsNotNone(entry, "Entry not found in loc_mapper") self.assertEqual(entry["offering"], u"{1}.{2}".format(org, course, run)) # now delete course location from loc_mapper and cache and test that course location no longer # exists in loca_mapper and cache loc_mapper().delete_course_mapping(course_location) # pylint: disable=protected-access entry = loc_mapper().location_map.find_one({"_id": loc_mapper()._construct_course_son(course_location)}) self.assertIsNone(entry, "Entry found in loc_mapper") # pylint: disable=protected-access cached_value = loc_mapper()._get_location_from_cache(course_location.make_usage_key("course", run)) self.assertIsNone(cached_value, "course_locator found in cache") # pylint: disable=protected-access cached_value = loc_mapper()._get_course_location_from_cache(course_location) self.assertIsNone(cached_value, "Entry found in cache")
def test_round_trip(self, source_builder, dest_builder, source_content_builder, dest_content_builder, course_data_name): source_course_key = SlashSeparatedCourseKey('source', 'course', 'key') dest_course_key = SlashSeparatedCourseKey('dest', 'course', 'key') # Construct the contentstore for storing the first import with source_content_builder.build() as source_content: # Construct the modulestore for storing the first import (using the previously created contentstore) with source_builder.build(source_content) as source_store: # Construct the contentstore for storing the second import with dest_content_builder.build() as dest_content: # Construct the modulestore for storing the second import (using the second contentstore) with dest_builder.build(dest_content) as dest_store: import_from_xml( source_store, 'test_user', 'common/test/data', course_dirs=[course_data_name], static_content_store=source_content, target_course_id=source_course_key, create_new_course_if_not_present=True, ) export_to_xml( source_store, source_content, source_course_key, self.export_dir, 'exported_course', ) import_from_xml( dest_store, 'test_user', self.export_dir, static_content_store=dest_content, target_course_id=dest_course_key, create_new_course_if_not_present=True, ) self.exclude_field(source_course_key.make_usage_key('course', 'key'), 'wiki_slug') self.exclude_field(None, 'xml_attributes') self.ignore_asset_key('_id') self.ignore_asset_key('uploadDate') self.ignore_asset_key('content_son') self.ignore_asset_key('thumbnail_location') self.assertCoursesEqual( source_store, source_course_key, dest_store, dest_course_key, ) self.assertAssetsEqual( source_content, source_course_key, dest_content, dest_course_key, )
def setUp(self): course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') self.course = course_key.make_usage_key('course', course_key.run) self.anonymous_user = AnonymousUserFactory() self.student = UserFactory() self.global_staff = UserFactory(is_staff=True) self.course_staff = StaffFactory(course_key=self.course.course_key) self.course_instructor = InstructorFactory(course_key=self.course.course_key)
def test_rewrite_reference(self): module_store = modulestore('direct') target_course_id = SlashSeparatedCourseKey('testX', 'peergrading_copy', 'copy_run') import_from_xml( module_store, 'common/test/data/', ['open_ended'], target_course_id=target_course_id ) peergrading_module = module_store.get_item( target_course_id.make_usage_key('peergrading', 'PeerGradingLinked') ) self.assertIsNotNone(peergrading_module) self.assertEqual( target_course_id.make_usage_key('combinedopenended', 'SampleQuestion'), peergrading_module.link_to_location )
def test_rewrite_reference_value_dict(self): module_store = modulestore('direct') target_course_id = SlashSeparatedCourseKey('testX', 'split_test_copy', 'copy_run') import_from_xml(module_store, 'common/test/data/', ['split_test_module'], target_course_id=target_course_id) split_test_module = module_store.get_item( target_course_id.make_usage_key('split_test', 'split1')) self.assertIsNotNone(split_test_module) self.assertEqual( { "0": target_course_id.make_usage_key('vertical', 'sample_0'), "2": target_course_id.make_usage_key('vertical', 'sample_2'), }, split_test_module.group_id_to_child, )
def setUp(self): system = get_test_descriptor_system() course_key = SlashSeparatedCourseKey("org", "course", "run") usage_key = course_key.make_usage_key("video", "name") self.descriptor = system.construct_xblock_from_class( VideoDescriptor, scope_ids=ScopeIds(None, None, usage_key, usage_key), field_data=DictFieldData({}) ) self.descriptor.runtime.handler_url = MagicMock()
class SetupTestErrorModules(unittest.TestCase): """Common setUp for use in ErrorModule tests.""" def setUp(self): super(SetupTestErrorModules, self).setUp() self.system = get_test_system() self.course_id = SlashSeparatedCourseKey('org', 'course', 'run') self.location = self.course_id.make_usage_key('foo', 'bar') self.valid_xml = u"<problem>ABC \N{SNOWMAN}</problem>" self.error_msg = "Error"
def i_click_on_error_dialog(step): world.click_link_by_text('Correct failed component') assert_true(world.css_html("span.inline-error").startswith("Problem i4x://MITx/999/problem")) course_key = SlashSeparatedCourseKey("MITx", "999", "Robot_Super_Course") # we don't know the actual ID of the vertical. So just check that we did go to a # vertical page in the course (there should only be one). vertical_usage_key = course_key.make_usage_key("vertical", "") vertical_url = reverse_usage_url('unit_handler', vertical_usage_key) assert_equal(1, world.browser.url.count(vertical_url))
def setup_xml_course(self): """ Define the XML backed course to use. Toy courses are already loaded in XML and mixed modulestores. """ course_key = SlashSeparatedCourseKey("edX", "toy", "2012_Fall") location = course_key.make_usage_key("chapter", "Overview") descriptor = modulestore().get_item(location) self.module = self._get_module(course_key, descriptor, location)
def setUp(self): system = get_test_descriptor_system() course_key = SlashSeparatedCourseKey('org', 'course', 'run') usage_key = course_key.make_usage_key('video', 'name') self.descriptor = system.construct_xblock_from_class( VideoDescriptor, scope_ids=ScopeIds(None, None, usage_key, usage_key), field_data=DictFieldData({}), ) self.descriptor.runtime.handler_url = MagicMock()
def setup_xml_course(self): """ Define the XML backed course to use. Toy courses are already loaded in XML and mixed modulestores. """ course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') location = course_key.make_usage_key('chapter', 'Overview') descriptor = modulestore().get_item(location) self.module = self._get_module(course_key, descriptor, location)
def instantiate_descriptor(**field_data): """ Instantiate descriptor with most properties. """ system = get_test_descriptor_system() course_key = SlashSeparatedCourseKey("org", "course", "run") usage_key = course_key.make_usage_key("html", "SampleHtml") return system.construct_xblock_from_class( HtmlDescriptor, scope_ids=ScopeIds(None, None, usage_key, usage_key), field_data=DictFieldData(field_data) )
def create_course(self, org, offering, user_id=None, fields=None, **kwargs): """ Creates and returns the course. Args: org (str): the organization that owns the course offering (str): the name of the course offering user_id: id of the user creating the course fields (dict): Fields to set on the course at initialization kwargs: Any optional arguments understood by a subset of modulestores to customize instantiation Returns: a CourseDescriptor Raises: InvalidLocationError: If a course with the same org and offering already exists """ course, _, run = offering.partition('/') course_id = SlashSeparatedCourseKey(org, course, run) # Check if a course with this org/course has been defined before (case-insensitive) course_search_location = SON([ ('_id.tag', 'i4x'), ('_id.org', re.compile(u'^{}$'.format(course_id.org), re.IGNORECASE)), ('_id.course', re.compile(u'^{}$'.format(course_id.course), re.IGNORECASE)), ('_id.category', 'course'), ]) courses = self.collection.find(course_search_location, fields=('_id')) if courses.count() > 0: raise InvalidLocationError( "There are already courses with the given org and course id: {}" .format([course['_id'] for course in courses])) location = course_id.make_usage_key('course', course_id.run) course = self.create_and_save_xmodule(location, fields=fields, **kwargs) # clone a default 'about' overview module as well about_location = location.replace(category='about', name='overview') overview_template = AboutDescriptor.get_template('overview.yaml') self.create_and_save_xmodule( about_location, system=course.system, definition_data=overview_template.get('data')) return course
def _verify_split_test_import(self, target_course_name, source_course_name, split_test_name, groups_to_verticals): module_store = modulestore() target_course_id = SlashSeparatedCourseKey('testX', target_course_name, 'copy_run') import_from_xml(module_store, self.user.id, 'common/test/data/', [source_course_name], target_course_id=target_course_id) split_test_module = module_store.get_item( target_course_id.make_usage_key('split_test', split_test_name)) self.assertIsNotNone(split_test_module) remapped_verticals = { key: target_course_id.make_usage_key('vertical', value) for key, value in groups_to_verticals.iteritems() } self.assertEqual(remapped_verticals, split_test_module.group_id_to_child)
def test_rewrite_reference_value_dict(self): module_store = modulestore('direct') target_course_id = SlashSeparatedCourseKey('testX', 'split_test_copy', 'copy_run') import_from_xml( module_store, 'common/test/data/', ['split_test_module'], target_course_id=target_course_id ) split_test_module = module_store.get_item( target_course_id.make_usage_key('split_test', 'split1') ) self.assertIsNotNone(split_test_module) self.assertEqual( { "0": target_course_id.make_usage_key('vertical', 'sample_0'), "2": target_course_id.make_usage_key('vertical', 'sample_2'), }, split_test_module.group_id_to_child, )
def setUp(self): super(AccessTestCase, self).setUp() course_key = SlashSeparatedCourseKey("edX", "toy", "2012_Fall") self.course = course_key.make_usage_key("course", course_key.run) self.anonymous_user = AnonymousUserFactory() self.beta_user = BetaTesterFactory(course_key=self.course.course_key) self.student = UserFactory() self.global_staff = UserFactory(is_staff=True) self.course_staff = StaffFactory(course_key=self.course.course_key) self.course_instructor = InstructorFactory(course_key=self.course.course_key) self.staff = GlobalStaffFactory()
def _verify_split_test_import(self, target_course_name, source_course_name, split_test_name, groups_to_verticals): module_store = modulestore() target_course_id = SlashSeparatedCourseKey('testX', target_course_name, 'copy_run') import_from_xml( module_store, self.user.id, 'common/test/data/', [source_course_name], target_course_id=target_course_id ) split_test_module = module_store.get_item( target_course_id.make_usage_key('split_test', split_test_name) ) self.assertIsNotNone(split_test_module) remapped_verticals = { key: target_course_id.make_usage_key('vertical', value) for key, value in groups_to_verticals.iteritems() } self.assertEqual(remapped_verticals, split_test_module.group_id_to_child)
def i_click_on_error_dialog(step): world.click_link_by_text('Correct failed component') assert_true( world.css_html("span.inline-error").startswith( "Problem i4x://MITx/999/problem")) course_key = SlashSeparatedCourseKey("MITx", "999", "Robot_Super_Course") # we don't know the actual ID of the vertical. So just check that we did go to a # vertical page in the course (there should only be one). vertical_usage_key = course_key.make_usage_key("vertical", "") vertical_url = reverse_usage_url('unit_handler', vertical_usage_key) assert_equal(1, world.browser.url.count(vertical_url))
def instantiate_descriptor(**field_data): """ Instantiate descriptor with most properties. """ system = get_test_descriptor_system() course_key = SlashSeparatedCourseKey('org', 'course', 'run') usage_key = course_key.make_usage_key('video', 'SampleProblem') return system.construct_xblock_from_class( VideoDescriptor, scope_ids=ScopeIds(None, None, usage_key, usage_key), field_data=DictFieldData(field_data), )
def setUp(self): super(AccessTestCase, self).setUp() course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') self.course = course_key.make_usage_key('course', course_key.run) self.anonymous_user = AnonymousUserFactory() self.beta_user = BetaTesterFactory(course_key=self.course.course_key) self.student = UserFactory() self.global_staff = UserFactory(is_staff=True) self.course_staff = StaffFactory(course_key=self.course.course_key) self.course_instructor = InstructorFactory( course_key=self.course.course_key) self.staff = GlobalStaffFactory()
def i_click_on_error_dialog(step): world.click_link_by_text('Correct failed component') assert_true(world.css_html("span.inline-error").startswith("Problem i4x://MITx/999/problem")) course_key = SlashSeparatedCourseKey("MITx", "999", "Robot_Super_Course") # we don't know the actual ID of the vertical. So just check that we did go to a # vertical page in the course (there should only be one). vertical_usage_key = course_key.make_usage_key("vertical", None) vertical_url = reverse_usage_url('container_handler', vertical_usage_key) # Remove the trailing "/None" from the URL - we don't know the course ID, so we just want to # check that we visited a vertical URL. if vertical_url.endswith("/None"): vertical_url = vertical_url[:-5] assert_equal(1, world.browser.url.count(vertical_url))
def create_course(self, org, offering, user_id=None, fields=None, **kwargs): """ Creates and returns the course. Args: org (str): the organization that owns the course offering (str): the name of the course offering user_id: id of the user creating the course fields (dict): Fields to set on the course at initialization kwargs: Any optional arguments understood by a subset of modulestores to customize instantiation Returns: a CourseDescriptor Raises: InvalidLocationError: If a course with the same org and offering already exists """ course, _, run = offering.partition('/') course_id = SlashSeparatedCourseKey(org, course, run) # Check if a course with this org/course has been defined before (case-insensitive) course_search_location = SON([ ('_id.tag', 'i4x'), ('_id.org', re.compile(u'^{}$'.format(course_id.org), re.IGNORECASE)), ('_id.course', re.compile(u'^{}$'.format(course_id.course), re.IGNORECASE)), ('_id.category', 'course'), ]) courses = self.collection.find(course_search_location, fields=('_id')) if courses.count() > 0: raise InvalidLocationError( "There are already courses with the given org and course id: {}".format([ course['_id'] for course in courses ])) location = course_id.make_usage_key('course', course_id.run) course = self.create_and_save_xmodule(location, user_id, fields=fields, **kwargs) # clone a default 'about' overview module as well about_location = location.replace( category='about', name='overview' ) overview_template = AboutDescriptor.get_template('overview.yaml') self.create_and_save_xmodule( about_location, user_id, definition_data=overview_template.get('data'), runtime=course.system ) return course
def i_click_on_error_dialog(step): world.click_link_by_text('Correct failed component') assert_true( world.css_html("span.inline-error").startswith( "Problem i4x://MITx/999/problem")) course_key = SlashSeparatedCourseKey("MITx", "999", "Robot_Super_Course") # we don't know the actual ID of the vertical. So just check that we did go to a # vertical page in the course (there should only be one). vertical_usage_key = course_key.make_usage_key("vertical", None) vertical_url = reverse_usage_url('container_handler', vertical_usage_key) # Remove the trailing "/None" from the URL - we don't know the course ID, so we just want to # check that we visited a vertical URL. if vertical_url.endswith("/None"): vertical_url = vertical_url[:-5] assert_equal(1, world.browser.url.count(vertical_url))
def check_path_to_location(modulestore): """ Make sure that path_to_location works: should be passed a modulestore with the toy and simple courses loaded. """ course_id = SlashSeparatedCourseKey("edX", "toy", "2012_Fall") should_work = ( (course_id.make_usage_key('video', 'Welcome'), (course_id, "Overview", "Welcome", None)), (course_id.make_usage_key('chapter', 'Overview'), (course_id, "Overview", None, None)), ) for location, expected in should_work: assert_equals(path_to_location(modulestore, location), expected) not_found = ( course_id.make_usage_key('video', 'WelcomeX'), course_id.make_usage_key('course', 'NotHome'), ) for location in not_found: with assert_raises(ItemNotFoundError): path_to_location(modulestore, location)
def test_graphicslidertool_import(self): ''' Check to see if definition_from_xml in gst_module.py works properly. Pulls data from the graphic_slider_tool directory in the test data directory. ''' modulestore = XMLModuleStore(DATA_DIR, course_dirs=['graphic_slider_tool']) sa_id = SlashSeparatedCourseKey("edX", "gst_test", "2012_Fall") location = sa_id.make_usage_key("graphical_slider_tool", "sample_gst") gst_sample = modulestore.get_item(location) render_string_from_sample_gst_xml = """ <slider var="a" style="width:400px;float:left;"/>\ <plot style="margin-top:15px;margin-bottom:15px;"/>""".strip() self.assertIn(render_string_from_sample_gst_xml, gst_sample.data)
def test_round_trip(self, source_builder, dest_builder, source_content_builder, dest_content_builder, course_data_name): source_course_key = SlashSeparatedCourseKey('source', 'course', 'key') dest_course_key = SlashSeparatedCourseKey('dest', 'course', 'key') # Construct the contentstore for storing the first import with source_content_builder.build() as source_content: # Construct the modulestore for storing the first import (using the previously created contentstore) with source_builder.build(source_content) as source_store: # Construct the contentstore for storing the second import with dest_content_builder.build() as dest_content: # Construct the modulestore for storing the second import (using the second contentstore) with dest_builder.build(dest_content) as dest_store: import_from_xml( source_store, 'test_user', 'common/test/data', course_dirs=[course_data_name], static_content_store=source_content, target_course_id=source_course_key, create_new_course_if_not_present=True, ) export_to_xml( source_store, source_content, source_course_key, self.export_dir, 'exported_course', ) import_from_xml( dest_store, 'test_user', self.export_dir, static_content_store=dest_content, target_course_id=dest_course_key, create_new_course_if_not_present=True, ) self.exclude_field( source_course_key.make_usage_key('course', 'key'), 'wiki_slug') self.exclude_field(None, 'xml_attributes') self.ignore_asset_key('_id') self.ignore_asset_key('uploadDate') self.ignore_asset_key('content_son') self.ignore_asset_key('thumbnail_location') self.assertCoursesEqual( source_store, source_course_key, dest_store, dest_course_key, ) self.assertAssetsEqual( source_content, source_course_key, dest_content, dest_course_key, )
def import_and_populate_course(self): """ Imports the test toy course and populates it with additional test data """ content_store = contentstore() import_course_from_xml(self.store, self.user.id, TEST_DATA_DIR, ['toy'], static_content_store=content_store) course_id = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') # create an Orphan # We had a bug where orphaned draft nodes caused export to fail. This is here to cover that case. vertical = self.store.get_item(course_id.make_usage_key('vertical', self.TEST_VERTICAL), depth=1) vertical.location = vertical.location.replace(name='no_references') self.store.update_item(vertical, self.user.id, allow_not_found=True) orphan_vertical = self.store.get_item(vertical.location) self.assertEqual(orphan_vertical.location.name, 'no_references') self.assertEqual(len(orphan_vertical.children), len(vertical.children)) # create an orphan vertical and html; we already don't try to import # the orphaned vertical, but we should make sure we don't import # the orphaned vertical's child html, too orphan_draft_vertical = self.store.create_item( self.user.id, course_id, 'vertical', self.ORPHAN_DRAFT_VERTICAL ) orphan_draft_html = self.store.create_item( self.user.id, course_id, 'html', self.ORPHAN_DRAFT_HTML ) orphan_draft_vertical.children.append(orphan_draft_html.location) self.store.update_item(orphan_draft_vertical, self.user.id) # create a Draft vertical vertical = self.store.get_item(course_id.make_usage_key('vertical', self.TEST_VERTICAL), depth=1) draft_vertical = self.store.convert_to_draft(vertical.location, self.user.id) self.assertTrue(self.store.has_published_version(draft_vertical)) # create a Private (draft only) vertical private_vertical = self.store.create_item(self.user.id, course_id, 'vertical', self.PRIVATE_VERTICAL) self.assertFalse(self.store.has_published_version(private_vertical)) # create a Published (no draft) vertical public_vertical = self.store.create_item(self.user.id, course_id, 'vertical', self.PUBLISHED_VERTICAL) public_vertical = self.store.publish(public_vertical.location, self.user.id) self.assertTrue(self.store.has_published_version(public_vertical)) # add the new private and new public as children of the sequential sequential = self.store.get_item(course_id.make_usage_key('sequential', self.SEQUENTIAL)) sequential.children.append(private_vertical.location) sequential.children.append(public_vertical.location) self.store.update_item(sequential, self.user.id) # create an html and video component to make drafts: draft_html = self.store.create_item(self.user.id, course_id, 'html', self.DRAFT_HTML) draft_video = self.store.create_item(self.user.id, course_id, 'video', self.DRAFT_VIDEO) # add them as children to the public_vertical public_vertical.children.append(draft_html.location) public_vertical.children.append(draft_video.location) self.store.update_item(public_vertical, self.user.id) # publish changes to vertical self.store.publish(public_vertical.location, self.user.id) # convert html/video to draft self.store.convert_to_draft(draft_html.location, self.user.id) self.store.convert_to_draft(draft_video.location, self.user.id) # lock an asset content_store.set_attr(self.LOCKED_ASSET_KEY, 'locked', True) # create a non-portable link - should be rewritten in new courses html_module = self.store.get_item(course_id.make_usage_key('html', 'nonportable')) new_data = html_module.data = html_module.data.replace( '/static/', '/c4x/{0}/{1}/asset/'.format(course_id.org, course_id.course) ) self.store.update_item(html_module, self.user.id) html_module = self.store.get_item(html_module.location) self.assertEqual(new_data, html_module.data) return course_id
class RolesTestCase(TestCase): """ Tests of student.roles """ def setUp(self): self.course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') self.course_loc = self.course_key.make_usage_key('course', '2012_Fall') self.anonymous_user = AnonymousUserFactory() self.student = UserFactory() self.global_staff = UserFactory(is_staff=True) self.course_staff = StaffFactory(course_key=self.course_key) self.course_instructor = InstructorFactory(course_key=self.course_key) def test_global_staff(self): self.assertFalse(GlobalStaff().has_user(self.student)) self.assertFalse(GlobalStaff().has_user(self.course_staff)) self.assertFalse(GlobalStaff().has_user(self.course_instructor)) self.assertTrue(GlobalStaff().has_user(self.global_staff)) def test_group_name_case_sensitive(self): uppercase_course_id = "ORG/COURSE/NAME" lowercase_course_id = uppercase_course_id.lower() uppercase_course_key = SlashSeparatedCourseKey.from_deprecated_string( uppercase_course_id) lowercase_course_key = SlashSeparatedCourseKey.from_deprecated_string( lowercase_course_id) role = "role" lowercase_user = UserFactory() CourseRole(role, lowercase_course_key).add_users(lowercase_user) uppercase_user = UserFactory() CourseRole(role, uppercase_course_key).add_users(uppercase_user) self.assertTrue( CourseRole(role, lowercase_course_key).has_user(lowercase_user)) self.assertFalse( CourseRole(role, uppercase_course_key).has_user(lowercase_user)) self.assertFalse( CourseRole(role, lowercase_course_key).has_user(uppercase_user)) self.assertTrue( CourseRole(role, uppercase_course_key).has_user(uppercase_user)) def test_course_role(self): """ Test that giving a user a course role enables access appropriately """ self.assertFalse( CourseStaffRole(self.course_key).has_user(self.student), "Student has premature access to {}".format(self.course_key)) CourseStaffRole(self.course_key).add_users(self.student) self.assertTrue( CourseStaffRole(self.course_key).has_user(self.student), "Student doesn't have access to {}".format(unicode( self.course_key))) # remove access and confirm CourseStaffRole(self.course_key).remove_users(self.student) self.assertFalse( CourseStaffRole(self.course_key).has_user(self.student), "Student still has access to {}".format(self.course_key)) def test_org_role(self): """ Test that giving a user an org role enables access appropriately """ self.assertFalse( OrgStaffRole(self.course_key.org).has_user(self.student), "Student has premature access to {}".format(self.course_key.org)) OrgStaffRole(self.course_key.org).add_users(self.student) self.assertTrue( OrgStaffRole(self.course_key.org).has_user(self.student), "Student doesn't have access to {}".format( unicode(self.course_key.org))) # remove access and confirm OrgStaffRole(self.course_key.org).remove_users(self.student) if hasattr(self.student, '_roles'): del self.student._roles self.assertFalse( OrgStaffRole(self.course_key.org).has_user(self.student), "Student still has access to {}".format(self.course_key.org)) def test_org_and_course_roles(self): """ Test that Org and course roles don't interfere with course roles or vice versa """ OrgInstructorRole(self.course_key.org).add_users(self.student) CourseInstructorRole(self.course_key).add_users(self.student) self.assertTrue( OrgInstructorRole(self.course_key.org).has_user(self.student), "Student doesn't have access to {}".format( unicode(self.course_key.org))) self.assertTrue( CourseInstructorRole(self.course_key).has_user(self.student), "Student doesn't have access to {}".format(unicode( self.course_key))) # remove access and confirm OrgInstructorRole(self.course_key.org).remove_users(self.student) self.assertFalse( OrgInstructorRole(self.course_key.org).has_user(self.student), "Student still has access to {}".format(self.course_key.org)) self.assertTrue( CourseInstructorRole(self.course_key).has_user(self.student), "Student doesn't have access to {}".format(unicode( self.course_key))) # ok now keep org role and get rid of course one OrgInstructorRole(self.course_key.org).add_users(self.student) CourseInstructorRole(self.course_key).remove_users(self.student) self.assertTrue( OrgInstructorRole(self.course_key.org).has_user(self.student), "Student lost has access to {}".format(self.course_key.org)) self.assertFalse( CourseInstructorRole(self.course_key).has_user(self.student), "Student doesn't have access to {}".format(unicode( self.course_key))) def test_get_user_for_role(self): """ test users_for_role """ role = CourseStaffRole(self.course_key) role.add_users(self.student) self.assertGreater(len(role.users_with_role()), 0) def test_add_users_doesnt_add_duplicate_entry(self): """ Tests that calling add_users multiple times before a single call to remove_users does not result in the user remaining in the group. """ role = CourseStaffRole(self.course_key) role.add_users(self.student) self.assertTrue(role.has_user(self.student)) # Call add_users a second time, then remove just once. role.add_users(self.student) role.remove_users(self.student) self.assertFalse(role.has_user(self.student))
class TestMongoKeyValueStore(object): """ Tests for MongoKeyValueStore. """ def setUp(self): self.data = {'foo': 'foo_value'} self.course_id = SlashSeparatedCourseKey('org', 'course', 'run') self.children = [ self.course_id.make_usage_key('child', 'a'), self.course_id.make_usage_key('child', 'b') ] self.metadata = {'meta': 'meta_val'} self.kvs = MongoKeyValueStore(self.data, self.children, self.metadata) def test_read(self): assert_equals( self.data['foo'], self.kvs.get(KeyValueStore.Key(Scope.content, None, None, 'foo'))) assert_equals( self.children, self.kvs.get( KeyValueStore.Key(Scope.children, None, None, 'children'))) assert_equals( self.metadata['meta'], self.kvs.get(KeyValueStore.Key(Scope.settings, None, None, 'meta'))) assert_equals( None, self.kvs.get(KeyValueStore.Key(Scope.parent, None, None, 'parent'))) def test_read_invalid_scope(self): for scope in (Scope.preferences, Scope.user_info, Scope.user_state): key = KeyValueStore.Key(scope, None, None, 'foo') with assert_raises(InvalidScopeError): self.kvs.get(key) assert_false(self.kvs.has(key)) def test_read_non_dict_data(self): self.kvs = MongoKeyValueStore('xml_data', self.children, self.metadata) assert_equals( 'xml_data', self.kvs.get(KeyValueStore.Key(Scope.content, None, None, 'data'))) def _check_write(self, key, value): self.kvs.set(key, value) assert_equals(value, self.kvs.get(key)) def test_write(self): yield (self._check_write, KeyValueStore.Key(Scope.content, None, None, 'foo'), 'new_data') yield (self._check_write, KeyValueStore.Key(Scope.children, None, None, 'children'), []) yield (self._check_write, KeyValueStore.Key(Scope.settings, None, None, 'meta'), 'new_settings') def test_write_non_dict_data(self): self.kvs = MongoKeyValueStore('xml_data', self.children, self.metadata) self._check_write(KeyValueStore.Key(Scope.content, None, None, 'data'), 'new_data') def test_write_invalid_scope(self): for scope in (Scope.preferences, Scope.user_info, Scope.user_state, Scope.parent): with assert_raises(InvalidScopeError): self.kvs.set(KeyValueStore.Key(scope, None, None, 'foo'), 'new_value') def _check_delete_default(self, key, default_value): self.kvs.delete(key) assert_equals(default_value, self.kvs.get(key)) assert self.kvs.has(key) def _check_delete_key_error(self, key): self.kvs.delete(key) with assert_raises(KeyError): self.kvs.get(key) assert_false(self.kvs.has(key)) def test_delete(self): yield (self._check_delete_key_error, KeyValueStore.Key(Scope.content, None, None, 'foo')) yield (self._check_delete_default, KeyValueStore.Key(Scope.children, None, None, 'children'), []) yield (self._check_delete_key_error, KeyValueStore.Key(Scope.settings, None, None, 'meta')) def test_delete_invalid_scope(self): for scope in (Scope.preferences, Scope.user_info, Scope.user_state, Scope.parent): with assert_raises(InvalidScopeError): self.kvs.delete(KeyValueStore.Key(scope, None, None, 'foo'))
class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase): """ Test the handle_xblock_callback function """ def setUp(self): self.course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') self.location = self.course_key.make_usage_key('chapter', 'Overview') self.toy_course = modulestore().get_course(self.course_key) self.mock_user = UserFactory() self.mock_user.id = 1 self.request_factory = RequestFactory() # Construct a mock module for the modulestore to return self.mock_module = MagicMock() self.mock_module.id = 1 self.dispatch = 'score_update' # Construct a 'standard' xqueue_callback url self.callback_url = reverse('xqueue_callback', kwargs={ 'course_id': self.course_key.to_deprecated_string(), 'userid': str(self.mock_user.id), 'mod_id': self.mock_module.id, 'dispatch': self.dispatch }) def _mock_file(self, name='file', size=10): """Create a mock file object for testing uploads""" mock_file = MagicMock(size=size, read=lambda: 'x' * size) # We can't use `name` as a kwarg to Mock to set the name attribute # because mock uses `name` to name the mock itself mock_file.name = name return mock_file def test_invalid_location(self): request = self.request_factory.post('dummy_url', data={'position': 1}) request.user = self.mock_user with self.assertRaises(Http404): render.handle_xblock_callback( request, self.course_key.to_deprecated_string(), 'invalid Location', 'dummy_handler' 'dummy_dispatch') def test_too_many_files(self): request = self.request_factory.post( 'dummy_url', data={ 'file_id': (self._mock_file(), ) * (settings.MAX_FILEUPLOADS_PER_INPUT + 1) }) request.user = self.mock_user self.assertEquals( render.handle_xblock_callback( request, self.course_key.to_deprecated_string(), quote_slashes(self.location.to_deprecated_string()), 'dummy_handler').content, json.dumps({ 'success': 'Submission aborted! Maximum %d files may be submitted at once' % settings.MAX_FILEUPLOADS_PER_INPUT })) def test_too_large_file(self): inputfile = self._mock_file(size=1 + settings.STUDENT_FILEUPLOAD_MAX_SIZE) request = self.request_factory.post('dummy_url', data={'file_id': inputfile}) request.user = self.mock_user self.assertEquals( render.handle_xblock_callback( request, self.course_key.to_deprecated_string(), quote_slashes(self.location.to_deprecated_string()), 'dummy_handler').content, json.dumps({ 'success': 'Submission aborted! Your file "%s" is too large (max size: %d MB)' % (inputfile.name, settings.STUDENT_FILEUPLOAD_MAX_SIZE / (1000**2)) })) def test_xmodule_dispatch(self): request = self.request_factory.post('dummy_url', data={'position': 1}) request.user = self.mock_user response = render.handle_xblock_callback( request, self.course_key.to_deprecated_string(), quote_slashes(self.location.to_deprecated_string()), 'xmodule_handler', 'goto_position', ) self.assertIsInstance(response, HttpResponse) def test_bad_course_id(self): request = self.request_factory.post('dummy_url') request.user = self.mock_user with self.assertRaises(Http404): render.handle_xblock_callback( request, 'bad_course_id', quote_slashes(self.location.to_deprecated_string()), 'xmodule_handler', 'goto_position', ) def test_bad_location(self): request = self.request_factory.post('dummy_url') request.user = self.mock_user with self.assertRaises(Http404): render.handle_xblock_callback( request, self.course_key.to_deprecated_string(), quote_slashes( self.course_key.make_usage_key( 'chapter', 'bad_location').to_deprecated_string()), 'xmodule_handler', 'goto_position', ) def test_bad_xmodule_dispatch(self): request = self.request_factory.post('dummy_url') request.user = self.mock_user with self.assertRaises(Http404): render.handle_xblock_callback( request, self.course_key.to_deprecated_string(), quote_slashes(self.location.to_deprecated_string()), 'xmodule_handler', 'bad_dispatch', ) def test_missing_handler(self): request = self.request_factory.post('dummy_url') request.user = self.mock_user with self.assertRaises(Http404): render.handle_xblock_callback( request, self.course_key.to_deprecated_string(), quote_slashes(self.location.to_deprecated_string()), 'bad_handler', 'bad_dispatch', )
class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase): ''' Check that staff grading service proxy works. Basically just checking the access control and error handling logic -- all the actual work is on the backend. ''' def setUp(self): self.student = '*****@*****.**' self.instructor = '*****@*****.**' self.password = '******' self.create_account('u1', self.student, self.password) self.create_account('u2', self.instructor, self.password) self.activate_user(self.student) self.activate_user(self.instructor) self.course_id = SlashSeparatedCourseKey("edX", "toy", "2012_Fall") self.location_string = self.course_id.make_usage_key('html', 'TestLocation').to_deprecated_string() self.toy = modulestore().get_course(self.course_id) location = "i4x://edX/toy/peergrading/init" field_data = DictFieldData({'data': "<peergrading/>", 'location': location, 'category': 'peergrading'}) self.mock_service = peer_grading_service.MockPeerGradingService() self.system = LmsModuleSystem( static_url=settings.STATIC_URL, track_function=None, get_module=None, render_template=render_to_string, replace_urls=None, s3_interface=test_util_open_ended.S3_INTERFACE, open_ended_grading_interface=test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE, mixins=settings.XBLOCK_MIXINS, error_descriptor_class=ErrorDescriptor, descriptor_runtime=None, ) self.descriptor = peer_grading_module.PeerGradingDescriptor(self.system, field_data, ScopeIds(None, None, None, None)) self.descriptor.xmodule_runtime = self.system self.peer_module = self.descriptor self.peer_module.peer_gs = self.mock_service self.logout() def test_get_next_submission_success(self): data = {'location': self.location_string} response = self.peer_module.get_next_submission(data) content = response self.assertTrue(content['success']) self.assertIsNotNone(content['submission_id']) self.assertIsNotNone(content['prompt']) self.assertIsNotNone(content['submission_key']) self.assertIsNotNone(content['max_score']) def test_get_next_submission_missing_location(self): data = {} d = self.peer_module.get_next_submission(data) self.assertFalse(d['success']) self.assertEqual(d['error'], "Missing required keys: location") def test_save_grade_success(self): data = { 'rubric_scores[]': [0, 0], 'location': self.location_string, 'submission_id': 1, 'submission_key': 'fake key', 'score': 2, 'feedback': 'feedback', 'submission_flagged': 'false', 'answer_unknown': 'false', 'rubric_scores_complete': 'true' } qdict = MagicMock() def fake_get_item(key): return data[key] qdict.__getitem__.side_effect = fake_get_item qdict.getlist = fake_get_item qdict.keys = data.keys response = self.peer_module.save_grade(qdict) self.assertTrue(response['success']) def test_save_grade_missing_keys(self): data = {} d = self.peer_module.save_grade(data) self.assertFalse(d['success']) self.assertTrue(d['error'].find('Missing required keys:') > -1) def test_is_calibrated_success(self): data = {'location': self.location_string} response = self.peer_module.is_student_calibrated(data) self.assertTrue(response['success']) self.assertTrue('calibrated' in response) def test_is_calibrated_failure(self): data = {} response = self.peer_module.is_student_calibrated(data) self.assertFalse(response['success']) self.assertFalse('calibrated' in response) def test_show_calibration_essay_success(self): data = {'location': self.location_string} response = self.peer_module.show_calibration_essay(data) self.assertTrue(response['success']) self.assertIsNotNone(response['submission_id']) self.assertIsNotNone(response['prompt']) self.assertIsNotNone(response['submission_key']) self.assertIsNotNone(response['max_score']) def test_show_calibration_essay_missing_key(self): data = {} response = self.peer_module.show_calibration_essay(data) self.assertFalse(response['success']) self.assertEqual(response['error'], "Missing required keys: location") def test_save_calibration_essay_success(self): data = { 'rubric_scores[]': [0, 0], 'location': self.location_string, 'submission_id': 1, 'submission_key': 'fake key', 'score': 2, 'feedback': 'feedback', 'submission_flagged': 'false' } qdict = MagicMock() def fake_get_item(key): return data[key] qdict.__getitem__.side_effect = fake_get_item qdict.getlist = fake_get_item qdict.keys = data.keys response = self.peer_module.save_calibration_essay(qdict) self.assertTrue(response['success']) self.assertTrue('actual_score' in response) def test_save_calibration_essay_missing_keys(self): data = {} response = self.peer_module.save_calibration_essay(data) self.assertFalse(response['success']) self.assertTrue(response['error'].find('Missing required keys:') > -1) self.assertFalse('actual_score' in response) def test_save_grade_with_long_feedback(self): """ Test if feedback is too long save_grade() should return error message. """ data = { 'rubric_scores[]': [0, 0], 'location': self.location_string, 'submission_id': 1, 'submission_key': 'fake key', 'score': 2, 'feedback': '', 'submission_flagged': 'false', 'answer_unknown': 'false', 'rubric_scores_complete': 'true' } feedback_fragment = "This is very long feedback." data["feedback"] = feedback_fragment * ( (staff_grading_service.MAX_ALLOWED_FEEDBACK_LENGTH / len(feedback_fragment) + 1) ) response_dict = self.peer_module.save_grade(data) # Should not succeed. self.assertEquals(response_dict['success'], False) self.assertEquals( response_dict['error'], "Feedback is too long, Max length is {0} characters.".format( staff_grading_service.MAX_ALLOWED_FEEDBACK_LENGTH ) )
class TestStaffGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase): ''' Check that staff grading service proxy works. Basically just checking the access control and error handling logic -- all the actual work is on the backend. ''' MODULESTORE = TEST_DATA_MIXED_TOY_MODULESTORE def setUp(self): super(TestStaffGradingService, self).setUp() self.student = '*****@*****.**' self.instructor = '*****@*****.**' self.password = '******' self.create_account('u1', self.student, self.password) self.create_account('u2', self.instructor, self.password) self.activate_user(self.student) self.activate_user(self.instructor) self.course_id = SlashSeparatedCourseKey("edX", "toy", "2012_Fall") self.location_string = self.course_id.make_usage_key( 'html', 'TestLocation').to_deprecated_string() self.toy = modulestore().get_course(self.course_id) make_instructor(self.toy, self.instructor) self.mock_service = staff_grading_service.staff_grading_service() self.logout() def test_access(self): """ Make sure only staff have access. """ self.login(self.student, self.password) # both get and post should return 404 for view_name in ('staff_grading_get_next', 'staff_grading_save_grade'): url = reverse( view_name, kwargs={'course_id': self.course_id.to_deprecated_string()}) self.assert_request_status_code(404, url, method="GET") self.assert_request_status_code(404, url, method="POST") def test_get_next(self): self.login(self.instructor, self.password) url = reverse( 'staff_grading_get_next', kwargs={'course_id': self.course_id.to_deprecated_string()}) data = {'location': self.location_string} response = self.assert_request_status_code(200, url, method="POST", data=data) content = json.loads(response.content) self.assertTrue(content['success']) self.assertEquals(content['submission_id'], self.mock_service.cnt) self.assertIsNotNone(content['submission']) self.assertIsNotNone(content['num_graded']) self.assertIsNotNone(content['min_for_ml']) self.assertIsNotNone(content['num_pending']) self.assertIsNotNone(content['prompt']) self.assertIsNotNone(content['ml_error_info']) self.assertIsNotNone(content['max_score']) self.assertIsNotNone(content['rubric']) def save_grade_base(self, skip=False): self.login(self.instructor, self.password) url = reverse( 'staff_grading_save_grade', kwargs={'course_id': self.course_id.to_deprecated_string()}) data = { 'score': '12', 'feedback': 'great!', 'submission_id': '123', 'location': self.location_string, 'submission_flagged': "true", 'rubric_scores[]': ['1', '2'] } if skip: data.update({'skipped': True}) response = self.assert_request_status_code(200, url, method="POST", data=data) content = json.loads(response.content) self.assertTrue(content['success'], str(content)) self.assertEquals(content['submission_id'], self.mock_service.cnt) def test_save_grade(self): self.save_grade_base(skip=False) def test_save_grade_skip(self): self.save_grade_base(skip=True) def test_get_problem_list(self): self.login(self.instructor, self.password) url = reverse( 'staff_grading_get_problem_list', kwargs={'course_id': self.course_id.to_deprecated_string()}) data = {} response = self.assert_request_status_code(200, url, method="POST", data=data) content = json.loads(response.content) self.assertTrue(content['success']) self.assertEqual(content['problem_list'], []) @patch('open_ended_grading.staff_grading_service._service', EmptyStaffGradingService()) def test_get_problem_list_missing(self): """ Test to see if a staff grading response missing a problem list is given the appropriate error. Mock the staff grading service to enable the key to be missing. """ # Get a valid user object. instructor = User.objects.get(email=self.instructor) # Mock a request object. request = Mock(user=instructor, ) # Get the response and load its content. response = json.loads( staff_grading_service.get_problem_list( request, self.course_id.to_deprecated_string()).content) # A valid response will have an "error" key. self.assertTrue('error' in response) # Check that the error text is correct. self.assertIn("Cannot find", response['error']) def test_save_grade_with_long_feedback(self): """ Test if feedback is too long save_grade() should return error message. """ self.login(self.instructor, self.password) url = reverse( 'staff_grading_save_grade', kwargs={'course_id': self.course_id.to_deprecated_string()}) data = { 'score': '12', 'feedback': '', 'submission_id': '123', 'location': self.location_string, 'submission_flagged': "false", 'rubric_scores[]': ['1', '2'] } feedback_fragment = "This is very long feedback." data["feedback"] = feedback_fragment * ( (staff_grading_service.MAX_ALLOWED_FEEDBACK_LENGTH / len(feedback_fragment) + 1)) response = self.assert_request_status_code(200, url, method="POST", data=data) content = json.loads(response.content) # Should not succeed. self.assertEquals(content['success'], False) self.assertEquals( content['error'], "Feedback is too long, Max length is {0} characters.".format( staff_grading_service.MAX_ALLOWED_FEEDBACK_LENGTH))
class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase): ''' Check that staff grading service proxy works. Basically just checking the access control and error handling logic -- all the actual work is on the backend. ''' def setUp(self): super(TestPeerGradingService, self).setUp() self.student = '*****@*****.**' self.instructor = '*****@*****.**' self.password = '******' self.create_account('u1', self.student, self.password) self.create_account('u2', self.instructor, self.password) self.activate_user(self.student) self.activate_user(self.instructor) self.course_id = SlashSeparatedCourseKey("edX", "toy", "2012_Fall") self.location_string = self.course_id.make_usage_key( 'html', 'TestLocation').to_deprecated_string() self.toy = modulestore().get_course(self.course_id) location = "i4x://edX/toy/peergrading/init" field_data = DictFieldData({ 'data': "<peergrading/>", 'location': location, 'category': 'peergrading' }) self.mock_service = peer_grading_service.MockPeerGradingService() self.system = LmsModuleSystem( static_url=settings.STATIC_URL, track_function=None, get_module=None, render_template=render_to_string, replace_urls=None, s3_interface=test_util_open_ended.S3_INTERFACE, open_ended_grading_interface=test_util_open_ended. OPEN_ENDED_GRADING_INTERFACE, mixins=settings.XBLOCK_MIXINS, error_descriptor_class=ErrorDescriptor, descriptor_runtime=Mock(spec=DescriptorSystem, name="descriptor_runtime"), ) self.descriptor = peer_grading_module.PeerGradingDescriptor( self.system, field_data, ScopeIds(None, None, None, None)) self.descriptor.xmodule_runtime = self.system self.peer_module = self.descriptor self.peer_module.peer_gs = self.mock_service self.logout() def test_get_next_submission_success(self): data = {'location': self.location_string} response = self.peer_module.get_next_submission(data) content = response self.assertTrue(content['success']) self.assertIsNotNone(content['submission_id']) self.assertIsNotNone(content['prompt']) self.assertIsNotNone(content['submission_key']) self.assertIsNotNone(content['max_score']) def test_get_next_submission_missing_location(self): data = {} d = self.peer_module.get_next_submission(data) self.assertFalse(d['success']) self.assertEqual(d['error'], "Missing required keys: location") def test_save_grade_success(self): data = { 'rubric_scores[]': [0, 0], 'location': self.location_string, 'submission_id': 1, 'submission_key': 'fake key', 'score': 2, 'feedback': 'feedback', 'submission_flagged': 'false', 'answer_unknown': 'false', 'rubric_scores_complete': 'true' } qdict = MagicMock() def fake_get_item(key): return data[key] qdict.__getitem__.side_effect = fake_get_item qdict.getlist = fake_get_item qdict.keys = data.keys response = self.peer_module.save_grade(qdict) self.assertTrue(response['success']) def test_save_grade_missing_keys(self): data = {} d = self.peer_module.save_grade(data) self.assertFalse(d['success']) self.assertTrue(d['error'].find('Missing required keys:') > -1) def test_is_calibrated_success(self): data = {'location': self.location_string} response = self.peer_module.is_student_calibrated(data) self.assertTrue(response['success']) self.assertTrue('calibrated' in response) def test_is_calibrated_failure(self): data = {} response = self.peer_module.is_student_calibrated(data) self.assertFalse(response['success']) self.assertFalse('calibrated' in response) def test_show_calibration_essay_success(self): data = {'location': self.location_string} response = self.peer_module.show_calibration_essay(data) self.assertTrue(response['success']) self.assertIsNotNone(response['submission_id']) self.assertIsNotNone(response['prompt']) self.assertIsNotNone(response['submission_key']) self.assertIsNotNone(response['max_score']) def test_show_calibration_essay_missing_key(self): data = {} response = self.peer_module.show_calibration_essay(data) self.assertFalse(response['success']) self.assertEqual(response['error'], "Missing required keys: location") def test_save_calibration_essay_success(self): data = { 'rubric_scores[]': [0, 0], 'location': self.location_string, 'submission_id': 1, 'submission_key': 'fake key', 'score': 2, 'feedback': 'feedback', 'submission_flagged': 'false' } qdict = MagicMock() def fake_get_item(key): return data[key] qdict.__getitem__.side_effect = fake_get_item qdict.getlist = fake_get_item qdict.keys = data.keys response = self.peer_module.save_calibration_essay(qdict) self.assertTrue(response['success']) self.assertTrue('actual_score' in response) def test_save_calibration_essay_missing_keys(self): data = {} response = self.peer_module.save_calibration_essay(data) self.assertFalse(response['success']) self.assertTrue(response['error'].find('Missing required keys:') > -1) self.assertFalse('actual_score' in response) def test_save_grade_with_long_feedback(self): """ Test if feedback is too long save_grade() should return error message. """ data = { 'rubric_scores[]': [0, 0], 'location': self.location_string, 'submission_id': 1, 'submission_key': 'fake key', 'score': 2, 'feedback': '', 'submission_flagged': 'false', 'answer_unknown': 'false', 'rubric_scores_complete': 'true' } feedback_fragment = "This is very long feedback." data["feedback"] = feedback_fragment * ( (staff_grading_service.MAX_ALLOWED_FEEDBACK_LENGTH / len(feedback_fragment) + 1)) response_dict = self.peer_module.save_grade(data) # Should not succeed. self.assertEquals(response_dict['success'], False) self.assertEquals( response_dict['error'], "Feedback is too long, Max length is {0} characters.".format( staff_grading_service.MAX_ALLOWED_FEEDBACK_LENGTH))
class TestMongoKeyValueStore(object): """ Tests for MongoKeyValueStore. """ def setUp(self): self.data = {'foo': 'foo_value'} self.course_id = SlashSeparatedCourseKey('org', 'course', 'run') self.children = [self.course_id.make_usage_key('child', 'a'), self.course_id.make_usage_key('child', 'b')] self.metadata = {'meta': 'meta_val'} self.kvs = MongoKeyValueStore(self.data, self.children, self.metadata) def test_read(self): assert_equals(self.data['foo'], self.kvs.get(KeyValueStore.Key(Scope.content, None, None, 'foo'))) assert_equals(self.children, self.kvs.get(KeyValueStore.Key(Scope.children, None, None, 'children'))) assert_equals(self.metadata['meta'], self.kvs.get(KeyValueStore.Key(Scope.settings, None, None, 'meta'))) assert_equals(None, self.kvs.get(KeyValueStore.Key(Scope.parent, None, None, 'parent'))) def test_read_invalid_scope(self): for scope in (Scope.preferences, Scope.user_info, Scope.user_state): key = KeyValueStore.Key(scope, None, None, 'foo') with assert_raises(InvalidScopeError): self.kvs.get(key) assert_false(self.kvs.has(key)) def test_read_non_dict_data(self): self.kvs = MongoKeyValueStore('xml_data', self.children, self.metadata) assert_equals('xml_data', self.kvs.get(KeyValueStore.Key(Scope.content, None, None, 'data'))) def _check_write(self, key, value): self.kvs.set(key, value) assert_equals(value, self.kvs.get(key)) def test_write(self): yield (self._check_write, KeyValueStore.Key(Scope.content, None, None, 'foo'), 'new_data') yield (self._check_write, KeyValueStore.Key(Scope.children, None, None, 'children'), []) yield (self._check_write, KeyValueStore.Key(Scope.settings, None, None, 'meta'), 'new_settings') def test_write_non_dict_data(self): self.kvs = MongoKeyValueStore('xml_data', self.children, self.metadata) self._check_write(KeyValueStore.Key(Scope.content, None, None, 'data'), 'new_data') def test_write_invalid_scope(self): for scope in (Scope.preferences, Scope.user_info, Scope.user_state, Scope.parent): with assert_raises(InvalidScopeError): self.kvs.set(KeyValueStore.Key(scope, None, None, 'foo'), 'new_value') def _check_delete_default(self, key, default_value): self.kvs.delete(key) assert_equals(default_value, self.kvs.get(key)) assert self.kvs.has(key) def _check_delete_key_error(self, key): self.kvs.delete(key) with assert_raises(KeyError): self.kvs.get(key) assert_false(self.kvs.has(key)) def test_delete(self): yield (self._check_delete_key_error, KeyValueStore.Key(Scope.content, None, None, 'foo')) yield (self._check_delete_default, KeyValueStore.Key(Scope.children, None, None, 'children'), []) yield (self._check_delete_key_error, KeyValueStore.Key(Scope.settings, None, None, 'meta')) def test_delete_invalid_scope(self): for scope in (Scope.preferences, Scope.user_info, Scope.user_state, Scope.parent): with assert_raises(InvalidScopeError): self.kvs.delete(KeyValueStore.Key(scope, None, None, 'foo'))
class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase): """ Test the handle_xblock_callback function """ def setUp(self): self.course_key = SlashSeparatedCourseKey("edX", "toy", "2012_Fall") self.location = self.course_key.make_usage_key("chapter", "Overview") self.toy_course = modulestore().get_course(self.course_key) self.mock_user = UserFactory() self.mock_user.id = 1 self.request_factory = RequestFactory() # Construct a mock module for the modulestore to return self.mock_module = MagicMock() self.mock_module.id = 1 self.dispatch = "score_update" # Construct a 'standard' xqueue_callback url self.callback_url = reverse( "xqueue_callback", kwargs={ "course_id": self.course_key.to_deprecated_string(), "userid": str(self.mock_user.id), "mod_id": self.mock_module.id, "dispatch": self.dispatch, }, ) def _mock_file(self, name="file", size=10): """Create a mock file object for testing uploads""" mock_file = MagicMock(size=size, read=lambda: "x" * size) # We can't use `name` as a kwarg to Mock to set the name attribute # because mock uses `name` to name the mock itself mock_file.name = name return mock_file def test_invalid_location(self): request = self.request_factory.post("dummy_url", data={"position": 1}) request.user = self.mock_user with self.assertRaises(Http404): render.handle_xblock_callback( request, self.course_key.to_deprecated_string(), "invalid Location", "dummy_handler" "dummy_dispatch" ) def test_too_many_files(self): request = self.request_factory.post( "dummy_url", data={"file_id": (self._mock_file(),) * (settings.MAX_FILEUPLOADS_PER_INPUT + 1)} ) request.user = self.mock_user self.assertEquals( render.handle_xblock_callback( request, self.course_key.to_deprecated_string(), quote_slashes(self.location.to_deprecated_string()), "dummy_handler", ).content, json.dumps( { "success": "Submission aborted! Maximum %d files may be submitted at once" % settings.MAX_FILEUPLOADS_PER_INPUT } ), ) def test_too_large_file(self): inputfile = self._mock_file(size=1 + settings.STUDENT_FILEUPLOAD_MAX_SIZE) request = self.request_factory.post("dummy_url", data={"file_id": inputfile}) request.user = self.mock_user self.assertEquals( render.handle_xblock_callback( request, self.course_key.to_deprecated_string(), quote_slashes(self.location.to_deprecated_string()), "dummy_handler", ).content, json.dumps( { "success": 'Submission aborted! Your file "%s" is too large (max size: %d MB)' % (inputfile.name, settings.STUDENT_FILEUPLOAD_MAX_SIZE / (1000 ** 2)) } ), ) def test_xmodule_dispatch(self): request = self.request_factory.post("dummy_url", data={"position": 1}) request.user = self.mock_user response = render.handle_xblock_callback( request, self.course_key.to_deprecated_string(), quote_slashes(self.location.to_deprecated_string()), "xmodule_handler", "goto_position", ) self.assertIsInstance(response, HttpResponse) def test_bad_course_id(self): request = self.request_factory.post("dummy_url") request.user = self.mock_user with self.assertRaises(Http404): render.handle_xblock_callback( request, "bad_course_id", quote_slashes(self.location.to_deprecated_string()), "xmodule_handler", "goto_position", ) def test_bad_location(self): request = self.request_factory.post("dummy_url") request.user = self.mock_user with self.assertRaises(Http404): render.handle_xblock_callback( request, self.course_key.to_deprecated_string(), quote_slashes(self.course_key.make_usage_key("chapter", "bad_location").to_deprecated_string()), "xmodule_handler", "goto_position", ) def test_bad_xmodule_dispatch(self): request = self.request_factory.post("dummy_url") request.user = self.mock_user with self.assertRaises(Http404): render.handle_xblock_callback( request, self.course_key.to_deprecated_string(), quote_slashes(self.location.to_deprecated_string()), "xmodule_handler", "bad_dispatch", ) def test_missing_handler(self): request = self.request_factory.post("dummy_url") request.user = self.mock_user with self.assertRaises(Http404): render.handle_xblock_callback( request, self.course_key.to_deprecated_string(), quote_slashes(self.location.to_deprecated_string()), "bad_handler", "bad_dispatch", )
class TestStaffGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase): ''' Check that staff grading service proxy works. Basically just checking the access control and error handling logic -- all the actual work is on the backend. ''' def setUp(self): self.student = '*****@*****.**' self.instructor = '*****@*****.**' self.password = '******' self.create_account('u1', self.student, self.password) self.create_account('u2', self.instructor, self.password) self.activate_user(self.student) self.activate_user(self.instructor) self.course_id = SlashSeparatedCourseKey("edX", "toy", "2012_Fall") self.location_string = self.course_id.make_usage_key('html', 'TestLocation').to_deprecated_string() self.toy = modulestore().get_course(self.course_id) make_instructor(self.toy, self.instructor) self.mock_service = staff_grading_service.staff_grading_service() self.logout() def test_access(self): """ Make sure only staff have access. """ self.login(self.student, self.password) # both get and post should return 404 for view_name in ('staff_grading_get_next', 'staff_grading_save_grade'): url = reverse(view_name, kwargs={'course_id': self.course_id.to_deprecated_string()}) self.assert_request_status_code(404, url, method="GET") self.assert_request_status_code(404, url, method="POST") def test_get_next(self): self.login(self.instructor, self.password) url = reverse('staff_grading_get_next', kwargs={'course_id': self.course_id.to_deprecated_string()}) data = {'location': self.location_string} response = self.assert_request_status_code(200, url, method="POST", data=data) content = json.loads(response.content) self.assertTrue(content['success']) self.assertEquals(content['submission_id'], self.mock_service.cnt) self.assertIsNotNone(content['submission']) self.assertIsNotNone(content['num_graded']) self.assertIsNotNone(content['min_for_ml']) self.assertIsNotNone(content['num_pending']) self.assertIsNotNone(content['prompt']) self.assertIsNotNone(content['ml_error_info']) self.assertIsNotNone(content['max_score']) self.assertIsNotNone(content['rubric']) def save_grade_base(self, skip=False): self.login(self.instructor, self.password) url = reverse('staff_grading_save_grade', kwargs={'course_id': self.course_id.to_deprecated_string()}) data = {'score': '12', 'feedback': 'great!', 'submission_id': '123', 'location': self.location_string, 'submission_flagged': "true", 'rubric_scores[]': ['1', '2']} if skip: data.update({'skipped': True}) response = self.assert_request_status_code(200, url, method="POST", data=data) content = json.loads(response.content) self.assertTrue(content['success'], str(content)) self.assertEquals(content['submission_id'], self.mock_service.cnt) def test_save_grade(self): self.save_grade_base(skip=False) def test_save_grade_skip(self): self.save_grade_base(skip=True) def test_get_problem_list(self): self.login(self.instructor, self.password) url = reverse('staff_grading_get_problem_list', kwargs={'course_id': self.course_id.to_deprecated_string()}) data = {} response = self.assert_request_status_code(200, url, method="POST", data=data) content = json.loads(response.content) self.assertTrue(content['success']) self.assertEqual(content['problem_list'], []) @patch('open_ended_grading.staff_grading_service._service', EmptyStaffGradingService()) def test_get_problem_list_missing(self): """ Test to see if a staff grading response missing a problem list is given the appropriate error. Mock the staff grading service to enable the key to be missing. """ # Get a valid user object. instructor = User.objects.get(email=self.instructor) # Mock a request object. request = Mock( user=instructor, ) # Get the response and load its content. response = json.loads(staff_grading_service.get_problem_list(request, self.course_id.to_deprecated_string()).content) # A valid response will have an "error" key. self.assertTrue('error' in response) # Check that the error text is correct. self.assertIn("Cannot find", response['error']) def test_save_grade_with_long_feedback(self): """ Test if feedback is too long save_grade() should return error message. """ self.login(self.instructor, self.password) url = reverse('staff_grading_save_grade', kwargs={'course_id': self.course_id.to_deprecated_string()}) data = { 'score': '12', 'feedback': '', 'submission_id': '123', 'location': self.location_string, 'submission_flagged': "false", 'rubric_scores[]': ['1', '2'] } feedback_fragment = "This is very long feedback." data["feedback"] = feedback_fragment * ( (staff_grading_service.MAX_ALLOWED_FEEDBACK_LENGTH / len(feedback_fragment) + 1) ) response = self.assert_request_status_code(200, url, method="POST", data=data) content = json.loads(response.content) # Should not succeed. self.assertEquals(content['success'], False) self.assertEquals( content['error'], "Feedback is too long, Max length is {0} characters.".format( staff_grading_service.MAX_ALLOWED_FEEDBACK_LENGTH ) )
class ModuleRenderTestCase(ModuleStoreTestCase, LoginEnrollmentTestCase): """ Tests of courseware.module_render """ def setUp(self): self.course_key = SlashSeparatedCourseKey("edX", "toy", "2012_Fall") self.location = self.course_key.make_usage_key("chapter", "Overview") self.toy_course = modulestore().get_course(self.course_key) self.mock_user = UserFactory() self.mock_user.id = 1 self.request_factory = RequestFactory() # Construct a mock module for the modulestore to return self.mock_module = MagicMock() self.mock_module.id = 1 self.dispatch = "score_update" # Construct a 'standard' xqueue_callback url self.callback_url = reverse( "xqueue_callback", kwargs=dict( course_id=self.course_key.to_deprecated_string(), userid=str(self.mock_user.id), mod_id=self.mock_module.id, dispatch=self.dispatch, ), ) def test_get_module(self): self.assertEqual(None, render.get_module("dummyuser", None, "invalid location", None)) def test_module_render_with_jump_to_id(self): """ This test validates that the /jump_to_id/<id> shorthand for intracourse linking works assertIn expected. Note there's a HTML element in the 'toy' course with the url_name 'toyjumpto' which defines this linkage """ mock_request = MagicMock() mock_request.user = self.mock_user course = get_course_with_access(self.mock_user, "load", self.course_key) field_data_cache = FieldDataCache.cache_for_descriptor_descendents( self.course_key, self.mock_user, course, depth=2 ) module = render.get_module( self.mock_user, mock_request, self.course_key.make_usage_key("html", "toyjumpto"), field_data_cache ) # get the rendered HTML output which should have the rewritten link html = module.render(STUDENT_VIEW).content # See if the url got rewritten to the target link # note if the URL mapping changes then this assertion will break self.assertIn("/courses/" + self.course_key.to_deprecated_string() + "/jump_to_id/vertical_test", html) def test_xqueue_callback_success(self): """ Test for happy-path xqueue_callback """ fake_key = "fake key" xqueue_header = json.dumps({"lms_key": fake_key}) data = {"xqueue_header": xqueue_header, "xqueue_body": "hello world"} # Patch getmodule to return our mock module with patch("courseware.module_render.find_target_student_module") as get_fake_module: get_fake_module.return_value = self.mock_module # call xqueue_callback with our mocked information request = self.request_factory.post(self.callback_url, data) render.xqueue_callback(request, self.course_key, self.mock_user.id, self.mock_module.id, self.dispatch) # Verify that handle ajax is called with the correct data request.POST["queuekey"] = fake_key self.mock_module.handle_ajax.assert_called_once_with(self.dispatch, request.POST) def test_xqueue_callback_missing_header_info(self): data = {"xqueue_header": "{}", "xqueue_body": "hello world"} with patch("courseware.module_render.find_target_student_module") as get_fake_module: get_fake_module.return_value = self.mock_module # Test with missing xqueue data with self.assertRaises(Http404): request = self.request_factory.post(self.callback_url, {}) render.xqueue_callback(request, self.course_key, self.mock_user.id, self.mock_module.id, self.dispatch) # Test with missing xqueue_header with self.assertRaises(Http404): request = self.request_factory.post(self.callback_url, data) render.xqueue_callback(request, self.course_key, self.mock_user.id, self.mock_module.id, self.dispatch) def test_get_score_bucket(self): self.assertEquals(render.get_score_bucket(0, 10), "incorrect") self.assertEquals(render.get_score_bucket(1, 10), "partial") self.assertEquals(render.get_score_bucket(10, 10), "correct") # get_score_bucket calls error cases 'incorrect' self.assertEquals(render.get_score_bucket(11, 10), "incorrect") self.assertEquals(render.get_score_bucket(-1, 10), "incorrect") def test_anonymous_handle_xblock_callback(self): dispatch_url = reverse( "xblock_handler", args=[ self.course_key.to_deprecated_string(), quote_slashes(self.course_key.make_usage_key("videosequence", "Toy_Videos").to_deprecated_string()), "xmodule_handler", "goto_position", ], ) response = self.client.post(dispatch_url, {"position": 2}) self.assertEquals(403, response.status_code) self.assertEquals("Unauthenticated", response.content) @ddt.data("pure", "vertical") @XBlock.register_temp_plugin(PureXBlock, identifier="pure") def test_rebinding_same_user(self, block_type): request = self.request_factory.get("") request.user = self.mock_user course = CourseFactory() descriptor = ItemFactory(category=block_type, parent=course) field_data_cache = FieldDataCache([self.toy_course, descriptor], self.toy_course.id, self.mock_user) render.get_module_for_descriptor(self.mock_user, request, descriptor, field_data_cache, self.toy_course.id) render.get_module_for_descriptor(self.mock_user, request, descriptor, field_data_cache, self.toy_course.id)
class TestJumpTo(ModuleStoreTestCase): """ Check the jumpto link for a course. """ MODULESTORE = TEST_DATA_MIXED_TOY_MODULESTORE def setUp(self): super(TestJumpTo, self).setUp() # Use toy course from XML self.course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') def test_jumpto_invalid_location(self): location = self.course_key.make_usage_key(None, 'NoSuchPlace') # This is fragile, but unfortunately the problem is that within the LMS we # can't use the reverse calls from the CMS jumpto_url = '{0}/{1}/jump_to/{2}'.format('/courses', self.course_key.to_deprecated_string(), location.to_deprecated_string()) response = self.client.get(jumpto_url) self.assertEqual(response.status_code, 404) @unittest.skip def test_jumpto_from_chapter(self): location = self.course_key.make_usage_key('chapter', 'Overview') jumpto_url = '{0}/{1}/jump_to/{2}'.format('/courses', self.course_key.to_deprecated_string(), location.to_deprecated_string()) expected = 'courses/edX/toy/2012_Fall/courseware/Overview/' response = self.client.get(jumpto_url) self.assertRedirects(response, expected, status_code=302, target_status_code=302) @unittest.skip def test_jumpto_id(self): jumpto_url = '{0}/{1}/jump_to_id/{2}'.format('/courses', self.course_key.to_deprecated_string(), 'Overview') expected = 'courses/edX/toy/2012_Fall/courseware/Overview/' response = self.client.get(jumpto_url) self.assertRedirects(response, expected, status_code=302, target_status_code=302) def test_jumpto_from_section(self): course = CourseFactory.create() chapter = ItemFactory.create(category='chapter', parent_location=course.location) section = ItemFactory.create(category='sequential', parent_location=chapter.location) expected = 'courses/{course_id}/courseware/{chapter_id}/{section_id}/'.format( course_id=unicode(course.id), chapter_id=chapter.url_name, section_id=section.url_name, ) jumpto_url = '{0}/{1}/jump_to/{2}'.format( '/courses', unicode(course.id), unicode(section.location), ) response = self.client.get(jumpto_url) self.assertRedirects(response, expected, status_code=302, target_status_code=302) def test_jumpto_from_module(self): course = CourseFactory.create() chapter = ItemFactory.create(category='chapter', parent_location=course.location) section = ItemFactory.create(category='sequential', parent_location=chapter.location) vertical1 = ItemFactory.create(category='vertical', parent_location=section.location) vertical2 = ItemFactory.create(category='vertical', parent_location=section.location) module1 = ItemFactory.create(category='html', parent_location=vertical1.location) module2 = ItemFactory.create(category='html', parent_location=vertical2.location) expected = 'courses/{course_id}/courseware/{chapter_id}/{section_id}/1'.format( course_id=unicode(course.id), chapter_id=chapter.url_name, section_id=section.url_name, ) jumpto_url = '{0}/{1}/jump_to/{2}'.format( '/courses', unicode(course.id), unicode(module1.location), ) response = self.client.get(jumpto_url) self.assertRedirects(response, expected, status_code=302, target_status_code=302) expected = 'courses/{course_id}/courseware/{chapter_id}/{section_id}/2'.format( course_id=unicode(course.id), chapter_id=chapter.url_name, section_id=section.url_name, ) jumpto_url = '{0}/{1}/jump_to/{2}'.format( '/courses', unicode(course.id), unicode(module2.location), ) response = self.client.get(jumpto_url) self.assertRedirects(response, expected, status_code=302, target_status_code=302) def test_jumpto_from_nested_module(self): course = CourseFactory.create() chapter = ItemFactory.create(category='chapter', parent_location=course.location) section = ItemFactory.create(category='sequential', parent_location=chapter.location) vertical = ItemFactory.create(category='vertical', parent_location=section.location) nested_section = ItemFactory.create(category='sequential', parent_location=vertical.location) nested_vertical1 = ItemFactory.create(category='vertical', parent_location=nested_section.location) # put a module into nested_vertical1 for completeness ItemFactory.create(category='html', parent_location=nested_vertical1.location) nested_vertical2 = ItemFactory.create(category='vertical', parent_location=nested_section.location) module2 = ItemFactory.create(category='html', parent_location=nested_vertical2.location) # internal position of module2 will be 1_2 (2nd item withing 1st item) expected = 'courses/{course_id}/courseware/{chapter_id}/{section_id}/1'.format( course_id=unicode(course.id), chapter_id=chapter.url_name, section_id=section.url_name, ) jumpto_url = '{0}/{1}/jump_to/{2}'.format( '/courses', unicode(course.id), unicode(module2.location), ) response = self.client.get(jumpto_url) self.assertRedirects(response, expected, status_code=302, target_status_code=302) def test_jumpto_id_invalid_location(self): location = Location('edX', 'toy', 'NoSuchPlace', None, None, None) jumpto_url = '{0}/{1}/jump_to_id/{2}'.format('/courses', self.course_key.to_deprecated_string(), location.to_deprecated_string()) response = self.client.get(jumpto_url) self.assertEqual(response.status_code, 404)
class ModuleRenderTestCase(ModuleStoreTestCase, LoginEnrollmentTestCase): """ Tests of courseware.module_render """ def setUp(self): self.course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') self.location = self.course_key.make_usage_key('chapter', 'Overview') self.toy_course = modulestore().get_course(self.course_key) self.mock_user = UserFactory() self.mock_user.id = 1 self.request_factory = RequestFactory() # Construct a mock module for the modulestore to return self.mock_module = MagicMock() self.mock_module.id = 1 self.dispatch = 'score_update' # Construct a 'standard' xqueue_callback url self.callback_url = reverse( 'xqueue_callback', kwargs=dict(course_id=self.course_key.to_deprecated_string(), userid=str(self.mock_user.id), mod_id=self.mock_module.id, dispatch=self.dispatch)) def test_get_module(self): self.assertEqual( None, render.get_module('dummyuser', None, 'invalid location', None, None)) def test_module_render_with_jump_to_id(self): """ This test validates that the /jump_to_id/<id> shorthand for intracourse linking works assertIn expected. Note there's a HTML element in the 'toy' course with the url_name 'toyjumpto' which defines this linkage """ mock_request = MagicMock() mock_request.user = self.mock_user course = get_course_with_access(self.mock_user, 'load', self.course_key) field_data_cache = FieldDataCache.cache_for_descriptor_descendents( self.course_key, self.mock_user, course, depth=2) module = render.get_module( self.mock_user, mock_request, self.course_key.make_usage_key('html', 'toyjumpto'), field_data_cache, self.course_key) # get the rendered HTML output which should have the rewritten link html = module.render('student_view').content # See if the url got rewritten to the target link # note if the URL mapping changes then this assertion will break self.assertIn( '/courses/' + self.course_key.to_deprecated_string() + '/jump_to_id/vertical_test', html) def test_xqueue_callback_success(self): """ Test for happy-path xqueue_callback """ fake_key = 'fake key' xqueue_header = json.dumps({'lms_key': fake_key}) data = { 'xqueue_header': xqueue_header, 'xqueue_body': 'hello world', } # Patch getmodule to return our mock module with patch('courseware.module_render.find_target_student_module' ) as get_fake_module: get_fake_module.return_value = self.mock_module # call xqueue_callback with our mocked information request = self.request_factory.post(self.callback_url, data) render.xqueue_callback(request, self.course_key, self.mock_user.id, self.mock_module.id, self.dispatch) # Verify that handle ajax is called with the correct data request.POST['queuekey'] = fake_key self.mock_module.handle_ajax.assert_called_once_with( self.dispatch, request.POST) def test_xqueue_callback_missing_header_info(self): data = { 'xqueue_header': '{}', 'xqueue_body': 'hello world', } with patch('courseware.module_render.find_target_student_module' ) as get_fake_module: get_fake_module.return_value = self.mock_module # Test with missing xqueue data with self.assertRaises(Http404): request = self.request_factory.post(self.callback_url, {}) render.xqueue_callback(request, self.course_key, self.mock_user.id, self.mock_module.id, self.dispatch) # Test with missing xqueue_header with self.assertRaises(Http404): request = self.request_factory.post(self.callback_url, data) render.xqueue_callback(request, self.course_key, self.mock_user.id, self.mock_module.id, self.dispatch) def test_get_score_bucket(self): self.assertEquals(render.get_score_bucket(0, 10), 'incorrect') self.assertEquals(render.get_score_bucket(1, 10), 'partial') self.assertEquals(render.get_score_bucket(10, 10), 'correct') # get_score_bucket calls error cases 'incorrect' self.assertEquals(render.get_score_bucket(11, 10), 'incorrect') self.assertEquals(render.get_score_bucket(-1, 10), 'incorrect') def test_anonymous_handle_xblock_callback(self): dispatch_url = reverse( 'xblock_handler', args=[ self.course_key.to_deprecated_string(), quote_slashes( self.course_key.make_usage_key( 'videosequence', 'Toy_Videos').to_deprecated_string()), 'xmodule_handler', 'goto_position' ]) response = self.client.post(dispatch_url, {'position': 2}) self.assertEquals(403, response.status_code)
class TestJumpTo(ModuleStoreTestCase): """ Check the jumpto link for a course. """ MODULESTORE = TEST_DATA_MIXED_TOY_MODULESTORE def setUp(self): super(TestJumpTo, self).setUp() # Use toy course from XML self.course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall') def test_jumpto_invalid_location(self): location = self.course_key.make_usage_key(None, 'NoSuchPlace') # This is fragile, but unfortunately the problem is that within the LMS we # can't use the reverse calls from the CMS jumpto_url = '{0}/{1}/jump_to/{2}'.format('/courses', self.course_key.to_deprecated_string(), location.to_deprecated_string()) response = self.client.get(jumpto_url) self.assertEqual(response.status_code, 404) @unittest.skip def test_jumpto_from_chapter(self): location = self.course_key.make_usage_key('chapter', 'Overview') jumpto_url = '{0}/{1}/jump_to/{2}'.format('/courses', self.course_key.to_deprecated_string(), location.to_deprecated_string()) expected = 'courses/edX/toy/2012_Fall/courseware/Overview/' response = self.client.get(jumpto_url) self.assertRedirects(response, expected, status_code=302, target_status_code=302) @unittest.skip def test_jumpto_id(self): jumpto_url = '{0}/{1}/jump_to_id/{2}'.format('/courses', self.course_key.to_deprecated_string(), 'Overview') expected = 'courses/edX/toy/2012_Fall/courseware/Overview/' response = self.client.get(jumpto_url) self.assertRedirects(response, expected, status_code=302, target_status_code=302) def test_jumpto_from_section(self): course = CourseFactory.create() chapter = ItemFactory.create(category='chapter', parent_location=course.location) section = ItemFactory.create(category='sequential', parent_location=chapter.location) expected = 'courses/{course_id}/courseware/{chapter_id}/{section_id}/?{activate_block_id}'.format( course_id=unicode(course.id), chapter_id=chapter.url_name, section_id=section.url_name, activate_block_id=urlencode({'activate_block_id': unicode(section.location)}) ) jumpto_url = '{0}/{1}/jump_to/{2}'.format( '/courses', unicode(course.id), unicode(section.location), ) response = self.client.get(jumpto_url) self.assertRedirects(response, expected, status_code=302, target_status_code=302) def test_jumpto_from_module(self): course = CourseFactory.create() chapter = ItemFactory.create(category='chapter', parent_location=course.location) section = ItemFactory.create(category='sequential', parent_location=chapter.location) vertical1 = ItemFactory.create(category='vertical', parent_location=section.location) vertical2 = ItemFactory.create(category='vertical', parent_location=section.location) module1 = ItemFactory.create(category='html', parent_location=vertical1.location) module2 = ItemFactory.create(category='html', parent_location=vertical2.location) expected = 'courses/{course_id}/courseware/{chapter_id}/{section_id}/1?{activate_block_id}'.format( course_id=unicode(course.id), chapter_id=chapter.url_name, section_id=section.url_name, activate_block_id=urlencode({'activate_block_id': unicode(module1.location)}) ) jumpto_url = '{0}/{1}/jump_to/{2}'.format( '/courses', unicode(course.id), unicode(module1.location), ) response = self.client.get(jumpto_url) self.assertRedirects(response, expected, status_code=302, target_status_code=302) expected = 'courses/{course_id}/courseware/{chapter_id}/{section_id}/2?{activate_block_id}'.format( course_id=unicode(course.id), chapter_id=chapter.url_name, section_id=section.url_name, activate_block_id=urlencode({'activate_block_id': unicode(module2.location)}) ) jumpto_url = '{0}/{1}/jump_to/{2}'.format( '/courses', unicode(course.id), unicode(module2.location), ) response = self.client.get(jumpto_url) self.assertRedirects(response, expected, status_code=302, target_status_code=302) def test_jumpto_from_nested_module(self): course = CourseFactory.create() chapter = ItemFactory.create(category='chapter', parent_location=course.location) section = ItemFactory.create(category='sequential', parent_location=chapter.location) vertical = ItemFactory.create(category='vertical', parent_location=section.location) nested_section = ItemFactory.create(category='sequential', parent_location=vertical.location) nested_vertical1 = ItemFactory.create(category='vertical', parent_location=nested_section.location) # put a module into nested_vertical1 for completeness ItemFactory.create(category='html', parent_location=nested_vertical1.location) nested_vertical2 = ItemFactory.create(category='vertical', parent_location=nested_section.location) module2 = ItemFactory.create(category='html', parent_location=nested_vertical2.location) # internal position of module2 will be 1_2 (2nd item withing 1st item) expected = 'courses/{course_id}/courseware/{chapter_id}/{section_id}/1?{activate_block_id}'.format( course_id=unicode(course.id), chapter_id=chapter.url_name, section_id=section.url_name, activate_block_id=urlencode({'activate_block_id': unicode(module2.location)}) ) jumpto_url = '{0}/{1}/jump_to/{2}'.format( '/courses', unicode(course.id), unicode(module2.location), ) response = self.client.get(jumpto_url) self.assertRedirects(response, expected, status_code=302, target_status_code=302) def test_jumpto_id_invalid_location(self): location = Location('edX', 'toy', 'NoSuchPlace', None, None, None) jumpto_url = '{0}/{1}/jump_to_id/{2}'.format('/courses', self.course_key.to_deprecated_string(), location.to_deprecated_string()) response = self.client.get(jumpto_url) self.assertEqual(response.status_code, 404)
def test_translate_location_read_only(self): """ Test the variants of translate_location which don't create entries, just decode """ # lookup before there are any maps org = 'foo_org' course = 'bar_course' run = 'baz_run' slash_course_key = SlashSeparatedCourseKey(org, course, run) with self.assertRaises(ItemNotFoundError): _ = loc_mapper().translate_location( Location(org, course, run, 'problem', 'abc123'), add_entry_if_missing=False ) new_style_org = '{}.geek_dept'.format(org) new_style_offering = '.{}.{}'.format(course, run) block_map = { 'abc123': {'problem': 'problem2', 'vertical': 'vertical2'}, 'def456': {'problem': 'problem4'}, 'ghi789': {'problem': 'problem7'}, } loc_mapper().create_map_entry( slash_course_key, new_style_org, new_style_offering, block_map=block_map ) test_problem_locn = Location(org, course, run, 'problem', 'abc123') self.translate_n_check(test_problem_locn, new_style_org, new_style_offering, 'problem2', 'published') # look for non-existent problem with self.assertRaises(ItemNotFoundError): loc_mapper().translate_location( Location(org, course, run, 'problem', '1def23'), add_entry_if_missing=False ) test_no_cat_locn = test_problem_locn.replace(category=None) with self.assertRaises(InvalidLocationError): loc_mapper().translate_location( slash_course_key.make_usage_key(None, 'abc123'), test_no_cat_locn, False, False ) test_no_cat_locn = test_no_cat_locn.replace(name='def456') self.translate_n_check( test_no_cat_locn, new_style_org, new_style_offering, 'problem4', 'published' ) # add a distractor course (note that abc123 has a different translation in this one) distractor_block_map = { 'abc123': {'problem': 'problem3'}, 'def456': {'problem': 'problem4'}, 'ghi789': {'problem': 'problem7'}, } run = 'delta_run' test_delta_new_org = '{}.geek_dept'.format(org) test_delta_new_offering = '{}.{}'.format(course, run) loc_mapper().create_map_entry( SlashSeparatedCourseKey(org, course, run), test_delta_new_org, test_delta_new_offering, block_map=distractor_block_map ) # test that old translation still works self.translate_n_check( test_problem_locn, new_style_org, new_style_offering, 'problem2', 'published' ) # and new returns new id self.translate_n_check( test_problem_locn.replace(run=run), test_delta_new_org, test_delta_new_offering, 'problem3', 'published' )