refactor: run modulestore tests in common/lib/... using Django

Does 3 things:
(1) Use django for modulestore tests
(2) Use normal LMS settings for modulestore tests instead of openedx/tests/settings.py
(3) Simplify some TestCase subclasses by converting them to use ModuleStoreTestCase


Details and rationale:

(1) Currently parts of the modulestore test suite are designed to run "without django", although there is still a lot of django functionality imported at times, and many of the tests do in fact use django. But for the upcoming PR #27565 (moving split's course indexes from MongoDB to MySQL), we will need to always have Django enabled. So this commit paves the way for that change.

(2) The previous tests that did use Django used a special settings file, openedx/tests/settings.py which made some debugging confusing because those tests had quite different django settings than other tests. This change deletes that file and runs the tests using the LMS test settings.

(3) The test suite also contains many different ways of initializing and testing a modulestore, with significant differences in their configuration, and also a lot of repetition. I find this makes understanding, debugging and writing tests more difficult. So this commit also reduces the number of different "test case using modulestore" base classes:

* Simplifies MixedWithOptionsTestCase and MixedSplitTestCase by making them simple subclasses of ModuleStoreTestCase.
* Removes PureModulestoreTestCase.
This commit is contained in:
Braden MacDonald
2021-07-30 14:16:18 -07:00
committed by Braden MacDonald
parent daac2f7585
commit da09bcadc5
17 changed files with 114 additions and 328 deletions

View File

@@ -1,14 +1,11 @@
"""
Testing indexing of the courseware as it is changed
"""
import json
import time
from datetime import datetime
from unittest import skip
from unittest.mock import patch
from uuid import uuid4
import ddt
import pytest
@@ -33,25 +30,14 @@ from openedx.core.djangoapps.models.course_details import CourseDetails
from xmodule.library_tools import normalize_key_for_search
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import SignalHandler, modulestore
from xmodule.modulestore.edit_info import EditInfoMixin
from xmodule.modulestore.inheritance import InheritanceMixin
from xmodule.modulestore.mixed import MixedModuleStore
from xmodule.modulestore.tests.django_utils import (
ModuleStoreTestCase,
TEST_DATA_MONGO_MODULESTORE,
TEST_DATA_SPLIT_MODULESTORE,
SharedModuleStoreTestCase
SharedModuleStoreTestCase,
)
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, LibraryFactory
from xmodule.modulestore.tests.mongo_connection import MONGO_HOST, MONGO_PORT_NUM
from xmodule.modulestore.tests.utils import (
LocationMixin,
MixedSplitTestCase,
MongoContentstoreBuilder,
create_modulestore_instance
)
from xmodule.partitions.partitions import UserPartition
from xmodule.tests import DATA_DIR
from xmodule.x_module import XModuleMixin
COURSE_CHILD_STRUCTURE = {
"course": "chapter",
@@ -93,46 +79,9 @@ def create_large_course(store, load_factor):
return course, child_count
class MixedWithOptionsTestCase(MixedSplitTestCase):
class MixedWithOptionsTestCase(ModuleStoreTestCase):
""" Base class for test cases within this file """
HOST = MONGO_HOST
PORT = MONGO_PORT_NUM
DATABASE = 'test_mongo_%s' % uuid4().hex[:5]
COLLECTION = 'modulestore'
ASSET_COLLECTION = 'assetstore'
DEFAULT_CLASS = 'xmodule.hidden_module.HiddenDescriptor'
RENDER_TEMPLATE = lambda t_n, d, ctx=None, nsp='main': ''
modulestore_options = {
'default_class': DEFAULT_CLASS,
'fs_root': DATA_DIR,
'render_template': RENDER_TEMPLATE,
'xblock_mixins': (EditInfoMixin, InheritanceMixin, LocationMixin, XModuleMixin),
}
DOC_STORE_CONFIG = {
'host': HOST,
'port': PORT,
'db': DATABASE,
'collection': COLLECTION,
'asset_collection': ASSET_COLLECTION,
}
OPTIONS = {
'stores': [
{
'NAME': 'draft',
'ENGINE': 'xmodule.modulestore.mongo.draft.DraftModuleStore',
'DOC_STORE_CONFIG': DOC_STORE_CONFIG,
'OPTIONS': modulestore_options
},
{
'NAME': 'split',
'ENGINE': 'xmodule.modulestore.split_mongo.split_draft.DraftVersioningModuleStore',
'DOC_STORE_CONFIG': DOC_STORE_CONFIG,
'OPTIONS': modulestore_options
},
],
'xblock_mixins': modulestore_options['xblock_mixins'],
}
CREATE_USER = False
INDEX_NAME = None
def setup_course_base(self, store):
@@ -155,18 +104,10 @@ class MixedWithOptionsTestCase(MixedSplitTestCase):
def _perform_test_using_store(self, store_type, test_to_perform):
""" Helper method to run a test function that uses a specific store """
with MongoContentstoreBuilder().build() as contentstore:
store = MixedModuleStore(
contentstore=contentstore,
create_modulestore_instance=create_modulestore_instance,
mappings={},
**self.OPTIONS
)
self.addCleanup(store.close_all_connections)
with store.default_store(store_type):
self.setup_course_base(store)
test_to_perform(store)
store = modulestore()
with store.default_store(store_type):
self.setup_course_base(store)
test_to_perform(store)
def publish_item(self, store, item_location):
""" publish the item at the given location """
@@ -190,6 +131,7 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase):
""" Tests the operation of the CoursewareSearchIndexer """
WORKS_WITH_STORES = (ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
ENABLED_SIGNALS = ['course_deleted']
def setUp(self):
super().setUp()
@@ -264,11 +206,8 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase):
def _test_indexing_course(self, store):
""" indexing course tests """
response = self.search()
self.assertEqual(response["total"], 0)
# Only published modules should be in the index
added_to_index = self.reindex_course(store)
added_to_index = self.reindex_course(store) # This reindex may not be necessary (it may already be indexed)
self.assertEqual(added_to_index, 3)
response = self.search()
self.assertEqual(response["total"], 3)
@@ -311,17 +250,15 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase):
Test that course will also be delete from search_index after course deletion.
"""
self.searcher = SearchEngine.get_search_engine(CourseAboutSearchIndexer.INDEX_NAME)
response = self.search()
self.assertEqual(response["total"], 0)
# index the course in search_index
# index the course in search_index (it may already be indexed)
self.reindex_course(store)
response = self.search()
self.assertEqual(response["total"], 1)
# delete the course and look course in search_index
modulestore().delete_course(self.course.id, ModuleStoreEnum.UserID.test)
self.assertIsNone(modulestore().get_course(self.course.id))
store.delete_course(self.course.id, ModuleStoreEnum.UserID.test)
self.assertIsNone(store.get_course(self.course.id))
# Now, because of contentstore.signals.handlers.listen_for_course_delete, the index should already be updated:
response = self.search()
self.assertEqual(response["total"], 0)
@@ -776,6 +713,7 @@ class TestTaskExecution(SharedModuleStoreTestCase):
self.assertFalse(mock_index.called)
@pytest.mark.django_db
@ddt.ddt
class TestLibrarySearchIndexer(MixedWithOptionsTestCase):
""" Tests the operation of the CoursewareSearchIndexer """
@@ -929,7 +867,7 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase):
"""
Tests indexing of content groups on course modules using mongo modulestore.
"""
CREATE_USER = True
MODULESTORE = TEST_DATA_MONGO_MODULESTORE
INDEX_NAME = CoursewareSearchIndexer.INDEX_NAME