Пример #1
0
    def test_contentstore_attrs(self):
        """
        Test getting, setting, and defaulting the locked attr and arbitrary attrs.
        """
        location = Location('edX', 'toy', '2012_Fall', 'course', '2012_Fall')
        course_content, __ = self.content_store.get_all_content_for_course(location.course_key)
        assert_true(len(course_content) > 0)
        # a bit overkill, could just do for content[0]
        for content in course_content:
            assert not content.get('locked', False)
            asset_key = AssetLocation._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 = AssetLocation._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,
            Location('bogus', 'bogus', 'bogus', 'asset', 'bogus'),
            'displayname'
        )
        assert_raises(
            NotFoundError, self.content_store.set_attr,
            Location('bogus', 'bogus', 'bogus', 'asset', 'bogus'),
            'displayname', 'hello'
        )
        assert_raises(
            NotFoundError, self.content_store.get_attrs,
            Location('bogus', 'bogus', 'bogus', 'asset', 'bogus')
        )
        assert_raises(
            NotFoundError, self.content_store.set_attrs,
            Location('bogus', 'bogus', 'bogus', 'asset', 'bogus'),
            {'displayname': 'hello'}
        )
        assert_raises(
            NotFoundError, self.content_store.set_attrs,
            Location('bogus', 'bogus', 'bogus', 'asset', None),
            {'displayname': 'hello'}
        )
Пример #2
0
    def test_contentstore_attrs(self):
        """
        Test getting, setting, and defaulting the locked attr and arbitrary attrs.
        """
        location = Location('edX', 'toy', '2012_Fall', 'course', '2012_Fall')
        course_content, __ = self.content_store.get_all_content_for_course(location.course_key)
        assert_true(len(course_content) > 0)
        # a bit overkill, could just do for content[0]
        for content in course_content:
            assert not content.get('locked', False)
            asset_key = AssetLocation._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 = AssetLocation._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,
            Location('bogus', 'bogus', 'bogus', 'asset', 'bogus'),
            'displayname'
        )
        assert_raises(
            NotFoundError, self.content_store.set_attr,
            Location('bogus', 'bogus', 'bogus', 'asset', 'bogus'),
            'displayname', 'hello'
        )
        assert_raises(
            NotFoundError, self.content_store.get_attrs,
            Location('bogus', 'bogus', 'bogus', 'asset', 'bogus')
        )
        assert_raises(
            NotFoundError, self.content_store.set_attrs,
            Location('bogus', 'bogus', 'bogus', 'asset', 'bogus'),
            {'displayname': 'hello'}
        )
        assert_raises(
            NotFoundError, self.content_store.set_attrs,
            Location('bogus', 'bogus', 'bogus', 'asset', None),
            {'displayname': 'hello'}
        )
Пример #3
0
 def test_generate_thumbnail_image(self):
     contentStore = ContentStore()
     content = Content(
         AssetLocation(u'mitX', u'800', u'ignore_run', u'asset',
                       u'monsters__.jpg'), None)
     (thumbnail_content,
      thumbnail_file_location) = contentStore.generate_thumbnail(content)
     self.assertIsNone(thumbnail_content)
     self.assertEqual(
         AssetLocation(u'mitX', u'800', u'ignore_run', u'thumbnail',
                       u'monsters__.jpg'), thumbnail_file_location)
 def test_generate_thumbnail_image(self, original_filename,
                                   thumbnail_filename):
     content_store = ContentStore()
     content = Content(
         AssetLocation(u'mitX', u'800', u'ignore_run', u'asset',
                       original_filename), None)
     (thumbnail_content,
      thumbnail_file_location) = content_store.generate_thumbnail(content)
     self.assertIsNone(thumbnail_content)
     self.assertEqual(
         AssetLocation(u'mitX', u'800', u'ignore_run', u'thumbnail',
                       thumbnail_filename), thumbnail_file_location)
Пример #5
0
    def compute_location(course_key, path, revision=None, is_thumbnail=False):
        """
        Constructs a location object for static content.

        - course_key: the course that this asset belongs to
        - path: is the name of the static asset
        - revision: is the object's revision information
        - is_tumbnail: is whether or not we want the thumbnail version of this
            asset
        """
        path = path.replace('/', '_')
        return AssetLocation(course_key.org, course_key.course, course_key.run,
                             'asset' if not is_thumbnail else 'thumbnail',
                             AssetLocation.clean_keeping_underscores(path),
                             revision)
Пример #6
0
    def test_delete_image_type_asset(self):
        """ Tests deletion of image type asset """
        image_asset = self.get_sample_asset(self.asset_name, asset_type="image")
        thumbnail_image_asset = self.get_sample_asset('delete_test_thumbnail', asset_type="image")

        # upload image
        response = self.client.post(self.url, {"name": "delete_image_test", "file": image_asset})
        self.assertEquals(response.status_code, 200)
        uploaded_image_url = json.loads(response.content)['asset']['url']

        # upload image thumbnail
        response = self.client.post(self.url, {"name": "delete_image_thumb_test", "file": thumbnail_image_asset})
        self.assertEquals(response.status_code, 200)
        thumbnail_url = json.loads(response.content)['asset']['url']
        thumbnail_location = StaticContent.get_location_from_path(thumbnail_url)

        image_asset_location = AssetLocation.from_deprecated_string(uploaded_image_url)
        content = contentstore().find(image_asset_location)
        content.thumbnail_location = thumbnail_location
        contentstore().save(content)

        with mock.patch('opaque_keys.edx.locator.CourseLocator.make_asset_key') as mock_asset_key:
            mock_asset_key.return_value = thumbnail_location

            test_url = reverse_course_url(
                'assets_handler', self.course.id, kwargs={'asset_key_string': unicode(uploaded_image_url)})
            resp = self.client.delete(test_url, HTTP_ACCEPT="application/json")
            self.assertEquals(resp.status_code, 204)
Пример #7
0
 def get_location_from_path(path):
     """
     Generate an AssetKey for the given path (old c4x/org/course/asset/name syntax)
     """
     # TODO OpaqueKey - change to from_string once opaque keys lands
     # return AssetLocation.from_string(path)
     return AssetLocation.from_deprecated_string(path)
