Commit 4f6905ed by E. Kolpakov

Added library search indexer and tests

Search indexer is not bound to any library modifications, so tests fail - this is expected.
parent d34c6a07
...@@ -10,10 +10,8 @@ from eventtracking import tracker ...@@ -10,10 +10,8 @@ from eventtracking import tracker
from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore import ModuleStoreEnum
from search.search_engine_base import SearchEngine from search.search_engine_base import SearchEngine
from opaque_keys.edx.locator import CourseLocator, LibraryLocator
# Use default index and document names for now
INDEX_NAME = "courseware_index"
DOCUMENT_TYPE = "courseware_content"
# REINDEX_AGE is the default amount of time that we look back for changes # REINDEX_AGE is the default amount of time that we look back for changes
# that might have happened. If we are provided with a time at which the # that might have happened. If we are provided with a time at which the
...@@ -40,18 +38,56 @@ class SearchIndexingError(Exception): ...@@ -40,18 +38,56 @@ class SearchIndexingError(Exception):
self.error_list = error_list self.error_list = error_list
class CoursewareSearchIndexer(object): class SearchIndexBase(object):
""" """
Class to perform indexing for courseware search from different modulestores Class to perform indexing for courseware search from different modulestores
""" """
INDEX_NAME = None
DOCUMENT_TYPE = None
INDEX_EVENT = {
'name': None,
'category': None
}
@classmethod
def _fetch_top_level(self, modulestore, structure_key):
""" Fetch the item from the modulestore location """
raise NotImplementedError("Should be overridden in child classes")
@classmethod
def _get_location_info(self, structure_key):
""" Builds location info dictionary """
raise NotImplementedError("Should be overridden in child classes")
@classmethod
def _id_modifier(self, usage_id):
""" Modifies usage_id to submit to index """
return usage_id
@classmethod
def remove_deleted_items(cls, searcher, structure_key, exclude_items):
"""
remove any item that is present in the search index that is not present in updated list of indexed items
as we find items we can shorten the set of items to keep
"""
response = searcher.search(
doc_type=cls.DOCUMENT_TYPE,
field_dictionary=cls._get_location_info(structure_key),
exclude_ids=exclude_items
)
result_ids = [result["data"]["id"] for result in response["results"]]
for result_id in result_ids:
searcher.remove(cls.DOCUMENT_TYPE, result_id)
@classmethod @classmethod
def index_course(cls, modulestore, course_key, triggered_at=None, reindex_age=REINDEX_AGE): def index(cls, modulestore, structure_key, triggered_at=None, reindex_age=REINDEX_AGE):
""" """
Process course for indexing Process course for indexing
Arguments: Arguments:
course_key (CourseKey) - course identifier structure_key (CourseKey|LibraryKey) - course or library identifier
triggered_at (datetime) - provides time at which indexing was triggered; triggered_at (datetime) - provides time at which indexing was triggered;
useful for index updates - only things changed recently from that date useful for index updates - only things changed recently from that date
...@@ -64,13 +100,11 @@ class CoursewareSearchIndexer(object): ...@@ -64,13 +100,11 @@ class CoursewareSearchIndexer(object):
Number of items that have been added to the index Number of items that have been added to the index
""" """
error_list = [] error_list = []
searcher = SearchEngine.get_search_engine(INDEX_NAME) searcher = SearchEngine.get_search_engine(cls.INDEX_NAME)
if not searcher: if not searcher:
return return
location_info = { location_info = cls._get_location_info(structure_key)
"course": unicode(course_key),
}
# Wrap counter in dictionary - otherwise we seem to lose scope inside the embedded function `index_item` # Wrap counter in dictionary - otherwise we seem to lose scope inside the embedded function `index_item`
indexed_count = { indexed_count = {
...@@ -101,7 +135,7 @@ class CoursewareSearchIndexer(object): ...@@ -101,7 +135,7 @@ class CoursewareSearchIndexer(object):
if not item_index_dictionary and not item.has_children: if not item_index_dictionary and not item.has_children:
return return
item_id = unicode(item.scope_ids.usage_id) item_id = unicode(cls._id_modifier(item.scope_ids.usage_id))
indexed_items.add(item_id) indexed_items.add(item_id)
if item.has_children: if item.has_children:
# determine if it's okay to skip adding the children herein based upon how recently any may have changed # determine if it's okay to skip adding the children herein based upon how recently any may have changed
...@@ -122,38 +156,24 @@ class CoursewareSearchIndexer(object): ...@@ -122,38 +156,24 @@ class CoursewareSearchIndexer(object):
if item.start: if item.start:
item_index['start_date'] = item.start item_index['start_date'] = item.start
searcher.index(DOCUMENT_TYPE, item_index) searcher.index(cls.DOCUMENT_TYPE, item_index)
indexed_count["count"] += 1 indexed_count["count"] += 1
except Exception as err: # pylint: disable=broad-except except Exception as err: # pylint: disable=broad-except
# broad exception so that index operation does not fail on one item of many # broad exception so that index operation does not fail on one item of many
log.warning('Could not index item: %s - %r', item.location, err) log.warning('Could not index item: %s - %r', item.location, err)
error_list.append(_('Could not index item: {}').format(item.location)) error_list.append(_('Could not index item: {}').format(item.location))
def remove_deleted_items():
"""
remove any item that is present in the search index that is not present in updated list of indexed items
as we find items we can shorten the set of items to keep
"""
response = searcher.search(
doc_type=DOCUMENT_TYPE,
field_dictionary={"course": unicode(course_key)},
exclude_ids=indexed_items
)
result_ids = [result["data"]["id"] for result in response["results"]]
for result_id in result_ids:
searcher.remove(DOCUMENT_TYPE, result_id)
try: try:
with modulestore.branch_setting(ModuleStoreEnum.RevisionOption.published_only): with modulestore.branch_setting(ModuleStoreEnum.RevisionOption.published_only):
course = modulestore.get_course(course_key, depth=None) structure = cls._fetch_top_level(modulestore, structure_key)
for item in course.get_children(): for item in structure.get_children():
index_item(item) index_item(item)
remove_deleted_items() cls.remove_deleted_items(searcher, structure_key, indexed_items)
except Exception as err: # pylint: disable=broad-except except Exception as err: # pylint: disable=broad-except
# broad exception so that index operation does not prevent the rest of the application from working # broad exception so that index operation does not prevent the rest of the application from working
log.exception( log.exception(
"Indexing error encountered, courseware index may be out of date %s - %r", "Indexing error encountered, courseware index may be out of date %s - %r",
course_key, structure_key,
err err
) )
error_list.append(_('General indexing error occurred')) error_list.append(_('General indexing error occurred'))
...@@ -164,31 +184,93 @@ class CoursewareSearchIndexer(object): ...@@ -164,31 +184,93 @@ class CoursewareSearchIndexer(object):
return indexed_count["count"] return indexed_count["count"]
@classmethod @classmethod
def do_course_reindex(cls, modulestore, course_key): def _do_reindex(cls, modulestore, structure_key):
""" """
(Re)index all content within the given course, tracking the fact that a full reindex has taking place (Re)index all content within the given structure (course or library),
tracking the fact that a full reindex has taken place
""" """
indexed_count = cls.index_course(modulestore, course_key) indexed_count = cls.index(modulestore, structure_key)
if indexed_count: if indexed_count:
cls._track_index_request('edx.course.index.reindexed', indexed_count) cls._track_index_request(cls.INDEX_EVENT['name'], cls.INDEX_EVENT['category'], indexed_count)
return indexed_count return indexed_count
@classmethod @classmethod
def _track_index_request(cls, event_name, indexed_count): def _track_index_request(cls, event_name, category, indexed_count):
"""Track content index requests. """Track content index requests.
Arguments: Arguments:
event_name (str): Name of the event to be logged. event_name (str): Name of the event to be logged.
category (str): cat3gory of indexed items
indexed_count (int): number of indexed items
Returns: Returns:
None None
""" """
data = { data = {
"indexed_count": indexed_count, "indexed_count": indexed_count,
'category': 'courseware_index', 'category': category,
} }
tracker.emit( tracker.emit(
event_name, event_name,
data data
) )
class CoursewareSearchIndexer(SearchIndexBase):
INDEX_NAME = "courseware_index"
DOCUMENT_TYPE = "courseware_content"
INDEX_EVENT = {
'name': 'edx.course.index.reindexed',
'category': 'courseware_index'
}
@classmethod
def _fetch_top_level(self, modulestore, structure_key):
""" Fetch the item from the modulestore location """
return modulestore.get_course(structure_key, depth=None)
@classmethod
def _get_location_info(self, structure_key):
""" Builds location info dictionary """
return {"course": unicode(structure_key)}
@classmethod
def do_course_reindex(cls, modulestore, course_key):
"""
(Re)index all content within the given course, tracking the fact that a full reindex has taken place
"""
return cls._do_reindex(modulestore, course_key)
class LibrarySearchIndexer(SearchIndexBase):
INDEX_NAME = "library_index"
DOCUMENT_TYPE = "library_content"
INDEX_EVENT = {
'name': 'edx.library.index.reindexed',
'category': 'library_index'
}
@classmethod
def _fetch_top_level(self, modulestore, structure_key):
""" Fetch the item from the modulestore location """
return modulestore.get_library(structure_key, depth=None)
@classmethod
def _get_location_info(self, structure_key):
""" Builds location info dictionary """
return {"library": unicode(structure_key.replace(version_guid=None, branch=None))}
@classmethod
def _id_modifier(self, usage_id):
""" Modifies usage_id to submit to index """
return usage_id.replace(library_key=(usage_id.library_key.replace(version_guid=None, branch=None)))
@classmethod
def do_library_reindex(cls, modulestore, library_key):
"""
(Re)index all content within the given library, tracking the fact that a full reindex has taken place
"""
return cls._do_reindex(modulestore, library_key)
\ No newline at end of file
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
Testing indexing of the courseware as it is changed Testing indexing of the courseware as it is changed
""" """
import ddt import ddt
from lazy.lazy import lazy
import time import time
from datetime import datetime from datetime import datetime
from mock import patch from mock import patch
...@@ -15,15 +16,18 @@ from xmodule.modulestore.edit_info import EditInfoMixin ...@@ -15,15 +16,18 @@ from xmodule.modulestore.edit_info import EditInfoMixin
from xmodule.modulestore.inheritance import InheritanceMixin from xmodule.modulestore.inheritance import InheritanceMixin
from xmodule.modulestore.mixed import MixedModuleStore from xmodule.modulestore.mixed import MixedModuleStore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, LibraryFactory
from xmodule.modulestore.tests.mongo_connection import MONGO_PORT_NUM, MONGO_HOST from xmodule.modulestore.tests.mongo_connection import MONGO_PORT_NUM, MONGO_HOST
from xmodule.modulestore.tests.test_cross_modulestore_import_export import MongoContentstoreBuilder from xmodule.modulestore.tests.test_cross_modulestore_import_export import MongoContentstoreBuilder
from xmodule.modulestore.tests.utils import create_modulestore_instance, LocationMixin, MixedSplitTestCase from xmodule.modulestore.tests.utils import create_modulestore_instance, LocationMixin, MixedSplitTestCase
from xmodule.tests import DATA_DIR from xmodule.tests import DATA_DIR
from xmodule.x_module import XModuleMixin from xmodule.x_module import XModuleMixin
from search.search_engine_base import SearchEngine from search.search_engine_base import SearchEngine
from contentstore.courseware_index import CoursewareSearchIndexer, INDEX_NAME, DOCUMENT_TYPE, SearchIndexingError from contentstore.courseware_index import (
CoursewareSearchIndexer, LibrarySearchIndexer, SearchIndexingError, get_indexer_for_location
)
from contentstore.signals import listen_for_course_publish from contentstore.signals import listen_for_course_publish
...@@ -123,9 +127,21 @@ class MixedWithOptionsTestCase(MixedSplitTestCase): ...@@ -123,9 +127,21 @@ class MixedWithOptionsTestCase(MixedSplitTestCase):
""" base version of setup_course_base is a no-op """ """ base version of setup_course_base is a no-op """
pass pass
@lazy
def searcher(self):
return self.get_search_engine()
def _get_default_search(self):
""" Returns field_dictionary for default search """
return {}
def get_search_engine(self): def get_search_engine(self):
""" Centralized call to getting the search engine for the test """ """ Centralized call to getting the search engine for the test """
return SearchEngine.get_search_engine(INDEX_NAME) return SearchEngine.get_search_engine(CoursewareSearchIndexer.INDEX_NAME)
def search(self, field_dictionary=None):
fields = field_dictionary if field_dictionary else self._get_default_search()
return self.searcher.search(field_dictionary=fields)
def _perform_test_using_store(self, store_type, test_to_perform): def _perform_test_using_store(self, store_type, test_to_perform):
""" Helper method to run a test function that uses a specific store """ """ Helper method to run a test function that uses a specific store """
...@@ -217,38 +233,39 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase): ...@@ -217,38 +233,39 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase):
def index_recent_changes(self, store, since_time): def index_recent_changes(self, store, since_time):
""" index course using recent changes """ """ index course using recent changes """
trigger_time = datetime.now(UTC) trigger_time = datetime.now(UTC)
return CoursewareSearchIndexer.index_course( return CoursewareSearchIndexer.index(
store, store,
self.course.id, self.course.id,
triggered_at=trigger_time, triggered_at=trigger_time,
reindex_age=(trigger_time - since_time) reindex_age=(trigger_time - since_time)
) )
def _get_default_search(self):
return {"course": unicode(self.course.id)}
def _test_indexing_course(self, store): def _test_indexing_course(self, store):
""" indexing course tests """ """ indexing course tests """
searcher = self.get_search_engine() response = self.search()
response = searcher.search(field_dictionary={"course": unicode(self.course.id)})
self.assertEqual(response["total"], 0) self.assertEqual(response["total"], 0)
# Only published modules should be in the index # Only published modules should be in the index
added_to_index = self.reindex_course(store) added_to_index = self.reindex_course(store)
self.assertEqual(added_to_index, 3) self.assertEqual(added_to_index, 3)
response = searcher.search(field_dictionary={"course": unicode(self.course.id)}) response = self.search()
self.assertEqual(response["total"], 3) self.assertEqual(response["total"], 3)
# Publish the vertical as is, and any unpublished children should now be available # Publish the vertical as is, and any unpublished children should now be available
self.publish_item(store, self.vertical.location) self.publish_item(store, self.vertical.location)
self.reindex_course(store) self.reindex_course(store)
response = searcher.search(field_dictionary={"course": unicode(self.course.id)}) response = self.search()
self.assertEqual(response["total"], 4) self.assertEqual(response["total"], 4)
def _test_not_indexing_unpublished_content(self, store): def _test_not_indexing_unpublished_content(self, store):
""" add a new one, only appers in index once added """ """ add a new one, only appers in index once added """
searcher = self.get_search_engine()
# Publish the vertical to start with # Publish the vertical to start with
self.publish_item(store, self.vertical.location) self.publish_item(store, self.vertical.location)
self.reindex_course(store) self.reindex_course(store)
response = searcher.search(field_dictionary={"course": unicode(self.course.id)}) response = self.search()
self.assertEqual(response["total"], 4) self.assertEqual(response["total"], 4)
# Now add a new unit to the existing vertical # Now add a new unit to the existing vertical
...@@ -260,44 +277,42 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase): ...@@ -260,44 +277,42 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase):
modulestore=store, modulestore=store,
) )
self.reindex_course(store) self.reindex_course(store)
response = searcher.search(field_dictionary={"course": unicode(self.course.id)}) response = self.search()
self.assertEqual(response["total"], 4) self.assertEqual(response["total"], 4)
# Now publish it and we should find it # Now publish it and we should find it
# Publish the vertical as is, and everything should be available # Publish the vertical as is, and everything should be available
self.publish_item(store, self.vertical.location) self.publish_item(store, self.vertical.location)
self.reindex_course(store) self.reindex_course(store)
response = searcher.search(field_dictionary={"course": unicode(self.course.id)}) response = self.search()
self.assertEqual(response["total"], 5) self.assertEqual(response["total"], 5)
def _test_deleting_item(self, store): def _test_deleting_item(self, store):
""" test deleting an item """ """ test deleting an item """
searcher = self.get_search_engine()
# Publish the vertical to start with # Publish the vertical to start with
self.publish_item(store, self.vertical.location) self.publish_item(store, self.vertical.location)
self.reindex_course(store) self.reindex_course(store)
response = searcher.search(field_dictionary={"course": unicode(self.course.id)}) response = self.search()
self.assertEqual(response["total"], 4) self.assertEqual(response["total"], 4)
# just a delete should not change anything # just a delete should not change anything
self.delete_item(store, self.html_unit.location) self.delete_item(store, self.html_unit.location)
self.reindex_course(store) self.reindex_course(store)
response = searcher.search(field_dictionary={"course": unicode(self.course.id)}) response = self.search()
self.assertEqual(response["total"], 4) self.assertEqual(response["total"], 4)
# but after publishing, we should no longer find the html_unit # but after publishing, we should no longer find the html_unit
self.publish_item(store, self.vertical.location) self.publish_item(store, self.vertical.location)
self.reindex_course(store) self.reindex_course(store)
response = searcher.search(field_dictionary={"course": unicode(self.course.id)}) response = self.search()
self.assertEqual(response["total"], 3) self.assertEqual(response["total"], 3)
def _test_not_indexable(self, store): def _test_not_indexable(self, store):
""" test not indexable items """ """ test not indexable items """
searcher = self.get_search_engine()
# Publish the vertical to start with # Publish the vertical to start with
self.publish_item(store, self.vertical.location) self.publish_item(store, self.vertical.location)
self.reindex_course(store) self.reindex_course(store)
response = searcher.search(field_dictionary={"course": unicode(self.course.id)}) response = self.search()
self.assertEqual(response["total"], 4) self.assertEqual(response["total"], 4)
# Add a non-indexable item # Add a non-indexable item
...@@ -309,25 +324,24 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase): ...@@ -309,25 +324,24 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase):
modulestore=store, modulestore=store,
) )
self.reindex_course(store) self.reindex_course(store)
response = searcher.search(field_dictionary={"course": unicode(self.course.id)}) response = self.search()
self.assertEqual(response["total"], 4) self.assertEqual(response["total"], 4)
# even after publishing, we should not find the non-indexable item # even after publishing, we should not find the non-indexable item
self.publish_item(store, self.vertical.location) self.publish_item(store, self.vertical.location)
self.reindex_course(store) self.reindex_course(store)
response = searcher.search(field_dictionary={"course": unicode(self.course.id)}) response = self.search()
self.assertEqual(response["total"], 4) self.assertEqual(response["total"], 4)
def _test_start_date_propagation(self, store): def _test_start_date_propagation(self, store):
""" make sure that the start date is applied at the right level """ """ make sure that the start date is applied at the right level """
searcher = self.get_search_engine()
early_date = self.course.start early_date = self.course.start
later_date = self.vertical.start later_date = self.vertical.start
# Publish the vertical # Publish the vertical
self.publish_item(store, self.vertical.location) self.publish_item(store, self.vertical.location)
self.reindex_course(store) self.reindex_course(store)
response = searcher.search(field_dictionary={"course": unicode(self.course.id)}) response = self.search()
self.assertEqual(response["total"], 4) self.assertEqual(response["total"], 4)
results = response["results"] results = response["results"]
...@@ -438,13 +452,13 @@ class TestLargeCourseDeletions(MixedWithOptionsTestCase): ...@@ -438,13 +452,13 @@ class TestLargeCourseDeletions(MixedWithOptionsTestCase):
def _clean_course_id(self): def _clean_course_id(self):
""" Clean all documents from the index that have a specific course provided """ """ Clean all documents from the index that have a specific course provided """
if self.course_id: if self.course_id:
searcher = self.get_search_engine()
response = searcher.search(field_dictionary={"course": self.course_id}) response = self.searcher.search(field_dictionary={"course": self.course_id})
while response["total"] > 0: while response["total"] > 0:
for item in response["results"]: for item in response["results"]:
searcher.remove(DOCUMENT_TYPE, item["data"]["id"]) self.searcher.remove(TestCoursewareSearchIndexer.DOCUMENT_TYPE, item["data"]["id"])
searcher.remove(DOCUMENT_TYPE, item["data"]["id"]) self.searcher.remove(TestCoursewareSearchIndexer.DOCUMENT_TYPE, item["data"]["id"])
response = searcher.search(field_dictionary={"course": self.course_id}) response = self.searcher.search(field_dictionary={"course": self.course_id})
self.course_id = None self.course_id = None
def setUp(self): def setUp(self):
...@@ -457,8 +471,8 @@ class TestLargeCourseDeletions(MixedWithOptionsTestCase): ...@@ -457,8 +471,8 @@ class TestLargeCourseDeletions(MixedWithOptionsTestCase):
def assert_search_count(self, expected_count): def assert_search_count(self, expected_count):
""" Check that the search within this course will yield the expected number of results """ """ Check that the search within this course will yield the expected number of results """
searcher = self.get_search_engine()
response = searcher.search(field_dictionary={"course": self.course_id}) response = self.searcher.search(field_dictionary={"course": self.course_id})
self.assertEqual(response["total"], expected_count) self.assertEqual(response["total"], expected_count)
def _do_test_large_course_deletion(self, store, load_factor): def _do_test_large_course_deletion(self, store, load_factor):
...@@ -553,7 +567,7 @@ class TestTaskExecution(ModuleStoreTestCase): ...@@ -553,7 +567,7 @@ class TestTaskExecution(ModuleStoreTestCase):
def test_task_indexing_course(self): def test_task_indexing_course(self):
""" Making sure that the receiver correctly fires off the task when invoked by signal """ """ Making sure that the receiver correctly fires off the task when invoked by signal """
searcher = SearchEngine.get_search_engine(INDEX_NAME) searcher = SearchEngine.get_search_engine(CoursewareSearchIndexer.INDEX_NAME)
response = searcher.search(field_dictionary={"course": unicode(self.course.id)}) response = searcher.search(field_dictionary={"course": unicode(self.course.id)})
self.assertEqual(response["total"], 0) self.assertEqual(response["total"], 0)
...@@ -563,3 +577,173 @@ class TestTaskExecution(ModuleStoreTestCase): ...@@ -563,3 +577,173 @@ class TestTaskExecution(ModuleStoreTestCase):
# Note that this test will only succeed if celery is working in inline mode # Note that this test will only succeed if celery is working in inline mode
response = searcher.search(field_dictionary={"course": unicode(self.course.id)}) response = searcher.search(field_dictionary={"course": unicode(self.course.id)})
self.assertEqual(response["total"], 3) self.assertEqual(response["total"], 3)
@ddt.ddt
class TestLibrarySearchIndexer(MixedWithOptionsTestCase):
""" Tests the operation of the CoursewareSearchIndexer """
def setUp(self):
super(TestLibrarySearchIndexer, self).setUp()
self.library = None
self.html_unit1 = None
self.html_unit2 = None
def setup_course_base(self, store):
"""
Set up the for the course outline tests.
"""
self.library = LibraryFactory.create(modulestore=store)
self.html_unit1 = ItemFactory.create(
parent_location=self.library.location,
category="html",
display_name="Html Content",
modulestore=store,
publish_item=False,
)
self.html_unit2 = ItemFactory.create(
parent_location=self.library.location,
category="html",
display_name="Html Content 2",
modulestore=store,
publish_item=False,
)
def _get_default_search(self):
""" Returns field_dictionary for default search """
return {"library": unicode(self.library.location.replace(version_guid=None, branch=None))}
def reindex_library(self, store):
""" kick off complete reindex of the course """
return LibrarySearchIndexer.do_library_reindex(store, self.library.location.library_key)
def _get_contents(self, response):
""" Extracts contents from search response """
return [item['contents'] for item in response['results']]
def index_recent_changes(self, store, since_time):
""" index course using recent changes """
trigger_time = datetime.now(UTC)
return LibrarySearchIndexer.index(
store,
self.library.id,
triggered_at=trigger_time,
reindex_age=(trigger_time - since_time)
)
def _test_indexing_library(self, store):
""" indexing course tests """
response = self.search()
self.assertEqual(response["total"], 2)
added_to_index = self.reindex_library(store)
self.assertEqual(added_to_index, 2)
response = self.search()
self.assertEqual(response["total"], 2)
def _test_creating_item(self, store):
""" test updating an item """
response = self.search()
self.assertEqual(response["total"], 2)
# updating a library item causes immediate reindexing
data = "Some data"
ItemFactory.create(
parent_location=self.library.location,
category="html",
display_name="Html Content 3",
data=data,
modulestore=store,
publish_item=False,
)
response = self.search()
self.assertEqual(response["total"], 3)
html_contents = [cont['html_content'] for cont in self._get_contents(response)]
self.assertIn(data, html_contents)
def _test_updating_item(self, store):
""" test updating an item """
response = self.search()
self.assertEqual(response["total"], 2)
# updating a library item causes immediate reindexing
new_data = "I'm new data"
self.html_unit1.data = new_data
self.reindex_library(store)
response = self.search()
self.assertEqual(response["total"], 2)
html_contents = [cont['html_content'] for cont in self._get_contents(response)]
self.assertIn(new_data, html_contents)
def _test_deleting_item(self, store):
""" test deleting an item """
response = self.search()
self.assertEqual(response["total"], 2)
# deleting a library item causes immediate reindexing
self.delete_item(store, self.html_unit1.location)
self.reindex_library(store)
response = self.search()
self.assertEqual(response["total"], 1)
def _test_not_indexable(self, store):
""" test not indexable items """
response = self.search()
self.assertEqual(response["total"], 2)
# Add a non-indexable item
ItemFactory.create(
parent_location=self.library.location,
category="problem",
display_name="Some other content",
publish_item=False,
modulestore=store,
)
self.reindex_library(store)
response = self.search()
self.assertEqual(response["total"], 2)
@patch('django.conf.settings.SEARCH_ENGINE', None)
def _test_search_disabled(self, store):
""" if search setting has it as off, confirm that nothing is indexed """
indexed_count = self.reindex_library(store)
self.assertFalse(indexed_count)
@patch('django.conf.settings.SEARCH_ENGINE', 'search.tests.utils.ErroringIndexEngine')
def _test_exception(self, store):
""" Test that exception within indexing yields a SearchIndexingError """
with self.assertRaises(SearchIndexingError):
self.reindex_library(store)
@ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
def test_indexing_library(self, store_type):
self._perform_test_using_store(store_type, self._test_indexing_library)
@ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
def test_updating_item(self, store_type):
self._perform_test_using_store(store_type, self._test_updating_item)
@ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
def test_creating_item(self, store_type):
self._perform_test_using_store(store_type, self._test_creating_item)
@ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
def test_deleting_item(self, store_type):
self._perform_test_using_store(store_type, self._test_deleting_item)
@ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
def test_not_indexable(self, store_type):
self._perform_test_using_store(store_type, self._test_not_indexable)
@ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
def test_search_disabled(self, store_type):
self._perform_test_using_store(store_type, self._test_search_disabled)
@ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
def test_exception(self, store_type):
self._perform_test_using_store(store_type, self._test_exception)
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment