Secure your code as it's written. Use Snyk Code to scan source code in minutes - no build needed - and fix issues immediately.
from unittest import mock
from django.db import connection
from django.test import TestCase
from richie.apps.courses.factories import (
CategoryFactory,
CourseFactory,
CourseRunFactory,
OrganizationFactory,
)
from richie.apps.search.indexers.courses import CoursesIndexer
@mock.patch.object( # Avoid messing up the development Elasticsearch index
CoursesIndexer,
"index_name",
new_callable=mock.PropertyMock,
return_value="test_courses",
)
@mock.patch("richie.apps.search.index_manager.bulk") # Mock call to Elasticsearch
class CoursesSignalsTestCase(TestCase):
"""
Test signals to keep the Elasticsearch indices up-to-date.
"""
@staticmethod
def run_commit_hooks():
"""
Run commit hooks as if the database transaction had been successful.
"""
while connection.run_on_commit:
from django.test.utils import override_settings
from django.utils import timezone
import pytz
from cms.test_utils.testcases import CMSTestCase
from elasticsearch.exceptions import NotFoundError
from richie.apps.search import ES_CLIENT
from richie.apps.search.indexers.courses import CoursesIndexer
# Patch the formatter once so we can keep our tests focused on what we're actually testing
# and avoid the distraction of passing around full-featured records.
@mock.patch.object(
CoursesIndexer,
"format_es_object_for_api",
side_effect=lambda es_course: "Course #{:n}".format(es_course["_id"]),
)
class CoursesViewsetsTestCase(CMSTestCase):
"""
Test the API endpoints for courses (list and details)
"""
def setUp(self):
"""
Make sure all our tests are timezone-agnostic. Some of them parse ISO datetimes and those
would be broken if we did not enforce timezone normalization.
"""
super().setUp()
timezone.activate(pytz.utc)
def test_indices_list(self):
"""The IndicesList instance gives access to indices via properties or iteration."""
indices = IndicesList(
courses="richie.apps.search.indexers.courses.CoursesIndexer",
organizations="richie.apps.search.indexers.organizations.OrganizationsIndexer",
)
self.assertEqual(indices.courses, CoursesIndexer)
self.assertEqual(list(indices), [CoursesIndexer, OrganizationsIndexer])
# > [[3, ["H", "D"]], [0, ["C", "F"]], [1, ["B", "A"]], [2, ["G", "E"]]]
courses_definition = [[i, suite[2 * i : 2 * i + 2]] for i in range(4)] # noqa
# Index these 4 courses in Elasticsearch
indices_client = IndicesClient(client=ES_CLIENT)
# Delete any existing indices so we get a clean slate
indices_client.delete(index="_all")
# Create an index we'll use to test the ES features
indices_client.create(index="test_courses")
indices_client.close(index="test_courses")
indices_client.put_settings(body=ANALYSIS_SETTINGS, index="test_courses")
indices_client.open(index="test_courses")
# Use the default courses mapping from the Indexer
indices_client.put_mapping(
body=CoursesIndexer.mapping, doc_type="course", index="test_courses"
)
# Add the sorting script
ES_CLIENT.put_script(id="state", body=CoursesIndexer.scripts["state"])
# Actually insert our courses in the index
now = arrow.utcnow()
actions = [
{
"_id": course_id,
"_index": "test_courses",
"_op_type": "create",
"_type": "course",
# The sorting algorithm assumes that course runs are sorted by decreasing
# end date in order to limit the number of iterations and courses with a
# lot of archived courses.
"absolute_url": {"en": "url"},
"cover_image": {"en": "cover_image.jpg"},
]
indices_client = IndicesClient(client=ES_CLIENT)
# Delete any existing indices so we get a clean slate
indices_client.delete(index="_all")
# Create an index we'll use to test the ES features
indices_client.create(index=COURSES_INDEX)
# The index needs to be closed before we set an analyzer
indices_client.close(index=COURSES_INDEX)
indices_client.put_settings(body=ANALYSIS_SETTINGS, index=COURSES_INDEX)
indices_client.open(index=COURSES_INDEX)
# Use the default courses mapping from the Indexer
indices_client.put_mapping(
body=CoursesIndexer.mapping, doc_type="course", index=COURSES_INDEX
)
# Add the sorting script
ES_CLIENT.put_script(id="state", body=CoursesIndexer.scripts["state"])
# Actually insert our courses in the index
actions = [
{
"_id": course["id"],
"_index": COURSES_INDEX,
"_op_type": "create",
"_type": "course",
"absolute_url": {"en": "en/url", "fr": "fr/url"},
"categories": ["1", "2", "3"],
"cover_image": {"en": "en/image", "fr": "fr/image"},
"is_meta": False,
"logo": {"en": "/en/some/img.png", "fr": "/fr/some/img.png"},
"nb_children": 0,
# As it is the only page we create, we expect it to have the path "0001"
CategoryFactory(page_reverse_id="subjects", should_publish=True)
# Index these 4 courses in Elasticsearch
indices_client = IndicesClient(client=ES_CLIENT)
# Delete any existing indices so we get a clean slate
indices_client.delete(index="_all")
# Create an index we'll use to test the ES features
indices_client.create(index="test_courses")
indices_client.close(index="test_courses")
indices_client.put_settings(body=ANALYSIS_SETTINGS, index="test_courses")
indices_client.open(index="test_courses")
# Use the default courses mapping from the Indexer
indices_client.put_mapping(
body=CoursesIndexer.mapping, doc_type="course", index="test_courses"
)
# Add the sorting script
ES_CLIENT.put_script(id="state", body=CoursesIndexer.scripts["state"])
# Actually insert our courses in the index
actions = [
{
"_id": course["id"],
"_index": "test_courses",
"_op_type": "create",
"_type": "course",
"absolute_url": {"en": "url"},
"cover_image": {"en": "image"},
"title": {"en": "title"},
**course,
"course_runs": [
{
"organizations": [],
"organizations_names": {"en": []},
"persons": [],
"persons_names": {"en": []},
"title": {"en": "Nullam ornare finibus sollicitudin."},
},
]
# Reduce the number of languages down to the top 11 (for simplicity, like our 11 subjects)
@mock.patch.dict(ALL_LANGUAGES_DICT, **NEW_LANGUAGES_DICT, clear=True)
@mock.patch.object( # Avoid having to build the categories and organizations indices
IndexableFilterDefinition, "get_i18n_names", return_value=INDEXABLE_SUBJECTS
)
@mock.patch.object( # Avoid messing up the development Elasticsearch index
CoursesIndexer,
"index_name",
new_callable=mock.PropertyMock,
return_value="test_courses",
)
class FacetsCoursesQueryTestCase(TestCase):
"""
Test search queries on courses to make sure they respect our default and maximum limits
for displayed facet counts in filters.
NB: We run all tests on two different filters as there are two different implementations
under the hood: one for automatic ES "terms" filters, and another one for manual filters
based on hand-crafted aggregations.
"""
def setUp(self):
"""Reset indexable filters cache before each test so the context is as expected."""
The organization and category ids in the Elasticsearch course document should be
the same as the ids with which the corresponding organization and category objects
are indexed.
"""
# Create a course with a page in both english and french
organization = OrganizationFactory(should_publish=True)
category = CategoryFactory(should_publish=True)
course = CourseFactory(
fill_organizations=[organization],
fill_categories=[category],
should_publish=True,
)
CourseRunFactory(page_parent=course.extended_object, should_publish=True)
course_document = list(
CoursesIndexer.get_es_documents(index="some_index", action="some_action")
)[0]
self.assertEqual(
course_document["organizations"],
[
next(
OrganizationsIndexer.get_es_documents(
index="some_index", action="some_action"
)
)["_id"]
],
)
self.assertEqual(
course_document["categories"],
[
next(
CategoriesIndexer.get_es_documents(