Exemple #1
0
    def test_independent_instances(self):
        """
        Test that independent instances of the same block don't share field data
        until .save() and re-loading, even when they're using the same runtime.
        """
        block_metadata = library_api.create_library_block(
            self.library.key, UserStateTestBlock.BLOCK_TYPE, "b4")
        block_usage_key = block_metadata.usage_key
        library_api.publish_changes(self.library.key)

        block_instance1 = xblock_api.load_block(block_usage_key,
                                                self.student_a)
        block_instance2 = block_instance1.runtime.get_block(block_usage_key)

        # We could assert that both instances of the block have the same runtime
        # instance, but that's an implementation detail. The main point of this
        # test is just to make sure there's never any surprises when reading
        # field data out of an XBlock, because of other instances of the same
        # block.

        block_instance1.user_str = 'changed to this'
        self.assertNotEqual(block_instance1.user_str, block_instance2.user_str)

        block_instance1.save()
        self.assertNotEqual(block_instance1.user_str, block_instance2.user_str)

        block_instance2 = block_instance1.runtime.get_block(block_usage_key)
        # Now they should be equal, because we've saved and re-loaded instance2:
        self.assertEqual(block_instance1.user_str, block_instance2.user_str)
Exemple #2
0
    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'), )
        self.assertEqual(public_view_result.status_code, 200)
        self.assertIn("Hello world", 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'), )
        self.assertEqual(public_view_result.status_code, 403)
Exemple #3
0
 def post(self, request, lib_key_str):
     """
     Add a new XBlock to this content library
     """
     library_key = LibraryLocatorV2.from_string(lib_key_str)
     api.require_permission_for_library_key(
         library_key, request.user,
         permissions.CAN_EDIT_THIS_CONTENT_LIBRARY)
     serializer = LibraryXBlockCreationSerializer(data=request.data)
     serializer.is_valid(raise_exception=True)
     parent_block_usage_str = serializer.validated_data.pop(
         "parent_block", None)
     if parent_block_usage_str:
         # Add this as a child of an existing block:
         parent_block_usage = LibraryUsageLocatorV2.from_string(
             parent_block_usage_str)
         if parent_block_usage.context_key != library_key:
             raise ValidationError(
                 detail={
                     "parent_block":
                     "Usage ID doesn't match library ID in the URL."
                 })
         result = api.create_library_block_child(
             parent_block_usage, **serializer.validated_data)
     else:
         # Create a new regular top-level block:
         try:
             result = api.create_library_block(library_key,
                                               **serializer.validated_data)
         except api.IncompatibleTypesError as err:
             raise ValidationError(detail={'block_type': str(err)}, )
     return Response(LibraryXBlockMetadataSerializer(result).data)
Exemple #4
0
    def test_modify_state_directly(self):
        """
        Test that we can modify user-specific XBlock fields directly in Python
        """
        # Create two XBlocks, block1 and block2
        block1_metadata = library_api.create_library_block(self.library.key, UserStateTestBlock.BLOCK_TYPE, "b2-1")
        block1_usage_key = block1_metadata.usage_key
        block2_metadata = library_api.create_library_block(self.library.key, UserStateTestBlock.BLOCK_TYPE, "b2-2")
        block2_usage_key = block2_metadata.usage_key
        library_api.publish_changes(self.library.key)

        # Alice changes all the fields of block1:
        block1_alice = xblock_api.load_block(block1_usage_key, self.student_a)
        block1_alice.user_str = 'Alice was here'
        block1_alice.uss_str = 'Alice was here (USS)'
        block1_alice.pref_str = 'Alice was here (prefs)'
        block1_alice.user_info_str = 'Alice was here (user info)'
        block1_alice.save()

        # Now load it back and expect the same field data:
        block1_alice = xblock_api.load_block(block1_usage_key, self.student_a)

        self.assertEqual(block1_alice.scope_ids.user_id, self.student_a.id)
        self.assertEqual(block1_alice.user_str, 'Alice was here')
        self.assertEqual(block1_alice.uss_str, 'Alice was here (USS)')
        self.assertEqual(block1_alice.pref_str, 'Alice was here (prefs)')
        self.assertEqual(block1_alice.user_info_str, 'Alice was here (user info)')

        # Now load a different block for Alice:
        block2_alice = xblock_api.load_block(block2_usage_key, self.student_a)
        # User state should be default:
        self.assertEqual(block2_alice.user_str, 'default value')
        # User state summary should be default:
        self.assertEqual(block2_alice.uss_str, 'default value')
        # But prefs and user info should be shared:
        self.assertEqual(block2_alice.pref_str, 'Alice was here (prefs)')
        self.assertEqual(block2_alice.user_info_str, 'Alice was here (user info)')

        # Now load the first block, block1, for Bob:
        block1_bob = xblock_api.load_block(block1_usage_key, self.student_b)

        self.assertEqual(block1_bob.scope_ids.user_id, self.student_b.id)
        self.assertEqual(block1_bob.user_str, 'default value')
        self.assertEqual(block1_bob.uss_str, 'Alice was here (USS)')
        self.assertEqual(block1_bob.pref_str, 'default value')
        self.assertEqual(block1_bob.user_info_str, 'default value')
Exemple #5
0
    def test_has_score(self):
        """
        Test that the LMS-specific 'has_score' attribute is getting added to
        blocks.
        """
        unit_block_key = library_api.create_library_block(self.library.key, "unit", "score-unit1").usage_key
        problem_block_key = library_api.create_library_block(self.library.key, "problem", "score-prob1").usage_key
        library_api.publish_changes(self.library.key)
        unit_block = xblock_api.load_block(unit_block_key, self.student_a)
        problem_block = xblock_api.load_block(problem_block_key, self.student_a)

        assert not hasattr(UnitBlock, 'has_score')
        # The block class doesn't declare 'has_score'
        assert unit_block.has_score is False
        # But it gets added by the runtime and defaults to False
        # And problems do have has_score True:
        assert problem_block.has_score is True
Exemple #6
0
    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
Exemple #7
0
 def test_identical_olx(self):
     """
     Test library blocks with children that also have identical OLX. Since
     the blockstore runtime caches authored field data based on the hash of
     the OLX, this can catch some potential bugs, especially given that the
     "children" field stores usage IDs, not definition IDs.
     """
     # Create a unit containing a <problem>
     unit_block_key = library_api.create_library_block(
         self.library.key, "unit", "u1").usage_key
     library_api.create_library_block_child(unit_block_key, "problem", "p1")
     library_api.publish_changes(self.library.key)
     # Now do the same in a different library:
     library2 = library_api.create_library(
         collection_uuid=self.collection.uuid,
         org=self.organization,
         slug="idolx",
         title=("Identical OLX Test Lib 2"),
         description="",
         library_type=COMPLEX,
         allow_public_learning=True,
         allow_public_read=False,
         library_license=CC_4_BY,
     )
     unit_block2_key = library_api.create_library_block(
         library2.key, "unit", "u1").usage_key
     library_api.create_library_block_child(unit_block2_key, "problem",
                                            "p1")
     library_api.publish_changes(library2.key)
     # Load both blocks:
     unit_block = xblock_api.load_block(unit_block_key, self.student_a)
     unit_block2 = xblock_api.load_block(unit_block2_key, self.student_a)
     self.assertEqual(
         library_api.get_library_block_olx(unit_block_key),
         library_api.get_library_block_olx(unit_block2_key),
     )
     self.assertNotEqual(unit_block.children, unit_block2.children)
 def setUpClass(cls):
     """
     Create some XBlocks in a content library for use in any tests
     """
     super().setUpClass()
     # Create a couple libraries and blocks that can be used for these tests:
     cls.collection = blockstore_api.create_collection("Pathway Test Collection")
     cls.organization = Organization.objects.create(name="TestOrg", short_name="TestOrg")
     cls.lib1 = library_api.create_library(
         collection_uuid=cls.collection.uuid,
         org=cls.organization,
         slug="lx-pathway-lib1",
         title="Pathway Test Lib",
         description="",
         allow_public_learning=True,
         allow_public_read=True,
         library_type='complex',
         library_license='',
     )
     cls.problem_block1_id = library_api.create_library_block(cls.lib1.key, "problem", "p1").usage_key
     library_api.publish_changes(cls.lib1.key)
     cls.lib2 = library_api.create_library(
         collection_uuid=cls.collection.uuid,
         org=cls.organization,
         slug="lx-pathway-lib2",
         title="Pathway Test Lib 2",
         description="",
         allow_public_learning=True,
         allow_public_read=True,
         library_type='complex',
         library_license='',
     )
     cls.html_block2_id = library_api.create_library_block(cls.lib2.key, "html", "h2").usage_key
     library_api.publish_changes(cls.lib2.key)
     # Create some users:
     cls.user = UserFactory(username='******', password='******')
     cls.other_user = UserFactory(username='******', password='******')
Exemple #9
0
    def test_default_values(self):
        """
        Test that a user sees the default field values at first
        """
        block_metadata = library_api.create_library_block(self.library.key, UserStateTestBlock.BLOCK_TYPE, "b1")
        block_usage_key = block_metadata.usage_key
        library_api.publish_changes(self.library.key)

        block_alice = xblock_api.load_block(block_usage_key, self.student_a)

        self.assertEqual(block_alice.scope_ids.user_id, self.student_a.id)
        self.assertEqual(block_alice.user_str, 'default value')
        self.assertEqual(block_alice.uss_str, 'default value')
        self.assertEqual(block_alice.pref_str, 'default value')
        self.assertEqual(block_alice.user_info_str, 'default value')
Exemple #10
0
    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
Exemple #11
0
    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']
Exemple #12
0
    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)
