test_import.py 12.1 KB
Newer Older
1
# -*- coding: utf-8 -*-
2
# pylint: disable=protected-access
3
"""
4
Tests for import_course_from_xml using the mongo modulestore.
5
"""
6

7 8 9 10 11
import copy
from uuid import uuid4

import ddt
from django.conf import settings
12 13
from django.test.client import Client
from django.test.utils import override_settings
14
from mock import patch
15

16
from openedx.core.djangoapps.content.course_structures.tests import SignalDisconnectTestMixin
17 18
from xmodule.contentstore.django import contentstore
from xmodule.exceptions import NotFoundError
19
from xmodule.modulestore import ModuleStoreEnum
20
from xmodule.modulestore.django import modulestore
21
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
22
from xmodule.modulestore.tests.factories import check_exact_number_of_calls, check_number_of_calls
23
from xmodule.modulestore.xml_importer import import_course_from_xml
24 25

TEST_DATA_CONTENTSTORE = copy.deepcopy(settings.CONTENTSTORE)
26
TEST_DATA_CONTENTSTORE['DOC_STORE_CONFIG']['db'] = 'test_xcontent_%s' % uuid4().hex
27

muhammad-ammar committed
28 29
TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT

30

31
@ddt.ddt
32
@override_settings(CONTENTSTORE=TEST_DATA_CONTENTSTORE, SEARCH_ENGINE=None)
33
class ContentStoreImportTest(SignalDisconnectTestMixin, ModuleStoreTestCase):
34 35
    """
    Tests that rely on the toy and test_import_course courses.
Chris Dodge committed
36
    NOTE: refactor using CourseFactory so they do not.
37 38
    """
    def setUp(self):
39
        super(ContentStoreImportTest, self).setUp()
40

41
        self.client = Client()
42
        self.client.login(username=self.user.username, password=self.user_password)
43

44
        # block_structure.update_course_in_cache cannot succeed in tests, as it needs to be run async on an lms worker
45
        self.task_patcher = patch('openedx.core.djangoapps.content.block_structure.tasks.update_course_in_cache_v2')
46 47 48 49 50 51
        self._mock_lms_task = self.task_patcher.start()

    def tearDown(self):
        self.task_patcher.stop()
        super(ContentStoreImportTest, self).tearDown()

52
    def load_test_import_course(self, target_id=None, create_if_not_present=True, module_store=None):
53
        '''
54 55
        Load the standard course used to test imports
        (for do_import_static=False behavior).
56 57
        '''
        content_store = contentstore()
58 59
        if module_store is None:
            module_store = modulestore()
60
        import_course_from_xml(
61
            module_store,
62
            self.user.id,
muhammad-ammar committed
63
            TEST_DATA_DIR,
64 65 66 67
            ['test_import_course'],
            static_content_store=content_store,
            do_import_static=False,
            verbose=True,
68 69
            target_id=target_id,
            create_if_not_present=create_if_not_present,
70
        )
71
        course_id = module_store.make_course_key('edX', 'test_import_course', '2012_Fall')
Calen Pennington committed
72
        course = module_store.get_course(course_id)
73 74
        self.assertIsNotNone(course)

75
        return module_store, content_store, course
76

77 78 79 80
    def test_import_course_into_similar_namespace(self):
        # Checks to make sure that a course with an org/course like
        # edx/course can be imported into a namespace with an org/course
        # like edx/course_name
81
        module_store, __, course = self.load_test_import_course()
82
        course_items = import_course_from_xml(
83
            module_store,
84
            self.user.id,
muhammad-ammar committed
85
            TEST_DATA_DIR,
86
            ['test_import_course_2'],
87
            target_id=course.id,
88 89 90 91
            verbose=True,
        )
        self.assertEqual(len(course_items), 1)

92 93 94 95
    def test_unicode_chars_in_course_name_import(self):
        """
        # Test that importing course with unicode 'id' and 'display name' doesn't give UnicodeEncodeError
        """