Пример #8
0
    def test_can_delete_signatory(self):
        """
        Delete an existing certificate signatory
        """
        self._add_course_certificates(count=2, signatory_count=3)
        certificates = self.course.certificates["certificates"]
        signatory = certificates[1].get("signatories")[1]
        image_asset_location = AssetLocation.from_deprecated_string(signatory["signature_image_path"])
        content = contentstore().find(image_asset_location)
        self.assertIsNotNone(content)
        test_url = "{}/signatories/1".format(self._url(cid=1))
        response = self.client.delete(
            test_url,
            content_type="application/json",
            HTTP_ACCEPT="application/json",
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertEqual(response.status_code, 204)
        self.reload_course()

        # Verify that certificates are properly updated in the course.
        certificates = self.course.certificates["certificates"]
        self.assertEqual(len(certificates[1].get("signatories")), 2)
        # make sure signatory signature image is deleted too
        self.assertRaises(NotFoundError, contentstore().find, image_asset_location)
Пример #9
0
 def get_location_from_path(path):
     """
     Generate an AssetKey for the given path (old c4x/org/course/asset/name syntax)
     """
     # TODO OpaqueKeys after opaque keys deprecation is working
     # return AssetLocation.from_string(path)
     return AssetLocation.from_deprecated_string(path)
Пример #10
0
 def test_can_delete_certificate_with_signatories(self):
     """
     Delete certificate
     """
     self._add_course_certificates(count=2, signatory_count=1)
     certificates = self.course.certificates['certificates']
     org_logo_url = certificates[1]['org_logo_path']
     image_asset_location = AssetLocation.from_deprecated_string(
         org_logo_url)
     content = contentstore().find(image_asset_location)
     self.assertIsNotNone(content)
     response = self.client.delete(
         self._url(cid=1),
         content_type="application/json",
         HTTP_ACCEPT="application/json",
         HTTP_X_REQUESTED_WITH="XMLHttpRequest",
     )
     self.assertEqual(response.status_code, 204)
     self.reload_course()
     # Verify that certificates are properly updated in the course.
     certificates = self.course.certificates['certificates']
     self.assertEqual(len(certificates), 1)
     # make sure certificate org logo is deleted too
     self.assertRaises(NotFoundError,
                       contentstore().find, image_asset_location)
     self.assertEqual(certificates[0].get('name'), 'Name 0')
     self.assertEqual(certificates[0].get('description'), 'Description 0')
Пример #11
0
 def test_can_delete_certificate_with_signatories(self):
     """
     Delete certificate
     """
     self._add_course_certificates(count=2, signatory_count=1)
     certificates = self.course.certificates["certificates"]
     org_logo_url = certificates[1]["org_logo_path"]
     image_asset_location = AssetLocation.from_deprecated_string(org_logo_url)
     content = contentstore().find(image_asset_location)
     self.assertIsNotNone(content)
     response = self.client.delete(
         self._url(cid=1),
         content_type="application/json",
         HTTP_ACCEPT="application/json",
         HTTP_X_REQUESTED_WITH="XMLHttpRequest",
     )
     self.assertEqual(response.status_code, 204)
     self.assert_event_emitted(
         "edx.certificate.configuration.deleted", course_id=unicode(self.course.id), configuration_id="1"
     )
     self.reload_course()
     # Verify that certificates are properly updated in the course.
     certificates = self.course.certificates["certificates"]
     self.assertEqual(len(certificates), 1)
     # make sure certificate org logo is deleted too
     self.assertRaises(NotFoundError, contentstore().find, image_asset_location)
     self.assertEqual(certificates[0].get("name"), "Name 0")
     self.assertEqual(certificates[0].get("description"), "Description 0")
Пример #12
0
    def export_all_for_course(self, course_key, output_directory,
                              assets_policy_file):
        """
        Export all of this course's assets to the output_directory. Export all of the assets'
        attributes to the policy file.

        Args:
            course_key (CourseKey): the :class:`CourseKey` identifying the course
            output_directory: the directory under which to put all the asset files
            assets_policy_file: the filename for the policy file which should be in the same
                directory as the other policy files.
        """
        policy = {}
        assets, __ = self.get_all_content_for_course(course_key)

        for asset in assets:
            asset_location = AssetLocation._from_deprecated_son(
                asset['_id'], course_key.run)  # pylint: disable=protected-access
            self.export(asset_location, output_directory)
            for attr, value in asset.iteritems():
                if attr not in [
                        '_id', 'md5', 'uploadDate', 'length', 'chunkSize'
                ]:
                    policy.setdefault(asset_location.name, {})[attr] = value

        with open(assets_policy_file, 'w') as f:
            json.dump(policy, f)
Пример #13
0
    def test_can_delete_signatory(self):
        """
        Delete an existing certificate signatory
        """
        self._add_course_certificates(count=2, signatory_count=3)
        certificates = self.course.certificates['certificates']
        signatory = certificates[1].get("signatories")[1]
        image_asset_location = AssetLocation.from_deprecated_string(
            signatory['signature_image_path'])
        content = contentstore().find(image_asset_location)
        self.assertIsNotNone(content)
        test_url = '{}/signatories/1'.format(self._url(cid=1))
        response = self.client.delete(
            test_url,
            content_type="application/json",
            HTTP_ACCEPT="application/json",
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertEqual(response.status_code, 204)
        self.reload_course()

        # Verify that certificates are properly updated in the course.
        certificates = self.course.certificates['certificates']
        self.assertEqual(len(certificates[1].get("signatories")), 2)
        # make sure signatory signature image is deleted too
        self.assertRaises(NotFoundError,
                          contentstore().find, image_asset_location)
Пример #14
0
    def test_static_import(self):
        '''
        Stuff in static_import should always be imported into contentstore
        '''
        _, content_store, course = self.load_test_import_course()

        # make sure we have ONE asset in our contentstore ("should_be_imported.html")
        all_assets, count = content_store.get_all_content_for_course(course.id)
        print "len(all_assets)=%d" % len(all_assets)
        self.assertEqual(len(all_assets), 1)
        self.assertEqual(count, 1)

        content = None
        try:
            location = AssetLocation.from_deprecated_string(
                '/c4x/edX/test_import_course/asset/should_be_imported.html')
            content = content_store.find(location)
        except NotFoundError:
            pass

        self.assertIsNotNone(content)

        # make sure course.static_asset_path is correct
        print "static_asset_path = {0}".format(course.static_asset_path)
        self.assertEqual(course.static_asset_path, 'test_import_course')
Пример #15
0
 def get_location_from_path(path):
     """
     Generate an AssetKey for the given path (old c4x/org/course/asset/name syntax)
     """
     # TODO OpaqueKeys after opaque keys deprecation is working
     # return AssetLocation.from_string(path)
     return AssetLocation.from_deprecated_string(path)
Пример #16
0
    def test_delete_image_type_asset(self):
        """ Tests deletion of image type asset """
        image_asset = self.get_sample_asset(self.asset_name, asset_type="image")
        thumbnail_image_asset = self.get_sample_asset('delete_test_thumbnail', asset_type="image")

        # upload image
        response = self.client.post(self.url, {"name": "delete_image_test", "file": image_asset})
        self.assertEquals(response.status_code, 200)
        uploaded_image_url = json.loads(response.content)['asset']['url']

        # upload image thumbnail
        response = self.client.post(self.url, {"name": "delete_image_thumb_test", "file": thumbnail_image_asset})
        self.assertEquals(response.status_code, 200)
        thumbnail_url = json.loads(response.content)['asset']['url']
        thumbnail_location = StaticContent.get_location_from_path(thumbnail_url)

        image_asset_location = AssetLocation.from_deprecated_string(uploaded_image_url)
        content = contentstore().find(image_asset_location)
        content.thumbnail_location = thumbnail_location
        contentstore().save(content)

        with mock.patch('opaque_keys.edx.locator.CourseLocator.make_asset_key') as mock_asset_key:
            mock_asset_key.return_value = thumbnail_location

            test_url = reverse_course_url(
                'assets_handler', self.course.id, kwargs={'asset_key_string': unicode(uploaded_image_url)})
            resp = self.client.delete(test_url, HTTP_ACCEPT="application/json")
            self.assertEquals(resp.status_code, 204)
Пример #17
0
    def export_all_for_course(self, course_key, output_directory, assets_policy_file):
        """
        Export all of this course's assets to the output_directory. Export all of the assets'
        attributes to the policy file.

        Args:
            course_key (CourseKey): the :class:`CourseKey` identifying the course
            output_directory: the directory under which to put all the asset files
            assets_policy_file: the filename for the policy file which should be in the same
                directory as the other policy files.
        """
        policy = {}
        assets, __ = self.get_all_content_for_course(course_key)

        for asset in assets:
            asset_location = AssetLocation._from_deprecated_son(asset['_id'], course_key.run)  # pylint: disable=protected-access
            # TODO: On 6/19/14, I had to put a try/except around this
            # to export a course. The course failed on JSON files in
            # the /static/ directory placed in it with an import.
            # 
            # If this hasn't been looked at in a while, remove this comment. 
            #
            # When debugging course exports, this might be a good place
            # to look. -- pmitros
            self.export(asset_location, output_directory) 
            for attr, value in asset.iteritems():
                if attr not in ['_id', 'md5', 'uploadDate', 'length', 'chunkSize']:
                    policy.setdefault(asset_location.name, {})[attr] = value

        with open(assets_policy_file, 'w') as f:
            json.dump(policy, f)
Пример #18
0
 def test_compute_location(self):
     # We had a bug that __ got converted into a single _. Make sure that substitution of INVALID_CHARS (like space)
     # still happen.
     asset_location = StaticContent.compute_location(
         SlashSeparatedCourseKey('mitX', '400', 'ignore'), 'subs__1eo_jXvZnE .srt.sjson'
     )
     self.assertEqual(AssetLocation(u'mitX', u'400', u'ignore', u'asset', u'subs__1eo_jXvZnE_.srt.sjson', None), asset_location)
Пример #19
0
    def test_static_import(self):
        '''
        Stuff in static_import should always be imported into contentstore
        '''
        _, content_store, course = self.load_test_import_course()

        # make sure we have ONE asset in our contentstore ("should_be_imported.html")
        all_assets, count = content_store.get_all_content_for_course(course.id)
        print "len(all_assets)=%d" % len(all_assets)
        self.assertEqual(len(all_assets), 1)
        self.assertEqual(count, 1)

        content = None
        try:
            location = AssetLocation.from_deprecated_string(
                '/c4x/edX/test_import_course/asset/should_be_imported.html'
            )
            content = content_store.find(location)
        except NotFoundError:
            pass

        self.assertIsNotNone(content)

        # make sure course.static_asset_path is correct
        print "static_asset_path = {0}".format(course.static_asset_path)
        self.assertEqual(course.static_asset_path, 'test_import_course')
 def test_store_svg_as_thumbnail(self):
     # We had a bug that caused generate_thumbnail to attempt to pass SVG to PIL to generate a thumbnail.
     # SVG files should be stored in original form for thumbnail purposes.
     content_store = ContentStore()
     content_store.save = Mock()
     thumbnail_filename = u'test.svg'
     content = Content(
         AssetLocation(u'mitX', u'800', u'ignore_run', u'asset',
                       u'test.svg'), 'image/svg+xml')
     content.data = 'mock svg file'
     (thumbnail_content,
      thumbnail_file_location) = content_store.generate_thumbnail(content)
     self.assertEqual(thumbnail_content.data.read(), b'mock svg file')
     self.assertEqual(
         AssetLocation(u'mitX', u'800', u'ignore_run', u'thumbnail',
                       thumbnail_filename), thumbnail_file_location)
Пример #21
0
def _clear_assets(location):
    """
    Clear all assets for location.
    """
    store = contentstore()

    assets, __ = store.get_all_content_for_course(location.course_key)
    for asset in assets:
        asset_location = AssetLocation._from_deprecated_son(asset["_id"], location.course_key.run)
        del_cached_content(asset_location)
        store.delete(asset_location)
Пример #22
0
def _clear_assets(location):
    """
    Clear all assets for location.
    """
    store = contentstore()

    assets, __ = store.get_all_content_for_course(location.course_key)
    for asset in assets:
        asset_location = AssetLocation._from_deprecated_son(asset["_id"], location.course_key.run)
        del_cached_content(asset_location)
        mongo_id = asset_location.to_deprecated_son()
        store.delete(mongo_id)
Пример #23
0
    def setUp(self):
        """ Scaffolding """
        super(DeleteAssetTestCase, self).setUp()
        self.url = reverse_course_url('assets_handler', self.course.id)
        # First, upload something.
        self.asset_name = 'delete_test'
        self.asset = self.get_sample_asset(self.asset_name)

        response = self.client.post(self.url, {"name": self.asset_name, "file": self.asset})
        self.assertEquals(response.status_code, 200)
        self.uploaded_url = json.loads(response.content)['asset']['url']

        self.asset_location = AssetLocation.from_deprecated_string(self.uploaded_url)
        self.content = contentstore().find(self.asset_location)
Пример #24
0
    def setUp(self):
        """ Scaffolding """
        super(DeleteAssetTestCase, self).setUp()
        self.url = reverse_course_url('assets_handler', self.course.id)
        # First, upload something.
        self.asset_name = 'delete_test'
        self.asset = self.get_sample_asset(self.asset_name)

        response = self.client.post(self.url, {"name": self.asset_name, "file": self.asset})
        self.assertEquals(response.status_code, 200)
        self.uploaded_url = json.loads(response.content)['asset']['url']

        self.asset_location = AssetLocation.from_deprecated_string(self.uploaded_url)
        self.content = contentstore().find(self.asset_location)
    def test_image_is_closed_when_generating_thumbnail(self, image_class_mock):
        # We used to keep the Image's file descriptor open when generating a thumbnail.
        # It should be closed after being used.
        mock_image = MockImage()
        image_class_mock.open.return_value = mock_image

        content_store = ContentStore()
        content = Content(
            AssetLocation(u'mitX', u'800', u'ignore_run', u'asset',
                          "monsters.jpg"), "image/jpeg")
        content.data = 'mock data'
        content_store.generate_thumbnail(content)
        self.assertTrue(image_class_mock.open.called, "Image.open not called")
        self.assertTrue(mock_image.close.called, "mock_image.close not called")
Пример #26
0
    def compute_location(course_key, path, revision=None, is_thumbnail=False):
        """
        Constructs a location object for static content.

        - course_key: the course that this asset belongs to
        - path: is the name of the static asset
        - revision: is the object's revision information
        - is_thumbnail: is whether or not we want the thumbnail version of this
            asset
        """
        path = path.replace('/', '_')
        return AssetLocation(
            course_key.org, course_key.course, course_key.run,
            'asset' if not is_thumbnail else 'thumbnail',
            AssetLocation.clean_keeping_underscores(path),
            revision
        )
Пример #27
0
    def test_pdf_asset(self):
        module_store = modulestore()
        course_items = import_course_from_xml(
            module_store, self.user.id, TEST_DATA_DIR, ["toy"], static_content_store=contentstore(), verbose=True
        )
        course = course_items[0]
        url = reverse_course_url("assets_handler", course.id)

        # Test valid contentType for pdf asset (textbook.pdf)
        resp = self.client.get(url, HTTP_ACCEPT="application/json")
        self.assertContains(resp, "/c4x/edX/toy/asset/textbook.pdf")
        asset_location = AssetLocation.from_deprecated_string("/c4x/edX/toy/asset/textbook.pdf")
        content = contentstore().find(asset_location)
        # Check after import textbook.pdf has valid contentType ('application/pdf')

        # Note: Actual contentType for textbook.pdf in asset.json is 'text/pdf'
        self.assertEqual(content.content_type, "application/pdf")
Пример #28
0
    def test_get_all_content(self, deprecated):
        """
        Test get_all_content_for_course
        """
        self.set_up_assets(deprecated)
        course1_assets, count = self.contentstore.get_all_content_for_course(self.course1_key)
        self.assertEqual(count, len(self.course1_files), course1_assets)
        for asset in course1_assets:
            parsed = AssetLocation.from_deprecated_string(asset['filename'])
            self.assertIn(parsed.name, self.course1_files)

        course1_assets, __ = self.contentstore.get_all_content_for_course(self.course1_key, 1, 1)
        self.assertEqual(len(course1_assets), 1, course1_assets)

        fake_course = SlashSeparatedCourseKey('test', 'fake', 'non')
        course_assets, count = self.contentstore.get_all_content_for_course(fake_course)
        self.assertEqual(count, 0)
        self.assertEqual(course_assets, [])
Пример #29
0
    def test_pdf_asset(self):
        module_store = modulestore('direct')
        _, course_items = import_from_xml(module_store,
                                          'common/test/data/', ['toy'],
                                          static_content_store=contentstore(),
                                          verbose=True)
        course = course_items[0]
        url = reverse_course_url('assets_handler', course.id)

        # Test valid contentType for pdf asset (textbook.pdf)
        resp = self.client.get(url, HTTP_ACCEPT='application/json')
        self.assertContains(resp, "/c4x/edX/toy/asset/textbook.pdf")
        asset_location = AssetLocation.from_deprecated_string(
            '/c4x/edX/toy/asset/textbook.pdf')
        content = contentstore().find(asset_location)
        # Check after import textbook.pdf has valid contentType ('application/pdf')

        # Note: Actual contentType for textbook.pdf in asset.json is 'text/pdf'
        self.assertEqual(content.content_type, 'application/pdf')
Пример #30
0
    def test_pdf_asset(self):
        module_store = modulestore('direct')
        _, course_items = import_from_xml(
            module_store,
            'common/test/data/',
            ['toy'],
            static_content_store=contentstore(),
            verbose=True
        )
        course = course_items[0]
        url = reverse_course_url('assets_handler', course.id)

        # Test valid contentType for pdf asset (textbook.pdf)
        resp = self.client.get(url, HTTP_ACCEPT='application/json')
        self.assertContains(resp, "/c4x/edX/toy/asset/textbook.pdf")
        asset_location = AssetLocation.from_deprecated_string('/c4x/edX/toy/asset/textbook.pdf')
        content = contentstore().find(asset_location)
        # Check after import textbook.pdf has valid contentType ('application/pdf')

        # Note: Actual contentType for textbook.pdf in asset.json is 'text/pdf'
        self.assertEqual(content.content_type, 'application/pdf')
Пример #31
0
    def test_get_all_content(self, deprecated):
        """
        Test get_all_content_for_course
        """
        self.set_up_assets(deprecated)
        course1_assets, count = self.contentstore.get_all_content_for_course(
            self.course1_key)
        self.assertEqual(count, len(self.course1_files), course1_assets)
        for asset in course1_assets:
            parsed = AssetLocation.from_deprecated_string(asset['filename'])
            self.assertIn(parsed.name, self.course1_files)

        course1_assets, __ = self.contentstore.get_all_content_for_course(
            self.course1_key, 1, 1)
        self.assertEqual(len(course1_assets), 1, course1_assets)

        fake_course = SlashSeparatedCourseKey('test', 'fake', 'non')
        course_assets, count = self.contentstore.get_all_content_for_course(
            fake_course)
        self.assertEqual(count, 0)
        self.assertEqual(course_assets, [])
Пример #32
0
    def export_all_for_course(self, course_key, output_directory, assets_policy_file):
        """
        Export all of this course's assets to the output_directory. Export all of the assets'
        attributes to the policy file.

        Args:
            course_key (CourseKey): the :class:`CourseKey` identifying the course
            output_directory: the directory under which to put all the asset files
            assets_policy_file: the filename for the policy file which should be in the same
                directory as the other policy files.
        """
        policy = {}
        assets, __ = self.get_all_content_for_course(course_key)

        for asset in assets:
            asset_location = AssetLocation._from_deprecated_son(asset['_id'], course_key.run)  # pylint: disable=protected-access
            self.export(asset_location, output_directory)
            for attr, value in asset.iteritems():
                if attr not in ['_id', 'md5', 'uploadDate', 'length', 'chunkSize']:
                    policy.setdefault(asset_location.name, {})[attr] = value

        with open(assets_policy_file, 'w') as f:
            json.dump(policy, f)
Пример #33
0
    def copy_all_course_assets(self, source_course_key, dest_course_key):
        """
        See :meth:`.ContentStore.copy_all_course_assets`

        This implementation fairly expensively copies all of the data
        """
        source_query = query_for_course(source_course_key)
        # it'd be great to figure out how to do all of this on the db server and not pull the bits over
        for asset in self.fs_files.find(source_query):
            asset_key = self.make_id_son(asset)
            # don't convert from string until fs access
            source_content = self.fs.get(asset_key)
            if isinstance(asset_key, basestring):
                asset_key = AssetLocation.from_string(asset_key)
                __, asset_key = self.asset_db_key(asset_key)
            asset_key['org'] = dest_course_key.org
            asset_key['course'] = dest_course_key.course
            if getattr(dest_course_key, 'deprecated', False):  # remove the run if exists
                if 'run' in asset_key:
                    del asset_key['run']
                asset_id = asset_key
            else:  # add the run, since it's the last field, we're golden
                asset_key['run'] = dest_course_key.run
                asset_id = unicode(dest_course_key.make_asset_key(asset_key['category'], asset_key['name']))

            self.fs.put(
                source_content.read(),
                _id=asset_id, filename=asset['filename'], content_type=asset['contentType'],
                displayname=asset['displayname'], content_son=asset_key,
                # thumbnail is not technically correct but will be functionally correct as the code
                # only looks at the name which is not course relative.
                thumbnail_location=asset['thumbnail_location'],
                import_path=asset['import_path'],
                # getattr b/c caching may mean some pickled instances don't have attr
                locked=asset.get('locked', False)
            )
Пример #34
0
    def test_contentstore_attrs(self):
        """
        Test getting, setting, and defaulting the locked attr and arbitrary attrs.
        """
        location = Location("edX", "toy", "2012_Fall", "course", "2012_Fall")
        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 = AssetLocation._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 = AssetLocation._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,
            Location("bogus", "bogus", "bogus", "asset", "bogus"),
            "displayname",
        )
        assert_raises(
            NotFoundError,
            self.content_store.set_attr,
            Location("bogus", "bogus", "bogus", "asset", "bogus"),
            "displayname",
            "hello",
        )
        assert_raises(
            NotFoundError, self.content_store.get_attrs, Location("bogus", "bogus", "bogus", "asset", "bogus")
        )
        assert_raises(
            NotFoundError,
            self.content_store.set_attrs,
            Location("bogus", "bogus", "bogus", "asset", "bogus"),
            {"displayname": "hello"},
        )
        assert_raises(
            NotFoundError,
            self.content_store.set_attrs,
            Location("bogus", "bogus", "bogus", "asset", None),
            {"displayname": "hello"},
        )
Пример #35
0
class CourseTestCase(ModuleStoreTestCase):
    def setUp(self):
        """
        These tests need a user in the DB so that the django Test Client can log them in.
        The test user is created in the ModuleStoreTestCase setUp method.
        They inherit from the ModuleStoreTestCase class so that the mongodb collection
        will be cleared out before each test case execution and deleted
        afterwards.
        """
        user_password = super(CourseTestCase, self).setUp()

        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password=user_password)

        self.course = CourseFactory.create(
            org='MITx',
            number='999',
            display_name='Robot Super Course',
        )

    def create_non_staff_authed_user_client(self, authenticate=True):
        """
        Create a non-staff user, log them in (if authenticate=True), and return the client, user to use for testing.
        """
        nonstaff, password = self.create_non_staff_user()

        client = Client()
        if authenticate:
            client.login(username=nonstaff.username, password=password)
        return client, nonstaff

    def populate_course(self):
        """
        Add 2 chapters, 4 sections, 8 verticals, 16 problems to self.course (branching 2)
        """
        def descend(parent, stack):
            xblock_type = stack.pop(0)
            for _ in range(2):
                child = ItemFactory.create(category=xblock_type,
                                           parent_location=parent.location)
                if stack:
                    descend(child, stack)

        descend(self.course, ['chapter', 'sequential', 'vertical', 'problem'])

    def reload_course(self):
        """
        Reloads the course object from the database
        """
        self.course = self.store.get_course(self.course.id)

    def save_course(self):
        """
        Updates the course object in the database
        """
        self.course.save()
        self.store.update_item(self.course, self.user.id)

    TEST_VERTICAL = 'vertical_test'
    PRIVATE_VERTICAL = 'a_private_vertical'
    PUBLISHED_VERTICAL = 'a_published_vertical'
    SEQUENTIAL = 'vertical_sequential'
    LOCKED_ASSET_KEY = AssetLocation.from_deprecated_string(
        '/c4x/edX/toy/asset/sample_static.txt')

    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 check_populated_course(self, course_id):
        """
        Verifies the content of the given course, per data that was populated in import_and_populate_course
        """
        items = self.store.get_items(
            course_id,
            category='vertical',
            revision=ModuleStoreEnum.RevisionOption.published_only)
        self.check_verticals(items)

        def verify_item_publish_state(item, publish_state):
            """Verifies the publish state of the item is as expected."""
            if publish_state in (PublishState.private, PublishState.draft):
                self.assertTrue(getattr(item, 'is_draft', False))
            else:
                self.assertFalse(getattr(item, 'is_draft', False))
            self.assertEqual(self.store.compute_publish_state(item),
                             publish_state)

        def get_and_verify_publish_state(item_type, item_name, publish_state):
            """Gets the given item from the store and verifies the publish state of the item is as expected."""
            item = self.store.get_item(
                course_id.make_usage_key(item_type, item_name))
            verify_item_publish_state(item, publish_state)
            return item

        # verify that the draft vertical is draft
        vertical = get_and_verify_publish_state('vertical', self.TEST_VERTICAL,
                                                PublishState.draft)
        for child in vertical.get_children():
            verify_item_publish_state(child, PublishState.draft)

        # make sure that we don't have a sequential that is not in draft mode
        sequential = get_and_verify_publish_state('sequential',
                                                  self.SEQUENTIAL,
                                                  PublishState.public)

        # verify that we have the private vertical
        private_vertical = get_and_verify_publish_state(
            'vertical', self.PRIVATE_VERTICAL, PublishState.private)

        # verify that we have the public vertical
        public_vertical = get_and_verify_publish_state('vertical',
                                                       self.PUBLISHED_VERTICAL,
                                                       PublishState.public)

        # verify verticals are children of sequential
        for vert in [vertical, private_vertical, public_vertical]:
            self.assertIn(vert.location, sequential.children)

        # verify textbook exists
        course = self.store.get_course(course_id)
        self.assertGreater(len(course.textbooks), 0)

        # verify asset attributes of locked asset key
        self.assertAssetsEqual(self.LOCKED_ASSET_KEY,
                               self.LOCKED_ASSET_KEY.course_key, course_id)

        # verify non-portable links are rewritten
        html_module = self.store.get_item(
            course_id.make_usage_key('html', 'nonportable'))
        self.assertIn('/static/foo.jpg', html_module.data)

        return course

    def assertCoursesEqual(self, course1_id, course2_id):
        """
        Verifies the content of the two given courses are equal
        """
        course1_items = self.store.get_items(course1_id)
        course2_items = self.store.get_items(course2_id)
        self.assertGreater(len(course1_items),
                           0)  # ensure it found content instead of [] == []
        self.assertEqual(len(course1_items), len(course2_items))

        for course1_item in course1_items:
            course2_item_location = course1_item.location.map_into_course(
                course2_id)
            if course1_item.location.category == 'course':
                # mongo uses the run as the name, split uses 'course'
                store = self.store._get_modulestore_for_courseid(course2_id)  # pylint: disable=protected-access
                new_name = 'course' if isinstance(
                    store,
                    SplitMongoModuleStore) else course2_item_location.run
                course2_item_location = course2_item_location.replace(
                    name=new_name)
            course2_item = self.store.get_item(course2_item_location)

            try:
                # compare published state
                self.assertEqual(
                    self.store.compute_publish_state(course1_item),
                    self.store.compute_publish_state(course2_item))
            except AssertionError:
                # TODO LMS-11017 "Studio auto-publish course-wide features and settings"
                # Temporary hack until autopublish implemented - right now, because we call
                # update_item within create_course to set the wiki & other course-wide settings,
                # the publish version does not necessarily equal the draft version in split.
                # So if either item is in Split, just continue on
                if not isinstance(course1_item.runtime.modulestore, SplitMongoModuleStore) and \
                   not isinstance(course2_item.runtime.modulestore, SplitMongoModuleStore):
                    # old mongo calls things draft if draft exists even if it's != published; so, do more work
                    c1_state = self.compute_real_state(course1_item)
                    c2_state = self.compute_real_state(course2_item)
                    self.assertEqual(
                        c1_state, c2_state,
                        "Course item {} in state {} != course item {} in state {}"
                        .format(course1_item, c1_state, course2_item,
                                c2_state))

            # compare data
            self.assertEqual(hasattr(course1_item, 'data'),
                             hasattr(course2_item, 'data'))
            if hasattr(course1_item, 'data'):
                self.assertEqual(course1_item.data, course2_item.data)

            # compare meta-data
            self.assertEqual(own_metadata(course1_item),
                             own_metadata(course2_item))

            # compare children
            self.assertEqual(course1_item.has_children,
                             course2_item.has_children)
            if course1_item.has_children:
                expected_children = []
                for course1_item_child in course1_item.children:
                    expected_children.append(
                        course1_item_child.map_into_course(course2_id))
                # also process course2_children just in case they have version guids
                course2_children = [
                    child.version_agnostic() for child in course2_item.children
                ]
                self.assertEqual(expected_children, course2_children)

        # compare assets
        content_store = self.store.contentstore
        course1_assets, count_course1_assets = content_store.get_all_content_for_course(
            course1_id)
        _, count_course2_assets = content_store.get_all_content_for_course(
            course2_id)
        self.assertEqual(count_course1_assets, count_course2_assets)
        for asset in course1_assets:
            asset_son = asset.get('content_son', asset['_id'])
            self.assertAssetsEqual(asset_son, course1_id, course2_id)

    def check_verticals(self, items):
        """ Test getting the editing HTML for each vertical. """
        # assert is here to make sure that the course being tested actually has verticals (units) to check.
        self.assertGreater(len(items), 0,
                           "Course has no verticals (units) to check")
        for descriptor in items:
            resp = self.client.get_html(
                get_url('unit_handler', descriptor.location))
            self.assertEqual(resp.status_code, 200)

    def assertAssetsEqual(self, asset_son, course1_id, course2_id):
        """Verifies the asset of the given key has the same attributes in both given courses."""
        content_store = contentstore()
        category = asset_son.block_type if hasattr(
            asset_son, 'block_type') else asset_son['category']
        filename = asset_son.block_id if hasattr(
            asset_son, 'block_id') else asset_son['name']
        course1_asset_attrs = content_store.get_attrs(
            course1_id.make_asset_key(category, filename))
        course2_asset_attrs = content_store.get_attrs(
            course2_id.make_asset_key(category, filename))
        self.assertEqual(len(course1_asset_attrs), len(course2_asset_attrs))
        for key, value in course1_asset_attrs.iteritems():
            if key in [
                    '_id', 'filename', 'uploadDate', 'content_son',
                    'thumbnail_location'
            ]:
                pass
            else:
                self.assertEqual(value, course2_asset_attrs[key])

    def compute_real_state(self, item):
        """
        In draft mongo, compute_published_state can return draft when the draft == published, but in split,
        it'll return public in that case
        """
        supposed_state = self.store.compute_publish_state(item)
        if supposed_state == PublishState.draft and isinstance(
                item.runtime.modulestore, DraftModuleStore):
            # see if the draft differs from the published
            published = self.store.get_item(
                item.location,
                revision=ModuleStoreEnum.RevisionOption.published_only)
            if item.get_explicitly_set_fields_by_scope(
            ) != published.get_explicitly_set_fields_by_scope():
                # checking content: if published differs from item, return draft
                return supposed_state
            if item.get_explicitly_set_fields_by_scope(
                    Scope.settings
            ) != published.get_explicitly_set_fields_by_scope(Scope.settings):
                # checking settings: if published differs from item, return draft
                return supposed_state
            if item.has_children and item.children != published.children:
                # checking children: if published differs from item, return draft
                return supposed_state
            # published == item in all respects, so return public
            return PublishState.public
        elif supposed_state == PublishState.public and item.location.category in mongo.base.DIRECT_ONLY_CATEGORIES:
            if not all([
                    self.store.has_item(
                        child_loc,
                        revision=ModuleStoreEnum.RevisionOption.draft_only)
                    for child_loc in item.children
            ]):
                return PublishState.draft
            else:
                return supposed_state
        else:
            return supposed_state