Exemple #13
0
    def test_state_for_anonymous_users(self):
        """
        Test that anonymous users can interact with XBlocks and get/set their
        state via handlers.
        """
        # Create two XBlocks, block1 and block2
        block1_metadata = library_api.create_library_block(self.library.key, UserStateTestBlock.BLOCK_TYPE, "b3-1")
        block1_usage_key = block1_metadata.usage_key
        block2_metadata = library_api.create_library_block(self.library.key, UserStateTestBlock.BLOCK_TYPE, "b3-2")
        block2_usage_key = block2_metadata.usage_key
        library_api.publish_changes(self.library.key)
        # Create two clients (anonymous user's browsers)
        client1 = APIClient()
        client2 = APIClient()

        def call_handler(client, block_key, handler_name, method, data=None):
            """ Call an XBlock handler """
            url_result = client.get(URL_BLOCK_GET_HANDLER_URL.format(block_key=block_key, handler_name=handler_name))
            url = url_result.data["handler_url"]
            data_json = json.dumps(data) if data else None
            response = getattr(client, method)(url, data_json, content_type="application/json")
            assert response.status_code == 200
            return response.json()

        # Now client1 sets all the fields via a handler:
        call_handler(client1, block1_usage_key, "set_user_state", "post", {
            "user_str": "1 was here",
            "uss_str": "1 was here (USS)",
            "pref_str": "1 was here (prefs)",
            "user_info_str": "1 was here (user info)",
        })

        # Now load it back and expect the same data:
        data = call_handler(client1, block1_usage_key, "get_user_state", "get")
        assert data['user_str'] == '1 was here'
        assert data['uss_str'] == '1 was here (USS)'
        assert data['pref_str'] == '1 was here (prefs)'
        assert data['user_info_str'] == '1 was here (user info)'

        # Now load a different XBlock and expect only pref_str and user_info_str to be set:
        data = call_handler(client1, block2_usage_key, "get_user_state", "get")
        assert data['user_str'] == 'default value'
        assert data['uss_str'] == 'default value'
        assert data['pref_str'] == '1 was here (prefs)'
        assert data['user_info_str'] == '1 was here (user info)'

        # Now a different anonymous user loading the first block should see only the uss_str set:
        data = call_handler(client2, block1_usage_key, "get_user_state", "get")
        assert data['user_str'] == 'default value'
        assert data['uss_str'] == '1 was here (USS)'
        assert data['pref_str'] == 'default value'
        assert data['user_info_str'] == 'default value'

        # The "user state summary" should not be shared between registered and anonymous users:
        client_registered = APIClient()
        client_registered.login(username=self.student_a.username, password='******')
        data = call_handler(client_registered, block1_usage_key, "get_user_state", "get")
        assert data['user_str'] == 'default value'
        assert data['uss_str'] == 'default value'
        assert data['pref_str'] == 'default value'
        assert data['user_info_str'] == 'default value'
Exemple #14
0
 def test_dndv2_sets_translator(self):
     dnd_block_key = library_api.create_library_block(self.library.key, "drag-and-drop-v2", "dnd1").usage_key
     library_api.publish_changes(self.library.key)
     dnd_block = xblock_api.load_block(dnd_block_key, self.student_a)
     i18n_service = dnd_block.runtime.service(dnd_block, 'i18n')
     assert isinstance(i18n_service.translator, GNUTranslations)