96 97 98 99
        # Test with the split modulestore because store.has_course fails in old mongo with unicode characters.
        with modulestore().default_store(ModuleStoreEnum.Type.split):
            module_store = modulestore()
            course_id = module_store.make_course_key(u'Юникода', u'unicode_course', u'échantillon')
100
            import_course_from_xml(
101 102 103 104
                module_store,
                self.user.id,
                TEST_DATA_DIR,
                ['2014_Uni'],
105 106
                target_id=course_id,
                create_if_not_present=True
107 108 109 110 111 112 113
            )

            course = module_store.get_course(course_id)
            self.assertIsNotNone(course)

            # test that course 'display_name' same as imported course 'display_name'
            self.assertEqual(course.display_name, u"Φυσικά το όνομα Unicode")
114

115 116 117 118
    def test_static_import(self):
        '''
        Stuff in static_import should always be imported into contentstore
        '''
119
        _, content_store, course = self.load_test_import_course()
120 121

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

        content = None
        try:
129
            location = course.id.make_asset_key('asset', 'should_be_imported.html')
130 131 132 133 134
            content = content_store.find(location)
        except NotFoundError:
            pass

        self.assertIsNotNone(content)
135

Calen Pennington committed
136 137 138
        # 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')
139 140 141 142 143 144 145

    def test_asset_import_nostatic(self):
        '''
        This test validates that an image asset is NOT imported when do_import_static=False
        '''
        content_store = contentstore()

146
        module_store = modulestore()
147
        import_course_from_xml(
148 149
            module_store, self.user.id, TEST_DATA_DIR, ['toy'],
            static_content_store=content_store, do_import_static=False,
150
            create_if_not_present=True, verbose=True
151
        )
152

153
        course = module_store.get_course(module_store.make_course_key('edX', 'toy', '2012_Fall'))
154 155

        # make sure we have NO assets in our contentstore
156
        all_assets, count = content_store.get_all_content_for_course(course.id)
157
        self.assertEqual(len(all_assets), 0)
158
        self.assertEqual(count, 0)
159 160

    def test_no_static_link_rewrites_on_import(self):
161
        module_store = modulestore()
162
        courses = import_course_from_xml(
163
            module_store, self.user.id, TEST_DATA_DIR, ['toy'], do_import_static=False, verbose=True,
164
            create_if_not_present=True
165
        )
166
        course_key = courses[0].id
167

168
        handouts = module_store.get_item(course_key.make_usage_key('course_info', 'handouts'))
169 170
        self.assertIn('/static/', handouts.data)

171
        handouts = module_store.get_item(course_key.make_usage_key('html', 'toyhtml'))
172
        self.assertIn('/static/', handouts.data)
173 174

    def test_tab_name_imports_correctly(self):
175
        _module_store, _content_store, course = self.load_test_import_course()
176
        print "course tabs = {0}".format(course.tabs)
177 178
        self.assertEqual(course.tabs[2]['name'], 'Syllabus')

179 180 181 182
    def test_import_performance_mongo(self):
        store = modulestore()._get_modulestore_by_type(ModuleStoreEnum.Type.mongo)

        # we try to refresh the inheritance tree for each update_item in the import
183
        with check_exact_number_of_calls(store, 'refresh_cached_metadata_inheritance_tree', 28):
184

185 186
            # _get_cached_metadata_inheritance_tree should be called once
            with check_exact_number_of_calls(store, '_get_cached_metadata_inheritance_tree', 1):
187 188

                # with bulk-edit in progress, the inheritance tree should be recomputed only at the end of the import
189 190 191
                # NOTE: On Jenkins, with memcache enabled, the number of calls here is 1.
                #       Locally, without memcache, the number of calls is 1 (publish no longer counted)
                with check_number_of_calls(store, '_compute_metadata_inheritance_tree', 1):
192
                    self.load_test_import_course(create_if_not_present=False, module_store=store)
193