Пример #36
0
 def get_location_from_path(path):
     """
     Generate an AssetKey for the given path (old c4x/org/course/asset/name syntax)
     """
     return AssetLocation.from_deprecated_string(path)
Пример #37
0
 def test_get_location_from_path(self):
     asset_location = StaticContent.get_location_from_path(u'/c4x/foo/bar/asset/images_course_image.jpg')
     self.assertEqual(
         AssetLocation(u'foo', u'bar', None, u'asset', u'images_course_image.jpg', None),
         asset_location
     )
Пример #38
0
 def get_location_from_path(path):
     """
     Generate an AssetKey for the given path (old c4x/org/course/asset/name syntax)
     """
     return AssetLocation.from_deprecated_string(path)
Пример #39
0
class CourseTestCase(ProceduralCourseTestMixin, ModuleStoreTestCase):
    """
    Base class for Studio tests that require a logged in user and a course.
    Also provides helper methods for manipulating and verifying the course.
    """
    def setUp(self):
        """
        These tests need a user in the DB so that the django Test Client can log them in.
        The test user is created in the ModuleStoreTestCase setUp method.
        They inherit from the ModuleStoreTestCase class so that the mongodb collection
        will be cleared out before each test case execution and deleted
        afterwards.
        """

        super(CourseTestCase, self).setUp()

        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username,
                          password=self.user_password)

        self.course = CourseFactory.create()

    def create_non_staff_authed_user_client(self, authenticate=True):
        """
        Create a non-staff user, log them in (if authenticate=True), and return the client, user to use for testing.
        """
        nonstaff, password = self.create_non_staff_user()

        client = AjaxEnabledTestClient()
        if authenticate:
            client.login(username=nonstaff.username, password=password)
        return client, nonstaff

    def reload_course(self):
        """
        Reloads the course object from the database
        """
        self.course = self.store.get_course(self.course.id)

    def save_course(self):
        """
        Updates the course object in the database
        """
        self.course.save()
        self.store.update_item(self.course, self.user.id)

    TEST_VERTICAL = 'vertical_test'
    ORPHAN_DRAFT_VERTICAL = 'orphan_draft_vertical'
    ORPHAN_DRAFT_HTML = 'orphan_draft_html'
    PRIVATE_VERTICAL = 'a_private_vertical'
    PUBLISHED_VERTICAL = 'a_published_vertical'
    SEQUENTIAL = 'vertical_sequential'
    DRAFT_HTML = 'draft_html'
    DRAFT_VIDEO = 'draft_video'
    LOCKED_ASSET_KEY = AssetLocation.from_deprecated_string(
        '/c4x/edX/toy/asset/sample_static.html')

    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 = CourseKey.from_string('/'.join(['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

    def check_populated_course(self, course_id):
        """
        Verifies the content of the given course, per data that was populated in import_and_populate_course
        """
        items = self.store.get_items(
            course_id,
            qualifiers={'category': 'vertical'},
            revision=ModuleStoreEnum.RevisionOption.published_only)
        self.check_verticals(items)

        def verify_item_publish_state(item, publish_state):
            """Verifies the publish state of the item is as expected."""
            self.assertEqual(self.store.has_published_version(item),
                             publish_state)

        def get_and_verify_publish_state(item_type, item_name, publish_state):
            """
            Gets the given item from the store and verifies the publish state
            of the item is as expected.
            """
            item = self.store.get_item(
                course_id.make_usage_key(item_type, item_name))
            verify_item_publish_state(item, publish_state)
            return item

        # verify draft vertical has a published version with published children
        vertical = get_and_verify_publish_state('vertical', self.TEST_VERTICAL,
                                                True)
        for child in vertical.get_children():
            verify_item_publish_state(child, True)

        # verify that it has a draft too
        self.assertTrue(getattr(vertical, "is_draft", False))

        # make sure that we don't have a sequential that is in draft mode
        sequential = get_and_verify_publish_state('sequential',
                                                  self.SEQUENTIAL, True)
        self.assertFalse(getattr(sequential, "is_draft", False))

        # verify that we have the private vertical
        private_vertical = get_and_verify_publish_state(
            'vertical', self.PRIVATE_VERTICAL, False)

        # verify that we have the public vertical
        public_vertical = get_and_verify_publish_state('vertical',
                                                       self.PUBLISHED_VERTICAL,
                                                       True)

        # verify that we have the draft html
        draft_html = self.store.get_item(
            course_id.make_usage_key('html', self.DRAFT_HTML))
        self.assertTrue(getattr(draft_html, 'is_draft', False))

        # verify that we have the draft video
        draft_video = self.store.get_item(
            course_id.make_usage_key('video', self.DRAFT_VIDEO))
        self.assertTrue(getattr(draft_video, 'is_draft', False))

        # verify verticals are children of sequential
        for vert in [vertical, private_vertical, public_vertical]:
            self.assertIn(vert.location, sequential.children)

        # verify draft html is the child of the public vertical
        self.assertIn(draft_html.location, public_vertical.children)

        # verify draft video is the child of the public vertical
        self.assertIn(draft_video.location, public_vertical.children)

        # verify textbook exists
        course = self.store.get_course(course_id)
        self.assertGreater(len(course.textbooks), 0)

        # verify asset attributes of locked asset key
        self.assertAssetsEqual(self.LOCKED_ASSET_KEY,
                               self.LOCKED_ASSET_KEY.course_key, course_id)

        # verify non-portable links are rewritten
        html_module = self.store.get_item(
            course_id.make_usage_key('html', 'nonportable'))
        self.assertIn('/static/foo.jpg', html_module.data)

        return course

    def assertCoursesEqual(self, course1_id, course2_id):
        """
        Verifies the content of the two given courses are equal
        """
        course1_items = self.store.get_items(course1_id)
        course2_items = self.store.get_items(course2_id)
        self.assertGreater(len(course1_items),
                           0)  # ensure it found content instead of [] == []
        if len(course1_items) != len(course2_items):
            course1_block_ids = set(
                [item.location.block_id for item in course1_items])
            course2_block_ids = set(
                [item.location.block_id for item in course2_items])
            raise AssertionError(
                u"Course1 extra blocks: {}; course2 extra blocks: {}".format(
                    course1_block_ids - course2_block_ids,
                    course2_block_ids - course1_block_ids))

        for course1_item in course1_items:
            course1_item_loc = course1_item.location
            course2_item_loc = course2_id.make_usage_key(
                course1_item_loc.block_type, course1_item_loc.block_id)
            if course1_item_loc.block_type == 'course':
                # mongo uses the run as the name, split uses 'course'
                store = self.store._get_modulestore_for_courselike(course2_id)  # pylint: disable=protected-access
                new_name = 'course' if isinstance(
                    store, SplitMongoModuleStore) else course2_item_loc.run
                course2_item_loc = course2_item_loc.replace(name=new_name)
            course2_item = self.store.get_item(course2_item_loc)

            # compare published state
            self.assertEqual(self.store.has_published_version(course1_item),
                             self.store.has_published_version(course2_item))

            # compare data
            self.assertEqual(hasattr(course1_item, 'data'),
                             hasattr(course2_item, 'data'))
            if hasattr(course1_item, 'data'):
                self.assertEqual(course1_item.data, course2_item.data)

            # compare meta-data
            self.assertEqual(own_metadata(course1_item),
                             own_metadata(course2_item))

            # compare children
            self.assertEqual(course1_item.has_children,
                             course2_item.has_children)
            if course1_item.has_children:
                expected_children = []
                for course1_item_child in course1_item.children:
                    expected_children.append(
                        course2_id.make_usage_key(
                            course1_item_child.block_type,
                            course1_item_child.block_id))
                self.assertEqual(expected_children, course2_item.children)

        # compare assets
        content_store = self.store.contentstore
        course1_assets, count_course1_assets = content_store.get_all_content_for_course(
            course1_id)
        _, count_course2_assets = content_store.get_all_content_for_course(
            course2_id)
        self.assertEqual(count_course1_assets, count_course2_assets)
        for asset in course1_assets:
            asset_son = asset.get('content_son', asset['_id'])
            self.assertAssetsEqual(asset_son, course1_id, course2_id)

    def check_verticals(self, items):
        """ Test getting the editing HTML for each vertical. """
        # assert is here to make sure that the course being tested actually has verticals (units) to check.
        self.assertGreater(len(items), 0,
                           "Course has no verticals (units) to check")
        for descriptor in items:
            resp = self.client.get_html(
                get_url('container_handler', descriptor.location))
            self.assertEqual(resp.status_code, 200)

    def assertAssetsEqual(self, asset_son, course1_id, course2_id):
        """Verifies the asset of the given key has the same attributes in both given courses."""
        content_store = contentstore()
        category = asset_son.block_type if hasattr(
            asset_son, 'block_type') else asset_son['category']
        filename = asset_son.block_id if hasattr(
            asset_son, 'block_id') else asset_son['name']
        course1_asset_attrs = content_store.get_attrs(
            course1_id.make_asset_key(category, filename))
        course2_asset_attrs = content_store.get_attrs(
            course2_id.make_asset_key(category, filename))
        self.assertEqual(len(course1_asset_attrs), len(course2_asset_attrs))
        for key, value in course1_asset_attrs.iteritems():
            if key in [
                    '_id', 'filename', 'uploadDate', 'content_son',
                    'thumbnail_location'
            ]:
                pass
            else:
                self.assertEqual(value, course2_asset_attrs[key])
Пример #40
0
class CourseTestCase(ModuleStoreTestCase):
    def setUp(self):
        """
        These tests need a user in the DB so that the django Test Client can log them in.
        The test user is created in the ModuleStoreTestCase setUp method.
        They inherit from the ModuleStoreTestCase class so that the mongodb collection
        will be cleared out before each test case execution and deleted
        afterwards.
        """
        user_password = super(CourseTestCase, self).setUp()

        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password=user_password)

        self.course = CourseFactory.create(
            org='MITx',
            number='999',
            display_name='Robot Super Course',
        )

    def create_non_staff_authed_user_client(self, authenticate=True):
        """
        Create a non-staff user, log them in (if authenticate=True), and return the client, user to use for testing.
        """
        nonstaff, password = self.create_non_staff_user()

        client = Client()
        if authenticate:
            client.login(username=nonstaff.username, password=password)
            nonstaff.is_authenticated = True
        return client, nonstaff

    def populate_course(self, branching=2):
        """
        Add k chapters, k^2 sections, k^3 verticals, k^4 problems to self.course (where k = branching)
        """
        user_id = self.user.id
        self.populated_usage_keys = {}

        def descend(parent, stack):
            if not stack:
                return

            xblock_type = stack[0]
            for _ in range(branching):
                child = ItemFactory.create(category=xblock_type, parent_location=parent.location, user_id=user_id)
                print child.location
                self.populated_usage_keys.setdefault(xblock_type, []).append(child.location)
                descend(child, stack[1:])

        descend(self.course, ['chapter', 'sequential', 'vertical', 'problem'])

    def reload_course(self):
        """
        Reloads the course object from the database
        """
        self.course = self.store.get_course(self.course.id)

    def save_course(self):
        """
        Updates the course object in the database
        """
        self.course.save()
        self.store.update_item(self.course, self.user.id)

    TEST_VERTICAL = 'vertical_test'
    PRIVATE_VERTICAL = 'a_private_vertical'
    PUBLISHED_VERTICAL = 'a_published_vertical'
    SEQUENTIAL = 'vertical_sequential'
    LOCKED_ASSET_KEY = AssetLocation.from_deprecated_string('/c4x/edX/toy/asset/sample_static.txt')

    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 check_populated_course(self, course_id):
        """
        Verifies the content of the given course, per data that was populated in import_and_populate_course
        """
        items = self.store.get_items(
            course_id,
            qualifiers={'category': 'vertical'},
            revision=ModuleStoreEnum.RevisionOption.published_only
        )
        self.check_verticals(items)

        def verify_item_publish_state(item, publish_state):
            """Verifies the publish state of the item is as expected."""
            self.assertEqual(self.store.has_published_version(item), publish_state)

        def get_and_verify_publish_state(item_type, item_name, publish_state):
            """Gets the given item from the store and verifies the publish state of the item is as expected."""
            item = self.store.get_item(course_id.make_usage_key(item_type, item_name))
            verify_item_publish_state(item, publish_state)
            return item

        # verify that the draft vertical is draft
        vertical = get_and_verify_publish_state('vertical', self.TEST_VERTICAL, True)
        for child in vertical.get_children():
            verify_item_publish_state(child, True)

        # make sure that we don't have a sequential that is not in draft mode
        sequential = get_and_verify_publish_state('sequential', self.SEQUENTIAL, True)

        # verify that we have the private vertical
        private_vertical = get_and_verify_publish_state('vertical', self.PRIVATE_VERTICAL, False)

        # verify that we have the public vertical
        public_vertical = get_and_verify_publish_state('vertical', self.PUBLISHED_VERTICAL, True)

        # verify verticals are children of sequential
        for vert in [vertical, private_vertical, public_vertical]:
            self.assertIn(vert.location, sequential.children)

        # verify textbook exists
        course = self.store.get_course(course_id)
        self.assertGreater(len(course.textbooks), 0)

        # verify asset attributes of locked asset key
        self.assertAssetsEqual(self.LOCKED_ASSET_KEY, self.LOCKED_ASSET_KEY.course_key, course_id)

        # verify non-portable links are rewritten
        html_module = self.store.get_item(course_id.make_usage_key('html', 'nonportable'))
        self.assertIn('/static/foo.jpg', html_module.data)

        return course

    def assertCoursesEqual(self, course1_id, course2_id):
        """
        Verifies the content of the two given courses are equal
        """
        course1_items = self.store.get_items(course1_id)
        course2_items = self.store.get_items(course2_id)
        self.assertGreater(len(course1_items), 0)  # ensure it found content instead of [] == []
        if len(course1_items) != len(course2_items):
            course1_block_ids = set([item.location.block_id for item in course1_items])
            course2_block_ids = set([item.location.block_id for item in course2_items])
            raise AssertionError(
                u"Course1 extra blocks: {}; course2 extra blocks: {}".format(
                    course1_block_ids - course2_block_ids, course2_block_ids - course1_block_ids
                )
            )

        for course1_item in course1_items:
            course1_item_loc = course1_item.location
            course2_item_loc = course2_id.make_usage_key(course1_item_loc.block_type, course1_item_loc.block_id)
            if course1_item_loc.block_type == 'course':
                # mongo uses the run as the name, split uses 'course'
                store = self.store._get_modulestore_for_courseid(course2_id)  # pylint: disable=protected-access
                new_name = 'course' if isinstance(store, SplitMongoModuleStore) else course2_item_loc.run
                course2_item_loc = course2_item_loc.replace(name=new_name)
            course2_item = self.store.get_item(course2_item_loc)

            # compare published state
            self.assertEqual(
                self.store.has_published_version(course1_item),
                self.store.has_published_version(course2_item)
            )

            # compare data
            self.assertEqual(hasattr(course1_item, 'data'), hasattr(course2_item, 'data'))
            if hasattr(course1_item, 'data'):
                self.assertEqual(course1_item.data, course2_item.data)

            # compare meta-data
            self.assertEqual(own_metadata(course1_item), own_metadata(course2_item))

            # compare children
            self.assertEqual(course1_item.has_children, course2_item.has_children)
            if course1_item.has_children:
                expected_children = []
                for course1_item_child in course1_item.children:
                    expected_children.append(
                        course2_id.make_usage_key(course1_item_child.block_type, course1_item_child.block_id)
                    )
                self.assertEqual(expected_children, course2_item.children)

        # compare assets
        content_store = self.store.contentstore
        course1_assets, count_course1_assets = content_store.get_all_content_for_course(course1_id)
        _, count_course2_assets = content_store.get_all_content_for_course(course2_id)
        self.assertEqual(count_course1_assets, count_course2_assets)
        for asset in course1_assets:
            asset_son = asset.get('content_son', asset['_id'])
            self.assertAssetsEqual(asset_son, course1_id, course2_id)

    def check_verticals(self, items):
        """ Test getting the editing HTML for each vertical. """
        # assert is here to make sure that the course being tested actually has verticals (units) to check.
        self.assertGreater(len(items), 0, "Course has no verticals (units) to check")
        for descriptor in items:
            resp = self.client.get_html(get_url('container_handler', descriptor.location))
            self.assertEqual(resp.status_code, 200)

    def assertAssetsEqual(self, asset_son, course1_id, course2_id):
        """Verifies the asset of the given key has the same attributes in both given courses."""
        content_store = contentstore()
        category = asset_son.block_type if hasattr(asset_son, 'block_type') else asset_son['category']
        filename = asset_son.block_id if hasattr(asset_son, 'block_id') else asset_son['name']
        course1_asset_attrs = content_store.get_attrs(course1_id.make_asset_key(category, filename))
        course2_asset_attrs = content_store.get_attrs(course2_id.make_asset_key(category, filename))
        self.assertEqual(len(course1_asset_attrs), len(course2_asset_attrs))
        for key, value in course1_asset_attrs.iteritems():
            if key in ['_id', 'filename', 'uploadDate', 'content_son', 'thumbnail_location']:
                pass
            else:
                self.assertEqual(value, course2_asset_attrs[key])
 def test_deprecated_init(self):
     with self.assertDeprecationWarning():
         loc = AssetLocation("foo", "bar", "baz", "cat", "name")
     self.assertTrue(isinstance(loc, AssetLocator))
     self.assertTrue(loc.deprecated)