def test_xblock_metadata(self): """ Test the XBlock metadata API """ unit_block_key = library_api.create_library_block( self.library.key, "unit", "metadata-u1").usage_key problem_key = library_api.create_library_block_child( unit_block_key, "problem", "metadata-p1").usage_key new_olx = """ <problem display_name="New Multi Choice Question" max_attempts="5"> <multiplechoiceresponse> <p>This is a normal capa problem. It has "maximum attempts" set to **5**.</p> <label>Blockstore is designed to store.</label> <choicegroup type="MultipleChoice"> <choice correct="false">XBlock metadata only</choice> <choice correct="true">XBlock data/metadata and associated static asset files</choice> <choice correct="false">Static asset files for XBlocks and courseware</choice> <choice correct="false">XModule metadata only</choice> </choicegroup> </multiplechoiceresponse> </problem> """.strip() library_api.set_library_block_olx(problem_key, new_olx) library_api.publish_changes(self.library.key) # Now view the problem as Alice: client = APIClient() client.login(username=self.student_a.username, password='******') # Check the metadata API for the unit: metadata_view_result = client.get( URL_BLOCK_METADATA_URL.format(block_key=unit_block_key), {"include": "children,editable_children"}, ) self.assertEqual(metadata_view_result.data["children"], [str(problem_key)]) self.assertEqual(metadata_view_result.data["editable_children"], [str(problem_key)]) # Check the metadata API for the problem: metadata_view_result = client.get( URL_BLOCK_METADATA_URL.format(block_key=problem_key), {"include": "student_view_data,index_dictionary"}, ) self.assertEqual(metadata_view_result.data["block_id"], str(problem_key)) self.assertEqual(metadata_view_result.data["display_name"], "New Multi Choice Question") self.assertNotIn("children", metadata_view_result.data) self.assertNotIn("editable_children", metadata_view_result.data) self.assertDictContainsSubset( { "content_type": "CAPA", "problem_types": ["multiplechoiceresponse"], }, metadata_view_result.data["index_dictionary"]) self.assertEqual(metadata_view_result.data["student_view_data"], None) # Capa doesn't provide student_view_data
def test_complex_xblock_tree(self, _mock1, _mock2): """ Test a Pathway with a deep XBlock tree and inter-bundle-links """ # Create the following XBlock tree # Library 2 # unit "alpha" # -> link to library 1's bundle ("link1") # -> unit "beta" (in Library 1) # -> html "gamma" # Then make sure we can add that whole hierarchy into a pathway. beta_key = library_api.create_library_block(self.lib1.key, "unit", "beta").usage_key gamma_key = library_api.create_library_block_child(beta_key, "html", "gamma").usage_key library_api.set_library_block_olx(gamma_key, '<html>This is gamma.</html>') library_api.publish_changes(self.lib1.key) library_api.create_bundle_link(self.lib2.key, 'link1', self.lib1.key) alpha_key = library_api.create_library_block(self.lib2.key, "unit", "alpha").usage_key library_api.set_library_block_olx(alpha_key, ''' <unit display_name="alpha"> <xblock-include source="link1" definition="unit/beta" usage_hint="beta" /> </unit> ''') library_api.publish_changes(self.lib2.key) # Create a new pathway: response = self.client.post(URL_CREATE_PATHWAY, { "owner_user_id": self.user.id, "draft_data": { "title": "A Commplex Pathway", "description": "Complex pathway for testing", "data": {}, "items": [ {"original_usage_id": str(alpha_key)}, ], }, }, format='json') self.assertEqual(response.status_code, 200) pathway_id = response.data["id"] pathway_url = URL_GET_PATHWAY.format(pathway_id=pathway_id) # Now publish the pathway: response = self.client.post(pathway_url + 'publish/') self.assertEqual(response.status_code, 200) # Get the resulting pathway: response = self.client.get(pathway_url) self.assertEqual(response.status_code, 200) # Get the usage ID of the root 'alpha' XBlock: alpha_key_pathway = UsageKey.from_string(response.data["published_data"]["items"][0]["usage_id"]) self.assertNotEqual(alpha_key_pathway, alpha_key) block = xblock_api.load_block(alpha_key_pathway, user=self.other_user) # Render the block and all its children - make sure even the descendants are rendered: fragment = block.render('student_view', context={}) self.assertIn('This is gamma.', fragment.content)
def post(self, request, usage_key_str): """ Replace the block's OLX. This API is only meant for use by developers or API client applications. Very little validation is done. """ key = LibraryUsageLocatorV2.from_string(usage_key_str) api.require_permission_for_library_key(key.lib_key, request.user, permissions.CAN_EDIT_THIS_CONTENT_LIBRARY) serializer = LibraryXBlockOlxSerializer(data=request.data) serializer.is_valid(raise_exception=True) new_olx_str = serializer.validated_data["olx"] try: api.set_library_block_olx(key, new_olx_str) except ValueError as err: raise ValidationError(detail=str(err)) return Response(LibraryXBlockOlxSerializer({"olx": new_olx_str}).data)
def test_mark_complete_via_handler(self): """ Test that a "complete on view" XBlock like the HTML block can be marked as complete using the LmsBlockMixin.publish_completion handler. """ block_id = library_api.create_library_block( self.library.key, "html", "completable_html").usage_key new_olx = """ <html display_name="Read this HTML"> <![CDATA[ <p>This is some <strong>HTML</strong>.</p> ]]> </html> """.strip() library_api.set_library_block_olx(block_id, new_olx) library_api.publish_changes(self.library.key) # We should get a REST API for retrieving completion data; for now use python def get_block_completion_status(): """ Get block completion status (0 to 1) """ block = xblock_api.load_block(block_id, self.student_a) assert hasattr(block, 'publish_completion') service = block.runtime.service(block, 'completion') return service.get_completions([block_id])[block_id] # At first the block is not completed assert get_block_completion_status() == 0 # Now call the 'publish_completion' handler: client = APIClient() client.login(username=self.student_a.username, password='******') result = client.get( URL_BLOCK_GET_HANDLER_URL.format( block_key=block_id, handler_name='publish_completion')) publish_completion_url = result.data["handler_url"] # This will test the 'completion' service and the completion event handler: result2 = client.post(publish_completion_url, {"completion": 1.0}, format='json') assert result2.status_code == 200 # Now the block is completed assert get_block_completion_status() == 1
def test_i18n(self): """ Test that a block's rendered content respects the Accept-Language header and returns translated content. """ block_id = library_api.create_library_block(self.library.key, "problem", "i18n_problem").usage_key new_olx = """ <problem display_name="New Multi Choice Question" max_attempts="5"> <multiplechoiceresponse> <p>This is a normal capa problem. It has "maximum attempts" set to **5**.</p> <label>Blockstore is designed to store.</label> <choicegroup type="MultipleChoice"> <choice correct="false">XBlock metadata only</choice> <choice correct="true">XBlock data/metadata and associated static asset files</choice> <choice correct="false">Static asset files for XBlocks and courseware</choice> <choice correct="false">XModule metadata only</choice> </choicegroup> </multiplechoiceresponse> </problem> """.strip() library_api.set_library_block_olx(block_id, new_olx) library_api.publish_changes(self.library.key) # Enable the dummy language in darklang DarkLangConfig(released_languages='eo', changed_by=self.student_a, enabled=True).save() client = APIClient() # View the problem without specifying a language default_public_view = client.get( URL_BLOCK_RENDER_VIEW.format(block_key=block_id, view_name='public_view')) assert 'Submit' in default_public_view.data['content'] assert 'Süßmït' not in default_public_view.data['content'] # View the problem and request the dummy language dummy_public_view = client.get(URL_BLOCK_RENDER_VIEW.format( block_key=block_id, view_name='public_view'), HTTP_ACCEPT_LANGUAGE='eo') assert 'Süßmït' in dummy_public_view.data['content'] assert 'Submit' not in dummy_public_view.data['content']
def test_views_for_anonymous_users(self): """ Test that anonymous users can view XBlock's 'public_view' but not other views """ # Create an XBlock block_metadata = library_api.create_library_block(self.library.key, "html", "html1") block_usage_key = block_metadata.usage_key library_api.set_library_block_olx(block_usage_key, "<html>Hello world</html>") library_api.publish_changes(self.library.key) anon_client = APIClient() # View the public_view: public_view_result = anon_client.get( URL_BLOCK_RENDER_VIEW.format(block_key=block_usage_key, view_name='public_view'), ) assert public_view_result.status_code == 200 assert 'Hello world' in public_view_result.data['content'] # Try to view the student_view: public_view_result = anon_client.get( URL_BLOCK_RENDER_VIEW.format(block_key=block_usage_key, view_name='student_view'), ) assert public_view_result.status_code == 403
def test_scores_persisted(self): """ Test that a block's emitted scores are cached in StudentModule In the future if there is a REST API to retrieve individual block's scores, that should be used instead of checking StudentModule directly. """ block_id = library_api.create_library_block(self.library.key, "problem", "scored_problem").usage_key new_olx = """ <problem display_name="New Multi Choice Question" max_attempts="5"> <multiplechoiceresponse> <p>This is a normal capa problem. It has "maximum attempts" set to **5**.</p> <label>Blockstore is designed to store.</label> <choicegroup type="MultipleChoice"> <choice correct="false">XBlock metadata only</choice> <choice correct="true">XBlock data/metadata and associated static asset files</choice> <choice correct="false">Static asset files for XBlocks and courseware</choice> <choice correct="false">XModule metadata only</choice> </choicegroup> </multiplechoiceresponse> </problem> """.strip() library_api.set_library_block_olx(block_id, new_olx) library_api.publish_changes(self.library.key) # Now view the problem as Alice: client = APIClient() client.login(username=self.student_a.username, password='******') student_view_result = client.get( URL_BLOCK_RENDER_VIEW.format(block_key=block_id, view_name='student_view')) problem_key = "input_{}_2_1".format(block_id) self.assertIn(problem_key, student_view_result.data["content"]) # And submit a wrong answer: result = client.get( URL_BLOCK_GET_HANDLER_URL.format(block_key=block_id, handler_name='xmodule_handler')) problem_check_url = result.data["handler_url"] + 'problem_check' submit_result = client.post(problem_check_url, data={problem_key: "choice_3"}) self.assertEqual(submit_result.status_code, 200) submit_data = json.loads(submit_result.content) self.assertDictContainsSubset( { "current_score": 0, "total_possible": 1, "attempts_used": 1, }, submit_data) # Now test that the score is also persisted in StudentModule: # If we add a REST API to get an individual block's score, that should be checked instead of StudentModule. sm = get_score(self.student_a, block_id) self.assertEqual(sm.grade, 0) self.assertEqual(sm.max_grade, 1) # And submit a correct answer: submit_result = client.post(problem_check_url, data={problem_key: "choice_1"}) self.assertEqual(submit_result.status_code, 200) submit_data = json.loads(submit_result.content) self.assertDictContainsSubset( { "current_score": 1, "total_possible": 1, "attempts_used": 2, }, submit_data) # Now test that the score is also updated in StudentModule: # If we add a REST API to get an individual block's score, that should be checked instead of StudentModule. sm = get_score(self.student_a, block_id) self.assertEqual(sm.grade, 1) self.assertEqual(sm.max_grade, 1)