def test_has_changes(self): """ Tests that has_changes() only returns true when changes are present """ draft_course = CourseLocator( org='testx', course='GreekHero', run='run', branch=ModuleStoreEnum.BranchName.draft ) head = draft_course.make_usage_key('course', 'head12345') dummy_user = ModuleStoreEnum.UserID.test # Not yet published, so changes are present self.assertTrue(self.module_store.has_changes(head)) # Publish and verify that there are no unpublished changes self.module_store.publish(head, dummy_user) self.assertFalse(self.module_store.has_changes(head)) # Change the course, then check that there now are changes course = self.module_store.get_item(head) course.show_calculator = not course.show_calculator self.module_store.update_item(course, dummy_user) self.assertTrue(self.module_store.has_changes(head)) # Publish and verify again self.module_store.publish(head, dummy_user) self.assertFalse(self.module_store.has_changes(head))
def test_make_usage_key_from_deprecated_string_roundtrip(self, url): course_key = CourseLocator('org', 'course', 'run') with self.assertDeprecationWarning(count=1): self.assertEqual( url, text_type(course_key.make_usage_key_from_deprecated_string(url)) )
class TestSortedAssetList(unittest.TestCase): """ Tests the SortedAssetList class. """ shard = 1 def setUp(self): super(TestSortedAssetList, self).setUp() asset_list = [dict(zip(AssetStoreTestData.asset_fields, asset)) for asset in AssetStoreTestData.all_asset_data] self.sorted_asset_list_by_filename = SortedAssetList(iterable=asset_list) self.sorted_asset_list_by_last_edit = SortedAssetList(iterable=asset_list, key=lambda x: x['edited_on']) self.course_key = CourseLocator('org', 'course', 'run') def test_exception_on_bad_sort(self): asset_key = self.course_key.make_asset_key('asset', 'pic1.jpg') with self.assertRaises(IncorrectlySortedList): __ = self.sorted_asset_list_by_last_edit.find(asset_key) def test_find(self): asset_key = self.course_key.make_asset_key('asset', 'asset.txt') self.assertEquals(self.sorted_asset_list_by_filename.find(asset_key), 0) asset_key_last = self.course_key.make_asset_key('asset', 'weather_patterns.bmp') self.assertEquals( self.sorted_asset_list_by_filename.find(asset_key_last), len(AssetStoreTestData.all_asset_data) - 1 )
def test_course_constructor_package_id_no_branch(self): org = 'mit.eecs' offering = '6002x' testurn = '{}+{}'.format(org, offering) testobj = CourseLocator(org=org, offering=offering) self.check_course_locn_fields(testobj, org=org, offering=offering) self.assertEqual(testobj._to_string(), testurn)
def initialize_ids(self): """Define set of id values for use in tests.""" course_key = CourseLocator(org='FooX', course='1.23x', run='2013_Spring') self.course_id = unicode(course_key) self.org_id = course_key.org block_id = "9cee77a606ea4c1aa5440e0ea5d0f618" self.problem_id = unicode(course_key.make_usage_key("problem", block_id)) self.answer_id = "{block_id}_2_1".format(block_id=block_id) self.second_answer_id = "{block_id}_3_1".format(block_id=block_id)
def test_course_constructor_package_id_no_branch(self): org = 'mit.eecs' course = '6002x' run = '2014_T2' testurn = '{}+{}+{}'.format(org, course, run) testobj = CourseLocator(org=org, course=course, run=run) self.check_course_locn_fields(testobj, org=org, course=course, run=run) # Allow access to _to_string # pylint: disable=protected-access self.assertEqual(testobj._to_string(), testurn)
def instantiate_descriptor(**field_data): """ Instantiate descriptor with most properties. """ system = get_test_descriptor_system() course_key = CourseLocator('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 survey_ajax(request): """Ajax call to submit a survey.""" MAX_CHARACTER_LENGTH = 1000 course_id = request.POST.get('course_id') unit_id = request.POST.get('unit_id') survey_name = request.POST.get('survey_name') survey_answer = request.POST.get('survey_answer') if not course_id or not unit_id: log.warning("Illegal parameter. course_id=%s, unit_id=%s" % (course_id, unit_id)) raise Http404 if not survey_name: log.warning("Illegal parameter. survey_name=%s" % survey_name) raise Http404 if not survey_answer: log.warning("Illegal parameter. survey_answer=%s" % survey_answer) raise Http404 try: obj = json.loads(survey_answer) except: log.warning("Illegal parameter. survey_answer=%s" % survey_answer) raise Http404 for k, v in obj.iteritems(): if len(v) > MAX_CHARACTER_LENGTH: log.warning("%s cannot be more than %d characters long." % (k, MAX_CHARACTER_LENGTH)) raise Http404 try: submission = SurveySubmission.objects.filter( course_id=CourseLocator.from_string(course_id), unit_id=unit_id, user=request.user ).order_by('created')[0:1].get() except SurveySubmission.DoesNotExist: pass else: return JsonResponse({ 'success': False, 'survey_answer': submission.get_survey_answer(), }) submission = SurveySubmission( course_id=CourseLocator.from_string(course_id), unit_id=unit_id, user=request.user, survey_name=survey_name, survey_answer=survey_answer, ) submission.save() return JsonResponse({'success': True})
def test_course_constructor_package_id_separate_branch(self): org = 'mit.eecs' offering = '6002x' test_branch = 'published' expected_urn = '{}+{}+{}+{}'.format(org, offering, CourseLocator.BRANCH_PREFIX, test_branch) testobj = CourseLocator(org=org, offering=offering, branch=test_branch) self.check_course_locn_fields( testobj, org=org, offering=offering, branch=test_branch, ) self.assertEqual(testobj.branch, test_branch) self.assertEqual(testobj._to_string(), expected_urn)
def handle(self, *args, **options): create_report = options['create'] delete_report = options['delete'] if len(args) != 1: raise CommandError('"course_id" is not specified') elif create_report and delete_report: raise CommandError( 'Cannot specify "-c" option and "-d" option at the same time.') course_id = args[0] try: course_id = CourseLocator.from_string(course_id) except InvalidKeyError: raise CommandError("'{}' is an invalid course_id".format(course_id)) if not modulestore().get_course(course_id): raise CommandError("The specified course does not exist.") if delete_report: try: delete_pgreport_csv(course_id) except NotFoundError: raise CommandError("CSV not found.") call_command('create_report_task', *['clear_cache'], **{'course_id': course_id.to_deprecated_string()}) elif create_report: call_command('create_report_task', *['create'], **{'course_id': course_id.to_deprecated_string()}) else: try: get_pgreport_csv(course_id) except NotFoundError: raise CommandError("CSV not found.")
def import_test_course(self, solution_attribute=None, solution_element=None): """ Import the test course with the sga unit """ # adapted from edx-platform/cms/djangoapps/contentstore/management/commands/tests/test_cleanup_assets.py root = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) input_dir = os.path.join(root, "test_data") temp_dir = tempfile.mkdtemp() self.addCleanup(lambda: shutil.rmtree(temp_dir)) xml_dir = os.path.join(temp_dir, "xml") shutil.copytree(input_dir, xml_dir) with open(os.path.join(xml_dir, "2017_SGA", "vertical", "vertical.xml"), "w") as f: f.write(self.make_test_vertical(solution_attribute, solution_element)) store = modulestore() import_course_from_xml( store, 'sga_user', xml_dir, ) return store.get_course(CourseLocator.from_string('SGAU/SGA101/course'))
def recalculate_subsection_grade_v2(**kwargs): """ Updates a saved subsection grade. Arguments: user_id (int): id of applicable User object course_id (string): identifying the course usage_id (string): identifying the course block only_if_higher (boolean): indicating whether grades should be updated only if the new raw_earned is higher than the previous value. expected_modified_time (serialized timestamp): indicates when the task was queued so that we can verify the underlying data update. score_deleted (boolean): indicating whether the grade change is a result of the problem's score being deleted. event_transaction_id(string): uuid identifying the current event transaction. event_transaction_type(string): human-readable type of the event at the root of the current event transaction. """ try: course_key = CourseLocator.from_string(kwargs['course_id']) if not PersistentGradesEnabledFlag.feature_enabled(course_key): return score_deleted = kwargs['score_deleted'] scored_block_usage_key = UsageKey.from_string(kwargs['usage_id']).replace(course_key=course_key) expected_modified_time = from_timestamp(kwargs['expected_modified_time']) # The request cache is not maintained on celery workers, # where this code runs. So we take the values from the # main request cache and store them in the local request # cache. This correlates model-level grading events with # higher-level ones. set_event_transaction_id(kwargs.pop('event_transaction_id', None)) set_event_transaction_type(kwargs.pop('event_transaction_type', None)) # Verify the database has been updated with the scores when the task was # created. This race condition occurs if the transaction in the task # creator's process hasn't committed before the task initiates in the worker # process. if not _has_database_updated_with_new_score( kwargs['user_id'], scored_block_usage_key, expected_modified_time, score_deleted, ): raise _retry_recalculate_subsection_grade(**kwargs) _update_subsection_grades( course_key, scored_block_usage_key, kwargs['only_if_higher'], kwargs['user_id'], ) except Exception as exc: # pylint: disable=broad-except if not isinstance(exc, KNOWN_RETRY_ERRORS): log.info("tnl-6244 grades unexpected failure: {}. kwargs={}".format( repr(exc), kwargs )) raise _retry_recalculate_subsection_grade(exc=exc, **kwargs)
def set_up_assets(self, deprecated): """ Setup contentstore w/ proper overriding of deprecated. """ # since MongoModuleStore and MongoContentStore are basically assumed to be together, create this class # as well self.contentstore = MongoContentStore(HOST, DB, port=PORT) self.addCleanup(self.contentstore._drop_database) # pylint: disable=protected-access AssetLocator.deprecated = deprecated CourseLocator.deprecated = deprecated self.course1_key = CourseLocator('test', 'asset_test', '2014_07') self.course2_key = CourseLocator('test', 'asset_test2', '2014_07') self.course1_files = ['contains.sh', 'picture1.jpg', 'picture2.jpg'] self.course2_files = ['picture1.jpg', 'picture3.jpg', 'door_2.ogg'] def load_assets(course_key, files): locked = False for filename in files: asset_key = course_key.make_asset_key('asset', filename) self.save_asset(filename, asset_key, filename, locked) locked = not locked load_assets(self.course1_key, self.course1_files) load_assets(self.course2_key, self.course2_files)
def get(self, request, course_id): """Implements the GET method as described in the class docstring.""" course_key = CourseLocator.from_string(course_id) course = get_course_with_access(request.user, 'load_forum', course_key) if not any([isinstance(tab, DiscussionTab) for tab in course.tabs]): raise Http404 return Response(get_course_topics(course, request.user))
def clean_course_id(self): """Validate course_id""" value = self.cleaned_data["course_id"] try: return CourseLocator.from_string(value) except InvalidKeyError: raise ValidationError("'{}' is not a valid course id".format(value))
def test_gitlog_pagination_out_of_range_invalid(self): """ Make sure the pagination behaves properly when the requested page is out of range. """ self._setstaff_login() mongoengine.connect(TEST_MONGODB_LOG['db']) for _ in xrange(15): CourseImportLog( course_id=CourseLocator.from_string("test/test/test"), location="location", import_log="import_log", git_log="git_log", repo_dir="repo_dir", created=datetime.now() ).save() for page, expected in [(-1, 1), (1, 1), (2, 2), (30, 2), ('abc', 1)]: response = self.client.get( '{}?page={}'.format( reverse('gitlogs'), page ) ) self.assertIn( u'Page {} of 2'.format(expected), response.content.decode(response.charset) ) CourseImportLog.objects.delete()
def initdb(self, default): """ Initialize the database and create one test course in it """ # set the default modulestore store_configs = self.options['stores'] for index in range(len(store_configs)): if store_configs[index]['NAME'] == default: if index > 0: store_configs[index], store_configs[0] = store_configs[0], store_configs[index] break self.store = MixedModuleStore(None, create_modulestore_instance=create_modulestore_instance, **self.options) self.addCleanup(self.store.close_all_connections) # convert to CourseKeys self.course_locations = { course_id: CourseLocator.from_string(course_id) for course_id in [self.MONGO_COURSEID, self.XML_COURSEID1, self.XML_COURSEID2] } # and then to the root UsageKey self.course_locations = { course_id: course_key.make_usage_key('course', course_key.run) for course_id, course_key in self.course_locations.iteritems() # pylint: disable=maybe-no-member } if default == 'split': self.fake_location = CourseLocator( 'foo', 'bar', 'slowly', branch=ModuleStoreEnum.BranchName.draft ).make_usage_key('vertical', 'baz') else: self.fake_location = Location('foo', 'bar', 'slowly', 'vertical', 'baz') self.xml_chapter_location = self.course_locations[self.XML_COURSEID1].replace( category='chapter', name='Overview' ) self._create_course(default, self.course_locations[self.MONGO_COURSEID].course_key)
def recalculate_subsection_grade_handler(sender, **kwargs): # pylint: disable=unused-argument """ Consume the SCORE_CHANGED signal and trigger an update. This method expects that the kwargs dictionary will contain the following entries (See the definition of SCORE_CHANGED): - points_possible: Maximum score available for the exercise - points_earned: Score obtained by the user - user: User object - course_id: Unicode string representing the course - usage_id: Unicode string indicating the courseware instance """ try: course_id = kwargs.get('course_id', None) usage_id = kwargs.get('usage_id', None) student = kwargs.get('user', None) course_key = CourseLocator.from_string(course_id) if not PersistentGradesEnabledFlag.feature_enabled(course_key): return usage_key = UsageKey.from_string(usage_id).replace(course_key=course_key) from lms.djangoapps.grades.new.subsection_grade import SubsectionGradeFactory SubsectionGradeFactory(student).update(usage_key, course_key) except Exception as ex: # pylint: disable=broad-except log.exception( u"Failed to process SCORE_CHANGED signal. " "user: %s, course_id: %s, " "usage_id: %s. Exception: %s", unicode(student), course_id, usage_id, ex.message )
def setUp(self): super(SetupTestErrorModules, self).setUp() self.system = get_test_system() self.course_id = CourseLocator('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 setUp(self): super(ApiTest, self).setUp() self.client = Client() # Mocks patcher = patch.object(api, 'api_enabled', Mock(return_value=True)) patcher.start() self.addCleanup(patcher.stop) # Create two accounts self.password = '******' self.student = User.objects.create_user('student', '*****@*****.**', self.password) self.student2 = User.objects.create_user('student2', '*****@*****.**', self.password) self.instructor = User.objects.create_user('instructor', '*****@*****.**', self.password) self.course_key = CourseLocator('HarvardX', 'CB22x', 'The_Ancient_Greek_Hero') self.note = { 'user': self.student, 'course_id': self.course_key, 'uri': '/', 'text': 'foo', 'quote': 'bar', 'range_start': 0, 'range_start_offset': 0, 'range_end': 100, 'range_end_offset': 0, 'tags': 'a,b,c' } # Make sure no note with this ID ever exists for testing purposes self.NOTE_ID_DOES_NOT_EXIST = 99999
def setUp(self): super(TestSortedAssetList, self).setUp() asset_list = [dict(zip(AssetStoreTestData.asset_fields, asset)) for asset in AssetStoreTestData.all_asset_data] self.sorted_asset_list_by_filename = SortedAssetList(iterable=asset_list) self.sorted_asset_list_by_last_edit = SortedAssetList(iterable=asset_list, key=lambda x: x['edited_on']) self.course_key = CourseLocator('org', 'course', 'run')
def generate_course_key(org, number, run): """ Makes a CourseLocator from org, number and run """ default_store = os.environ.get('DEFAULT_STORE', 'draft') return CourseLocator(org, number, run, deprecated=(default_store == 'draft'))
class CertificatesBrandingTest(TestCase): """Test certificates branding. """ COURSE_KEY = CourseLocator(org='test', course='test', run='test') configuration = { 'logo_image_url': 'test_site/images/header-logo.png', 'SITE_NAME': 'test_site.localhost', 'urls': { 'ABOUT': 'test-site/about', 'PRIVACY': 'test-site/privacy', 'TOS_AND_HONOR': 'test-site/tos-and-honor', }, } @with_site_configuration(configuration=configuration) def test_certificate_header_data(self): """ Test that get_certificate_header_context from lms.djangoapps.certificates api returns data customized according to site branding. """ # Generate certificates for the course CourseModeFactory.create(course_id=self.COURSE_KEY, mode_slug=CourseMode.HONOR) data = certs_api.get_certificate_header_context(is_secure=True) # Make sure there are not unexpected keys in dict returned by 'get_certificate_header_context' self.assertItemsEqual( list(data.keys()), ['logo_src', 'logo_url'] ) self.assertIn( self.configuration['logo_image_url'], data['logo_src'] ) self.assertIn( self.configuration['SITE_NAME'], data['logo_url'] ) @with_site_configuration(configuration=configuration) def test_certificate_footer_data(self): """ Test that get_certificate_footer_context from lms.djangoapps.certificates api returns data customized according to site branding. """ # Generate certificates for the course CourseModeFactory.create(course_id=self.COURSE_KEY, mode_slug=CourseMode.HONOR) data = certs_api.get_certificate_footer_context() # Make sure there are not unexpected keys in dict returned by 'get_certificate_footer_context' self.assertItemsEqual( list(data.keys()), ['company_about_url', 'company_privacy_url', 'company_tos_url'] ) self.assertIn( self.configuration['urls']['ABOUT'], data['company_about_url'] ) self.assertIn( self.configuration['urls']['PRIVACY'], data['company_privacy_url'] ) self.assertIn( self.configuration['urls']['TOS_AND_HONOR'], data['company_tos_url'] )
def setUp(self): super(ReportStoreTestMixin, self).setUp() self.course_id = CourseLocator(org="testx", course="coursex", run="runx")
def test_non_existent_usage_key(self): self.form_data['usage_key'] = self.store.make_course_usage_key( CourseLocator('non', 'existent', 'course')) self.assert_raises_permission_denied()
def setUp(self): super(CourseModeModelTest, self).setUp() self.course_key = CourseLocator('Test', 'TestCourse', 'TestCourseRun') CourseMode.objects.all().delete()
'oauth_signature': 'OAuth Signature', 'oauth_signature_method': 'HMAC-SHA1', 'oauth_timestamp': 'OAuth Timestamp', 'oauth_nonce': 'OAuth Nonce', 'user_id': 'LTI_User', } LTI_OPTIONAL_PARAMS = { 'context_title': 'context title', 'context_label': 'context label', 'lis_result_sourcedid': 'result sourcedid', 'lis_outcome_service_url': 'outcome service URL', 'tool_consumer_instance_guid': 'consumer instance guid' } COURSE_KEY = CourseLocator(org='some_org', course='some_course', run='some_run') USAGE_KEY = BlockUsageLocator(course_key=COURSE_KEY, block_type='problem', block_id='block_id') COURSE_PARAMS = { 'course_key': COURSE_KEY, 'usage_key': USAGE_KEY } ALL_PARAMS = dict(list(LTI_DEFAULT_PARAMS.items()) + list(COURSE_PARAMS.items())) def build_launch_request(extra_post_data=None, param_to_delete=None): """ Helper method to create a new request object for the LTI launch. """
def test_non_existent_course(self): usage_key = self.store.make_course_usage_key( CourseLocator('non', 'existent', 'course')) url = reverse('blocks_in_block_tree', kwargs={'usage_key_string': str(usage_key)}) self.verify_response(403, url=url)
class GenerateExampleCertificatesTest(TestCase): """Test generation of example certificates. """ COURSE_KEY = CourseLocator(org='test', course='test', run='test') def setUp(self): super(GenerateExampleCertificatesTest, self).setUp() def test_generate_example_certs(self): # Generate certificates for the course with self._mock_xqueue() as mock_queue: certs_api.generate_example_certificates(self.COURSE_KEY) # Verify that the appropriate certs were added to the queue self._assert_certs_in_queue(mock_queue, 1) # Verify that the certificate status is "started" self._assert_cert_status({'description': 'honor', 'status': 'started'}) def test_generate_example_certs_with_verified_mode(self): # Create verified and honor modes for the course CourseModeFactory(course_id=self.COURSE_KEY, mode_slug='honor') CourseModeFactory(course_id=self.COURSE_KEY, mode_slug='verified') # Generate certificates for the course with self._mock_xqueue() as mock_queue: certs_api.generate_example_certificates(self.COURSE_KEY) # Verify that the appropriate certs were added to the queue self._assert_certs_in_queue(mock_queue, 2) # Verify that the certificate status is "started" self._assert_cert_status( { 'description': 'verified', 'status': 'started' }, { 'description': 'honor', 'status': 'started' }) @contextmanager def _mock_xqueue(self): """Mock the XQueue method for adding a task to the queue. """ with patch.object(XQueueCertInterface, 'add_example_cert') as mock_queue: yield mock_queue def _assert_certs_in_queue(self, mock_queue, expected_num): """Check that the certificate generation task was added to the queue. """ certs_in_queue = [ call_args[0] for (call_args, __) in mock_queue.call_args_list ] self.assertEqual(len(certs_in_queue), expected_num) for cert in certs_in_queue: self.assertTrue(isinstance(cert, ExampleCertificate)) def _assert_cert_status(self, *expected_statuses): """Check the example certificate status. """ actual_status = certs_api.example_certificates_status(self.COURSE_KEY) self.assertEqual(list(expected_statuses), actual_status)
def id(self): return CourseLocator(self.org, 'toy', self.run)
def test_possibly_scored(self): course_key = CourseLocator(u'org', u'course', u'run') for block_type in self.possibly_scored_block_types: usage_key = BlockUsageLocator(course_key, block_type, 'mock_block_id') self.assertTrue(scores.possibly_scored(usage_key))
def create(system, source_is_error_module=False, source_visible_to_staff_only=False): """ return a dict of modules: the conditional with a single source and a single child. Keys are 'cond_module', 'source_module', and 'child_module'. if the source_is_error_module flag is set, create a real ErrorBlock for the source. """ descriptor_system = get_test_descriptor_system() # construct source descriptor and module: source_location = BlockUsageLocator(CourseLocator("edX", "conditional_test", "test_run", deprecated=True), "problem", "SampleProblem", deprecated=True) if source_is_error_module: # Make an error descriptor and module source_descriptor = NonStaffErrorBlock.from_xml( 'some random xml data', system, id_generator=CourseLocationManager(source_location.course_key), error_msg='random error message' ) else: source_descriptor = Mock(name='source_descriptor') source_descriptor.location = source_location source_descriptor.visible_to_staff_only = source_visible_to_staff_only source_descriptor.runtime = descriptor_system source_descriptor.render = lambda view, context=None: descriptor_system.render(source_descriptor, view, context) # construct other descriptors: child_descriptor = Mock(name='child_descriptor') child_descriptor.visible_to_staff_only = False child_descriptor._xmodule.student_view.return_value = Fragment(content=u'<p>This is a secret</p>') # lint-amnesty, pylint: disable=protected-access child_descriptor.student_view = child_descriptor._xmodule.student_view # lint-amnesty, pylint: disable=protected-access child_descriptor.displayable_items.return_value = [child_descriptor] child_descriptor.runtime = descriptor_system child_descriptor.xmodule_runtime = get_test_system() child_descriptor.render = lambda view, context=None: descriptor_system.render(child_descriptor, view, context) child_descriptor.location = source_location.replace(category='html', name='child') def visible_to_nonstaff_users(desc): """ Returns if the object is visible to nonstaff users. """ return not desc.visible_to_staff_only def load_item(usage_id, for_parent=None): # pylint: disable=unused-argument """Test-only implementation of load_item that simply returns static xblocks.""" return { child_descriptor.location: child_descriptor, source_location: source_descriptor }.get(usage_id) descriptor_system.load_item = load_item system.descriptor_runtime = descriptor_system # construct conditional module: cond_location = BlockUsageLocator(CourseLocator("edX", "conditional_test", "test_run", deprecated=True), "conditional", "SampleConditional", deprecated=True) field_data = DictFieldData({ 'data': '<conditional/>', 'conditional_attr': 'attempted', 'conditional_value': 'true', 'xml_attributes': {'attempted': 'true'}, 'children': [child_descriptor.location], }) cond_descriptor = ConditionalBlock( descriptor_system, field_data, ScopeIds(None, None, cond_location, cond_location) ) cond_descriptor.xmodule_runtime = system system.get_module = lambda desc: desc if visible_to_nonstaff_users(desc) else None cond_descriptor.get_required_blocks = [ system.get_module(source_descriptor), ] # return dict: return {'cond_module': cond_descriptor, 'source_module': source_descriptor, 'child_module': child_descriptor}
class TestAssetXml(unittest.TestCase): """ Tests for storing/querying course asset metadata. """ shard = 1 def setUp(self): super(TestAssetXml, self).setUp() xsd_filename = "assets.xsd" self.course_id = CourseLocator('org1', 'course1', 'run1') self.course_assets = [] for asset in AssetStoreTestData.all_asset_data: asset_dict = dict( zip(AssetStoreTestData.asset_fields[1:], asset[1:])) asset_md = AssetMetadata( self.course_id.make_asset_key('asset', asset[0]), **asset_dict) self.course_assets.append(asset_md) # Read in the XML schema definition and make a validator. xsd_path = path(__file__).realpath().parent / xsd_filename with open(xsd_path, 'r') as f: schema_root = etree.XML(f.read()) schema = etree.XMLSchema(schema_root) self.xmlparser = etree.XMLParser(schema=schema) def test_export_single_asset_to_from_xml(self): """ Export a single AssetMetadata to XML and verify the structure and fields. """ asset_md = self.course_assets[0] root = etree.Element("assets") asset = etree.SubElement(root, "asset") asset_md.to_xml(asset) # If this line does *not* raise, the XML is valid. etree.fromstring(etree.tostring(root), self.xmlparser) new_asset_key = self.course_id.make_asset_key('tmp', 'tmp') new_asset_md = AssetMetadata(new_asset_key) new_asset_md.from_xml(asset) # Compare asset_md to new_asset_md. for attr in AssetMetadata.XML_ATTRS: if attr in AssetMetadata.XML_ONLY_ATTRS: continue orig_value = getattr(asset_md, attr) new_value = getattr(new_asset_md, attr) self.assertEqual(orig_value, new_value) def test_export_with_None_value(self): """ Export and import a single AssetMetadata to XML with a None created_by field, without causing an exception. """ asset_md = AssetMetadata( self.course_id.make_asset_key('asset', 'none_value'), created_by=None, ) asset = etree.Element("asset") asset_md.to_xml(asset) asset_md.from_xml(asset) def test_export_all_assets_to_xml(self): """ Export all AssetMetadatas to XML and verify the structure and fields. """ root = etree.Element("assets") AssetMetadata.add_all_assets_as_xml(root, self.course_assets) # If this line does *not* raise, the XML is valid. etree.fromstring(etree.tostring(root), self.xmlparser) def test_wrong_node_type_all(self): """ Ensure full asset sections with the wrong tag are detected. """ root = etree.Element("glassets") with self.assertRaises(ContractNotRespected): AssetMetadata.add_all_assets_as_xml(root, self.course_assets) def test_wrong_node_type_single(self): """ Ensure single asset blocks with the wrong tag are detected. """ asset_md = self.course_assets[0] root = etree.Element("assets") asset = etree.SubElement(root, "smashset") with self.assertRaises(ContractNotRespected): asset_md.to_xml(asset)
def get_key(self): return CourseLocator( self.courselike_key.org, self.courselike_key.course, self.courselike_key.run, deprecated=True )
class XBlockCacheModelTest(ModuleStoreTestCase): """ Test the XBlockCache model. """ COURSE_KEY = CourseLocator(org='test', course='test', run='test') CHAPTER1_USAGE_KEY = BlockUsageLocator(COURSE_KEY, block_type='chapter', block_id='chapter1') SECTION1_USAGE_KEY = BlockUsageLocator(COURSE_KEY, block_type='section', block_id='section1') SECTION2_USAGE_KEY = BlockUsageLocator(COURSE_KEY, block_type='section', block_id='section1') VERTICAL1_USAGE_KEY = BlockUsageLocator(COURSE_KEY, block_type='vertical', block_id='sequential1') PATH1 = [ [unicode(CHAPTER1_USAGE_KEY), 'Chapter 1'], [unicode(SECTION1_USAGE_KEY), 'Section 1'], ] PATH2 = [ [unicode(CHAPTER1_USAGE_KEY), 'Chapter 1'], [unicode(SECTION2_USAGE_KEY), 'Section 2'], ] def assert_xblock_cache_data(self, xblock_cache, data): """ Assert that the XBlockCache object values match. """ self.assertEqual(xblock_cache.usage_key, data['usage_key']) self.assertEqual(xblock_cache.course_key, data['usage_key'].course_key) self.assertEqual(xblock_cache.display_name, data['display_name']) self.assertEqual(xblock_cache._paths, data['_paths']) # pylint: disable=protected-access self.assertEqual(xblock_cache.paths, [parse_path_data(path) for path in data['_paths']]) @ddt.data( ( [ { 'usage_key': VERTICAL1_USAGE_KEY, }, { 'display_name': '', '_paths': [], }, ], [ { 'usage_key': VERTICAL1_USAGE_KEY, 'display_name': 'Vertical 5', '_paths': [PATH2] }, { '_paths': [] }, ], ), ( [ { 'usage_key': VERTICAL1_USAGE_KEY, 'display_name': 'Vertical 4', '_paths': [PATH1] }, {}, ], [ { 'usage_key': VERTICAL1_USAGE_KEY, 'display_name': 'Vertical 5', '_paths': [PATH2] }, { '_paths': [PATH1] }, ], ), ) def test_create(self, data): """ Test XBlockCache.create() constructs and updates objects correctly. """ for create_data, additional_data_to_expect in data: xblock_cache = XBlockCache.create(create_data) create_data.update(additional_data_to_expect) self.assert_xblock_cache_data(xblock_cache, create_data) @ddt.data( ([], [PATH1]), ([PATH1, PATH2], [PATH1]), ([PATH1], []), ) @ddt.unpack def test_paths(self, original_paths, updated_paths): xblock_cache = XBlockCache.create({ 'usage_key': self.VERTICAL1_USAGE_KEY, 'display_name': 'The end.', '_paths': original_paths, }) self.assertEqual(xblock_cache.paths, [parse_path_data(path) for path in original_paths]) xblock_cache.paths = [parse_path_data(path) for path in updated_paths] xblock_cache.save() xblock_cache = XBlockCache.objects.get(id=xblock_cache.id) self.assertEqual(xblock_cache._paths, updated_paths) # pylint: disable=protected-access self.assertEqual(xblock_cache.paths, [parse_path_data(path) for path in updated_paths])
def test_contentstore_attrs(self): """ Test getting, setting, and defaulting the locked attr and arbitrary attrs. """ location = BlockUsageLocator(CourseLocator('edX', 'toy', '2012_Fall', deprecated=True), 'course', '2012_Fall', deprecated=True) course_content, __ = self.content_store.get_all_content_for_course( location.course_key) assert_true(len(course_content) > 0) filter_params = _build_requested_filter('Images') filtered_course_content, __ = self.content_store.get_all_content_for_course( location.course_key, filter_params=filter_params) assert_true(len(filtered_course_content) < len(course_content)) # a bit overkill, could just do for content[0] for content in course_content: assert not content.get('locked', False) asset_key = AssetLocator._from_deprecated_son( content.get('content_son', content['_id']), location.run) assert not self.content_store.get_attr(asset_key, 'locked', False) attrs = self.content_store.get_attrs(asset_key) assert_in('uploadDate', attrs) assert not attrs.get('locked', False) self.content_store.set_attr(asset_key, 'locked', True) assert self.content_store.get_attr(asset_key, 'locked', False) attrs = self.content_store.get_attrs(asset_key) assert_in('locked', attrs) assert attrs['locked'] is True self.content_store.set_attrs(asset_key, {'miscel': 99}) assert_equals(self.content_store.get_attr(asset_key, 'miscel'), 99) asset_key = AssetLocator._from_deprecated_son( course_content[0].get('content_son', course_content[0]['_id']), location.run) assert_raises(AttributeError, self.content_store.set_attr, asset_key, 'md5', 'ff1532598830e3feac91c2449eaa60d6') assert_raises(AttributeError, self.content_store.set_attrs, asset_key, { 'foo': 9, 'md5': 'ff1532598830e3feac91c2449eaa60d6' }) assert_raises( NotFoundError, self.content_store.get_attr, BlockUsageLocator(CourseLocator('bogus', 'bogus', 'bogus'), 'asset', 'bogus'), 'displayname') assert_raises( NotFoundError, self.content_store.set_attr, BlockUsageLocator(CourseLocator('bogus', 'bogus', 'bogus'), 'asset', 'bogus'), 'displayname', 'hello') assert_raises( NotFoundError, self.content_store.get_attrs, BlockUsageLocator(CourseLocator('bogus', 'bogus', 'bogus'), 'asset', 'bogus')) assert_raises( NotFoundError, self.content_store.set_attrs, BlockUsageLocator(CourseLocator('bogus', 'bogus', 'bogus'), 'asset', 'bogus'), {'displayname': 'hello'}) assert_raises( NotFoundError, self.content_store.set_attrs, BlockUsageLocator(CourseLocator('bogus', 'bogus', 'bogus', deprecated=True), 'asset', None, deprecated=True), {'displayname': 'hello'})
def test_all_current_course_configs(self): # Set up test objects for global_setting in (True, False, None): CourseDurationLimitConfig.objects.create(enabled=global_setting, enabled_as_of=datetime( 2018, 1, 1)) for site_setting in (True, False, None): test_site_cfg = SiteConfigurationFactory.create( site_values={'course_org_filter': []}) CourseDurationLimitConfig.objects.create( site=test_site_cfg.site, enabled=site_setting, enabled_as_of=datetime(2018, 1, 1)) for org_setting in (True, False, None): test_org = "{}-{}".format(test_site_cfg.id, org_setting) test_site_cfg.site_values['course_org_filter'].append( test_org) test_site_cfg.save() CourseDurationLimitConfig.objects.create( org=test_org, enabled=org_setting, enabled_as_of=datetime(2018, 1, 1)) for course_setting in (True, False, None): test_course = CourseOverviewFactory.create( org=test_org, id=CourseLocator(test_org, 'test_course', 'run-{}'.format(course_setting))) CourseDurationLimitConfig.objects.create( course=test_course, enabled=course_setting, enabled_as_of=datetime(2018, 1, 1)) with self.assertNumQueries(4): all_configs = CourseDurationLimitConfig.all_current_course_configs( ) # Deliberatly using the last all_configs that was checked after the 3rd pass through the global_settings loop # We should be creating 3^4 courses (3 global values * 3 site values * 3 org values * 3 course values) # Plus 1 for the edX/toy/2012_Fall course self.assertEqual(len(all_configs), 3**4 + 1) # Point-test some of the final configurations self.assertEqual( all_configs[CourseLocator('7-True', 'test_course', 'run-None')], { 'enabled': (True, Provenance.org), 'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=pytz.UTC), Provenance.run), }) self.assertEqual( all_configs[CourseLocator('7-True', 'test_course', 'run-False')], { 'enabled': (False, Provenance.run), 'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=pytz.UTC), Provenance.run), }) self.assertEqual( all_configs[CourseLocator('7-None', 'test_course', 'run-None')], { 'enabled': (True, Provenance.site), 'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=pytz.UTC), Provenance.run), })
def setUp(self): super(PersistentGradesFeatureFlagTests, self).setUp() self.course_id_1 = CourseLocator(org="edx", course="course", run="run") self.course_id_2 = CourseLocator(org="edx", course="course2", run="run")
def _create_test_tree(self, name, user_id=None): """ Creates and returns a tree with the following structure: Grandparent Parent Sibling Parent Child Child Sibling """ if user_id is None: user_id = self.dummy_user org = 'edX' course = 'tree{}'.format(name) run = name if not self.draft_store.has_course( CourseKey.from_string('/'.join[org, course, run])): self.draft_store.create_course(org, course, run, user_id) locations = { 'grandparent': BlockUsageLocator(CourseLocator(org, course, run, deprecated=True), 'chapter', 'grandparent', deprecated=True), 'parent_sibling': BlockUsageLocator(CourseLocator(org, course, run, deprecated=True), 'sequential', 'parent_sibling', deprecated=True), 'parent': BlockUsageLocator(CourseLocator(org, course, run, deprecated=True), 'sequential', 'parent', deprecated=True), 'child_sibling': BlockUsageLocator(CourseLocator(org, course, run, deprecated=True), 'vertical', 'child_sibling', deprecated=True), 'child': BlockUsageLocator(CourseLocator(org, course, run, deprecated=True), 'vertical', 'child', deprecated=True), } for key in locations: self.draft_store.create_item(user_id, locations[key].course_key, locations[key].block_type, block_id=locations[key].block_id) grandparent = self.draft_store.get_item(locations['grandparent']) grandparent.children += [ locations['parent_sibling'], locations['parent'] ] self.draft_store.update_item(grandparent, user_id=user_id) parent = self.draft_store.get_item(locations['parent']) parent.children += [locations['child_sibling'], locations['child']] self.draft_store.update_item(parent, user_id=user_id) self.draft_store.publish(locations['parent'], user_id) self.draft_store.publish(locations['parent_sibling'], user_id) return locations
class CertificateGenerationEnabledTest(EventTestMixin, TestCase): """Test enabling/disabling self-generated certificates for a course. """ COURSE_KEY = CourseLocator(org='test', course='test', run='test') def setUp(self): # pylint: disable=arguments-differ super(CertificateGenerationEnabledTest, self).setUp('lms.djangoapps.certificates.api.tracker') # Since model-based configuration is cached, we need # to clear the cache before each test. cache.clear() @ddt.data( (None, None, False), (False, None, False), (False, True, False), (True, None, False), (True, False, False), (True, True, True) ) @ddt.unpack def test_cert_generation_enabled(self, is_feature_enabled, is_course_enabled, expect_enabled): if is_feature_enabled is not None: CertificateGenerationConfiguration.objects.create(enabled=is_feature_enabled) if is_course_enabled is not None: certs_api.set_cert_generation_enabled(self.COURSE_KEY, is_course_enabled) cert_event_type = 'enabled' if is_course_enabled else 'disabled' event_name = '.'.join(['edx', 'certificate', 'generation', cert_event_type]) self.assert_event_emitted( event_name, course_id=six.text_type(self.COURSE_KEY), ) self._assert_enabled_for_course(self.COURSE_KEY, expect_enabled) def test_latest_setting_used(self): # Enable the feature CertificateGenerationConfiguration.objects.create(enabled=True) # Enable for the course certs_api.set_cert_generation_enabled(self.COURSE_KEY, True) self._assert_enabled_for_course(self.COURSE_KEY, True) # Disable for the course certs_api.set_cert_generation_enabled(self.COURSE_KEY, False) self._assert_enabled_for_course(self.COURSE_KEY, False) def test_setting_is_course_specific(self): # Enable the feature CertificateGenerationConfiguration.objects.create(enabled=True) # Enable for one course certs_api.set_cert_generation_enabled(self.COURSE_KEY, True) self._assert_enabled_for_course(self.COURSE_KEY, True) # Should be disabled for another course other_course = CourseLocator(org='other', course='other', run='other') self._assert_enabled_for_course(other_course, False) def _assert_enabled_for_course(self, course_key, expect_enabled): """Check that self-generated certificates are enabled or disabled for the course. """ actual_enabled = certs_api.cert_generation_enabled(course_key) self.assertEqual(expect_enabled, actual_enabled)
def setUp(self): super(TestLTIConsumerHideFieldsFlag, self).setUp() self.course_id = CourseLocator(org="edx", course="course", run="run")
def get_course_locator(self, course_key): from opaque_keys.edx.locator import CourseLocator return CourseLocator.from_string(course_key)
def test_make_course_usage_key(self): """Test that we get back the appropriate usage key for the root of a course key.""" course_key = CourseLocator(org="edX", course="101", run="2015") root_block_key = self.draft_store.make_course_usage_key(course_key) self.assertEqual(root_block_key.block_type, "course") self.assertEqual(root_block_key.block_id, "2015")
def setUp(self): super(TestCohortSignals, self).setUp() self.course_key = CourseLocator("dummy", "dummy", "dummy")
class ExampleCertificateTest(TestCase): """Tests for the ExampleCertificate model. """ COURSE_KEY = CourseLocator(org='test', course='test', run='test') DESCRIPTION = 'test' TEMPLATE = 'test.pdf' DOWNLOAD_URL = 'http://www.example.com' ERROR_REASON = 'Kaboom!' def setUp(self): super(ExampleCertificateTest, self).setUp() self.cert_set = ExampleCertificateSet.objects.create(course_key=self.COURSE_KEY) self.cert = ExampleCertificate.objects.create( example_cert_set=self.cert_set, description=self.DESCRIPTION, template=self.TEMPLATE ) def test_update_status_success(self): self.cert.update_status( ExampleCertificate.STATUS_SUCCESS, download_url=self.DOWNLOAD_URL ) self.assertEqual( self.cert.status_dict, { 'description': self.DESCRIPTION, 'status': ExampleCertificate.STATUS_SUCCESS, 'download_url': self.DOWNLOAD_URL } ) def test_update_status_error(self): self.cert.update_status( ExampleCertificate.STATUS_ERROR, error_reason=self.ERROR_REASON ) self.assertEqual( self.cert.status_dict, { 'description': self.DESCRIPTION, 'status': ExampleCertificate.STATUS_ERROR, 'error_reason': self.ERROR_REASON } ) def test_update_status_invalid(self): with self.assertRaisesRegexp(ValueError, 'status'): self.cert.update_status('invalid') def test_latest_status_unavailable(self): # Delete any existing statuses ExampleCertificateSet.objects.all().delete() # Verify that the "latest" status is None result = ExampleCertificateSet.latest_status(self.COURSE_KEY) self.assertIs(result, None) def test_latest_status_is_course_specific(self): other_course = CourseLocator(org='other', course='other', run='other') result = ExampleCertificateSet.latest_status(other_course) self.assertIs(result, None)
def location(self): return BlockUsageLocator(CourseLocator('org', 'course', 'run'), 'category', self.url_name)
class UpdateExampleCertificateViewTest(CacheIsolationTestCase): """Tests for the XQueue callback that updates example certificates. """ COURSE_KEY = CourseLocator(org='test', course='test', run='test') DESCRIPTION = 'test' TEMPLATE = 'test.pdf' DOWNLOAD_URL = 'http://www.example.com' ERROR_REASON = 'Kaboom!' ENABLED_CACHES = ['default'] def setUp(self): super(UpdateExampleCertificateViewTest, self).setUp() self.cert_set = ExampleCertificateSet.objects.create( course_key=self.COURSE_KEY) self.cert = ExampleCertificate.objects.create( example_cert_set=self.cert_set, description=self.DESCRIPTION, template=self.TEMPLATE, ) self.url = reverse('update_example_certificate') # Since rate limit counts are cached, we need to clear # this before each test. cache.clear() def test_update_example_certificate_success(self): response = self._post_to_view(self.cert, download_url=self.DOWNLOAD_URL) self._assert_response(response) self.cert = ExampleCertificate.objects.get() self.assertEqual(self.cert.status, ExampleCertificate.STATUS_SUCCESS) self.assertEqual(self.cert.download_url, self.DOWNLOAD_URL) def test_update_example_certificate_invalid_key(self): payload = { 'xqueue_header': json.dumps({'lms_key': 'invalid'}), 'xqueue_body': json.dumps({ 'username': self.cert.uuid, 'url': self.DOWNLOAD_URL }) } response = self.client.post(self.url, data=payload) self.assertEqual(response.status_code, 404) def test_update_example_certificate_error(self): response = self._post_to_view(self.cert, error_reason=self.ERROR_REASON) self._assert_response(response) self.cert = ExampleCertificate.objects.get() self.assertEqual(self.cert.status, ExampleCertificate.STATUS_ERROR) self.assertEqual(self.cert.error_reason, self.ERROR_REASON) @ddt.data('xqueue_header', 'xqueue_body') def test_update_example_certificate_invalid_params(self, missing_param): payload = { 'xqueue_header': json.dumps({'lms_key': self.cert.access_key}), 'xqueue_body': json.dumps({ 'username': self.cert.uuid, 'url': self.DOWNLOAD_URL }) } del payload[missing_param] response = self.client.post(self.url, data=payload) self.assertEqual(response.status_code, 400) def test_update_example_certificate_missing_download_url(self): payload = { 'xqueue_header': json.dumps({'lms_key': self.cert.access_key}), 'xqueue_body': json.dumps({'username': self.cert.uuid}) } response = self.client.post(self.url, data=payload) self.assertEqual(response.status_code, 400) def test_update_example_cetificate_non_json_param(self): payload = {'xqueue_header': '{/invalid', 'xqueue_body': '{/invalid'} response = self.client.post(self.url, data=payload) self.assertEqual(response.status_code, 400) def test_unsupported_http_method(self): response = self.client.get(self.url) self.assertEqual(response.status_code, 405) def test_bad_request_rate_limiting(self): payload = { 'xqueue_header': json.dumps({'lms_key': 'invalid'}), 'xqueue_body': json.dumps({ 'username': self.cert.uuid, 'url': self.DOWNLOAD_URL }) } # Exceed the rate limit for invalid requests # (simulate a DDOS with invalid keys) for _ in range(100): response = self.client.post(self.url, data=payload) if response.status_code == 403: break # The final status code should indicate that the rate # limit was exceeded. self.assertEqual(response.status_code, 403) def _post_to_view(self, cert, download_url=None, error_reason=None): """Simulate a callback from the XQueue to the example certificate end-point. """ header = {'lms_key': cert.access_key} body = {'username': cert.uuid} if download_url is not None: body['url'] = download_url if error_reason is not None: body['error'] = 'error' body['error_reason'] = self.ERROR_REASON payload = { 'xqueue_header': json.dumps(header), 'xqueue_body': json.dumps(body) } return self.client.post(self.url, data=payload) def _assert_response(self, response): """Check the response from the callback end-point. """ content = json.loads(response.content) self.assertEqual(response.status_code, 200) self.assertEqual(content['return_code'], 0)
def test_conditional_module(self, _): """Make sure that conditional module works""" print("Starting import") course = self.get_course('conditional_and_poll') print("Course: ", course) print("id: ", course.id) def inner_get_module(descriptor): if isinstance(descriptor, BlockUsageLocator): location = descriptor descriptor = self.modulestore.get_item(location, depth=None) descriptor.xmodule_runtime = get_test_system() descriptor.xmodule_runtime.descriptor_runtime = descriptor._runtime # pylint: disable=protected-access descriptor.xmodule_runtime.get_module = inner_get_module return descriptor # edx - HarvardX # cond_test - ER22x location = BlockUsageLocator(CourseLocator("HarvardX", "ER22x", "2013_Spring", deprecated=True), "conditional", "condone", deprecated=True) def replace_urls(text, staticfiles_prefix=None, replace_prefix='/static/', course_namespace=None): # lint-amnesty, pylint: disable=unused-argument return text self.test_system.replace_urls = replace_urls self.test_system.get_module = inner_get_module module = inner_get_module(location) print("module: ", module) print("module children: ", module.get_children()) print("module display items (children): ", module.get_display_items()) html = module.render(STUDENT_VIEW).content print("html type: ", type(html)) print("html: ", html) html_expect = module.xmodule_runtime.render_template( 'conditional_ajax.html', { # Test ajax url is just usage-id / handler_name 'ajax_url': '{}/xmodule_handler'.format(text_type(location)), 'element_id': u'i4x-HarvardX-ER22x-conditional-condone', 'depends': u'i4x-HarvardX-ER22x-problem-choiceprob' } ) assert html == html_expect gdi = module.get_display_items() print("gdi=", gdi) ajax = json.loads(module.handle_ajax('', '')) module.save() print("ajax: ", ajax) fragments = ajax['fragments'] assert not any(('This is a secret' in item['content']) for item in fragments) # Now change state of the capa problem to make it completed inner_module = inner_get_module(location.replace(category="problem", name='choiceprob')) inner_module.attempts = 1 # Save our modifications to the underlying KeyValueStore so they can be persisted inner_module.save() ajax = json.loads(module.handle_ajax('', '')) module.save() print("post-attempt ajax: ", ajax) fragments = ajax['fragments'] assert any(('This is a secret' in item['content']) for item in fragments)
def _recalculate_subsection_grade(self, **kwargs): """ Updates a saved subsection grade. Keyword Arguments: user_id (int): id of applicable User object anonymous_user_id (int, OPTIONAL): Anonymous ID of the User course_id (string): identifying the course usage_id (string): identifying the course block only_if_higher (boolean): indicating whether grades should be updated only if the new raw_earned is higher than the previous value. expected_modified_time (serialized timestamp): indicates when the task was queued so that we can verify the underlying data update. score_deleted (boolean): indicating whether the grade change is a result of the problem's score being deleted. event_transaction_id (string): uuid identifying the current event transaction. event_transaction_type (string): human-readable type of the event at the root of the current event transaction. score_db_table (ScoreDatabaseTableEnum): database table that houses the changed score. Used in conjunction with expected_modified_time. """ try: course_key = CourseLocator.from_string(kwargs['course_id']) scored_block_usage_key = UsageKey.from_string( kwargs['usage_id']).replace(course_key=course_key) set_custom_metrics_for_course_key(course_key) set_custom_metric('usage_id', unicode(scored_block_usage_key)) # The request cache is not maintained on celery workers, # where this code runs. So we take the values from the # main request cache and store them in the local request # cache. This correlates model-level grading events with # higher-level ones. set_event_transaction_id(kwargs.get('event_transaction_id')) set_event_transaction_type(kwargs.get('event_transaction_type')) # Verify the database has been updated with the scores when the task was # created. This race condition occurs if the transaction in the task # creator's process hasn't committed before the task initiates in the worker # process. has_database_updated = _has_db_updated_with_new_score( self, scored_block_usage_key, **kwargs) if not has_database_updated: raise DatabaseNotReadyError _update_subsection_grades( course_key, scored_block_usage_key, kwargs['only_if_higher'], kwargs['user_id'], kwargs['score_deleted'], ) except Exception as exc: # pylint: disable=broad-except if not isinstance(exc, KNOWN_RETRY_ERRORS): log.info( "tnl-6244 grades unexpected failure: {}. task id: {}. kwargs={}" .format( repr(exc), self.request.id, kwargs, )) raise self.retry(kwargs=kwargs, exc=exc)
class XQueueCertInterfaceExampleCertificateTest(TestCase): """Tests for the XQueue interface for certificate generation. """ COURSE_KEY = CourseLocator(org='test', course='test', run='test') TEMPLATE = 'test.pdf' DESCRIPTION = 'test' ERROR_MSG = 'Kaboom!' def setUp(self): super().setUp() self.xqueue = XQueueCertInterface() def test_add_example_cert(self): cert = self._create_example_cert() with self._mock_xqueue() as mock_send: self.xqueue.add_example_cert(cert) # Verify that the correct payload was sent to the XQueue self._assert_queue_task(mock_send, cert) # Verify the certificate status assert cert.status == ExampleCertificate.STATUS_STARTED def test_add_example_cert_error(self): cert = self._create_example_cert() with self._mock_xqueue(success=False): self.xqueue.add_example_cert(cert) # Verify the error status of the certificate assert cert.status == ExampleCertificate.STATUS_ERROR assert self.ERROR_MSG in cert.error_reason def _create_example_cert(self): """Create an example certificate. """ cert_set = ExampleCertificateSet.objects.create( course_key=self.COURSE_KEY) return ExampleCertificate.objects.create(example_cert_set=cert_set, description=self.DESCRIPTION, template=self.TEMPLATE) @contextmanager def _mock_xqueue(self, success=True): """Mock the XQueue method for sending a task to the queue. """ with patch.object(XQueueInterface, 'send_to_queue') as mock_send: mock_send.return_value = (0, None) if success else (1, self.ERROR_MSG) yield mock_send def _assert_queue_task(self, mock_send, cert): """Check that the task was added to the queue. """ expected_header = { 'lms_key': cert.access_key, 'lms_callback_url': f'https://edx.org/update_example_certificate?key={cert.uuid}', 'queue_name': 'certificates' } expected_body = { 'action': 'create', 'username': cert.uuid, 'name': 'John Doë', 'course_id': str(self.COURSE_KEY), 'template_pdf': 'test.pdf', 'example_certificate': True } assert mock_send.called __, kwargs = mock_send.call_args_list[0] actual_header = json.loads(kwargs['header']) actual_body = json.loads(kwargs['body']) assert expected_header == actual_header assert expected_body == actual_body