194 195 196
    @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
    def test_reimport(self, default_ms_type):
        with modulestore().default_store(default_ms_type):
197 198
            __, __, course = self.load_test_import_course(create_if_not_present=True)
            self.load_test_import_course(target_id=course.id)
199

200
    def test_rewrite_reference_list(self):
201 202 203
        # This test fails with split modulestore (the HTML component is not in "different_course_id" namespace).
        # More investigation needs to be done.
        module_store = modulestore()._get_modulestore_by_type(ModuleStoreEnum.Type.mongo)
204 205
        target_id = module_store.make_course_key('testX', 'conditional_copy', 'copy_run')
        import_course_from_xml(
206
            module_store,
207
            self.user.id,
muhammad-ammar committed
208
            TEST_DATA_DIR,
209
            ['conditional'],
210
            target_id=target_id
211 212
        )
        conditional_module = module_store.get_item(
213
            target_id.make_usage_key('conditional', 'condone')
214 215
        )
        self.assertIsNotNone(conditional_module)
216
        different_course_id = module_store.make_course_key('edX', 'different_course', None)
217 218
        self.assertListEqual(
            [
219
                target_id.make_usage_key('problem', 'choiceprob'),
220
                different_course_id.make_usage_key('html', 'for_testing_import_rewrites')
221 222 223 224 225
            ],
            conditional_module.sources_list
        )
        self.assertListEqual(
            [
226 227
                target_id.make_usage_key('html', 'congrats'),
                target_id.make_usage_key('html', 'secret_page')
228 229 230 231
            ],
            conditional_module.show_tag_list
        )

232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
    def test_rewrite_reference_value_dict_published(self):
        """
        Test rewriting references in ReferenceValueDict, specifically with published content.
        """
        self._verify_split_test_import(
            'split_test_copy',
            'split_test_module',
            'split1',
            {"0": 'sample_0', "2": 'sample_2'},
        )

    def test_rewrite_reference_value_dict_draft(self):
        """
        Test rewriting references in ReferenceValueDict, specifically with draft content.
        """
        self._verify_split_test_import(
            'split_test_copy_with_draft',
            'split_test_module_draft',
            'fb34c21fe64941999eaead421a8711b8',
            {"0": '9f0941d021414798836ef140fb5f6841', "1": '0faf29473cf1497baa33fcc828b179cd'},
        )

    def _verify_split_test_import(self, target_course_name, source_course_name, split_test_name, groups_to_verticals):
255
        module_store = modulestore()
256 257
        target_id = module_store.make_course_key('testX', target_course_name, 'copy_run')
        import_course_from_xml(
258
            module_store,
259
            self.user.id,
muhammad-ammar committed
260
            TEST_DATA_DIR,
261
            [source_course_name],
262 263
            target_id=target_id,
            create_if_not_present=True
264 265
        )
        split_test_module = module_store.get_item(
266
            target_id.make_usage_key('split_test', split_test_name)
267 268
        )
        self.assertIsNotNone(split_test_module)
269 270

        remapped_verticals = {
271
            key: target_id.make_usage_key('vertical', value) for key, value in groups_to_verticals.iteritems()
272 273 274
        }

        self.assertEqual(remapped_verticals, split_test_module.group_id_to_child)
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294

    @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
    def test_video_components_present_while_import(self, store):
        """
        Test that video components with same edx_video_id are present while re-importing
        """
        with modulestore().default_store(store):
            module_store = modulestore()
            course_id = module_store.make_course_key('edX', 'test_import_course', '2012_Fall')

            # Import first time
            __, __, course = self.load_test_import_course(target_id=course_id, module_store=module_store)

            # Re-import
            __, __, re_course = self.load_test_import_course(target_id=course.id, module_store=module_store)

            vertical = module_store.get_item(re_course.id.make_usage_key('vertical', 'vertical_test'))

            video = module_store.get_item(vertical.children[1])
            self.assertEqual(video.display_name, 'default')