Skip to content

Commit

Permalink
wip: tests
Browse files Browse the repository at this point in the history
  • Loading branch information
aaxelb committed Aug 15, 2024
1 parent 9dedd01 commit 3b2365e
Show file tree
Hide file tree
Showing 17 changed files with 142 additions and 125 deletions.
4 changes: 2 additions & 2 deletions share/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@


def check_all_index_strategies_current(app_configs, **kwargs):
from share.search.index_strategy import all_index_strategies
from share.search import index_strategy
from share.search.exceptions import IndexStrategyError
errors = []
for _index_strategy in all_index_strategies().values():
for _index_strategy in index_strategy.all_index_strategies().values():
try:
_index_strategy.assert_strategy_is_current()
except IndexStrategyError as exception:
Expand Down
3 changes: 1 addition & 2 deletions share/search/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from share.search.messages import MessageType, MessagesChunk
from share.search.index_strategy import IndexStrategy
from share.search.index_messenger import IndexMessenger


__all__ = ('IndexStrategy', 'IndexMessenger', 'MessageType', 'MessagesChunk',)
__all__ = ('IndexMessenger', 'MessageType', 'MessagesChunk',)
12 changes: 8 additions & 4 deletions share/search/index_strategy/trovesearch_flattery.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ def build(self) -> dict:
'source_record_identifier': self.indexcard.source_record_suid.identifier,
'source_config_label': self.indexcard.source_record_suid.source_config.label,
},
'focus_iri': self._exact_and_suffuniq_iris(self.focus_iri, self._fullwalk),
'focus_iri': self._exact_and_suffuniq_iris([self.focus_iri], self._fullwalk),
'propertypaths_present': self._propertypaths_present(self._fullwalk),
'iri_by_propertypath': self._iris_by_propertypath(self._fullwalk),
'iri_by_depth': self._iris_by_depth(self._fullwalk),
Expand Down Expand Up @@ -340,7 +340,7 @@ def _texts_by_propertypath(self, walk: _GraphWalk):

def _texts_by_depth(self, walk: _GraphWalk):
_by_depth: dict[int, set[str]] = defaultdict(set)
for (_path, _language_iris), _value_set in walk.text_values.items():
for _path, _value_set in walk.text_values.items():
_by_depth[len(_path)].update(_value_set)
return {
_depth_field_name(_depth): list(_value_set)
Expand All @@ -349,7 +349,10 @@ def _texts_by_depth(self, walk: _GraphWalk):

def _dates_by_propertypath(self, walk: _GraphWalk):
return {
propertypath_as_field_name(_path): list(_value_set)
propertypath_as_field_name(_path): [
_date.isoformat()
for _date in _value_set
]
for _path, _value_set in walk.date_values.items()
}

Expand Down Expand Up @@ -592,6 +595,7 @@ def _is_date_valuesearch(self) -> bool:
)

def _can_use_nonnested_aggs(self):
raise RuntimeError('remove this')
return (
len(self.params.valuesearch_propertypath_set) == 1
and not self.params.valuesearch_textsegment_set
Expand Down Expand Up @@ -1118,7 +1122,7 @@ def walk_twoples(
else:
_iter_twoples = (
(_pred, _obj)
for _pred, _obj_set in twoples.items
for _pred, _obj_set in twoples.items()
if _pred not in SKIPPABLE_PROPERTIES
for _obj in _obj_set
)
Expand Down
2 changes: 1 addition & 1 deletion share/search/index_strategy/trovesearch_nesterly.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def _nested_iri_usages(self) -> list:
def _iri_usage_sourcedoc(self, iri: str, paths: set[flattery.Propertypath]) -> dict | None:
_shortwalk = self._fullwalk.shortwalk(iri)
return {
'iri': self._exact_and_suffuniq_iris(iri, _shortwalk),
'iri': self._exact_and_suffuniq_iris([iri], _shortwalk),
'propertypath_from_focus': list(map(flattery.propertypath_as_keyword, paths)),
'depth_from_focus': list(map(len, paths)),
'iri_by_propertypath': self._iris_by_propertypath(_shortwalk),
Expand Down
16 changes: 16 additions & 0 deletions tests/_testutil.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import contextlib
import typing
from unittest import mock

if typing.TYPE_CHECKING:
from share.search import index_strategy


def patch_feature_flag(*flag_names, up=True):
from share.models.feature_flag import FeatureFlag
Expand All @@ -10,3 +15,14 @@ def _patched_isup(flag_name):
return up
return _old_isup(flag_name)
return mock.patch.object(FeatureFlag.objects, 'flag_is_up', new=_patched_isup)


@contextlib.contextmanager
def patch_index_strategies(strategies: dict[str, index_strategy.IndexStrategy]):
index_strategy.all_index_strategies.cache_clear()
with mock.patch(
'share.bin.search.index_strategy._iter_all_index_strategies',
return_value=strategies.items(),
):
yield
index_strategy.all_index_strategies.cache_clear()
15 changes: 8 additions & 7 deletions tests/share/bin/test_sharectl.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import pytest

from share.bin.util import execute_cmd
from share.test._testutil import patch_index_strategies
import share.version


Expand Down Expand Up @@ -49,14 +50,14 @@ def _get_specific_index(indexname):
mock_specific_index.pls_delete.assert_called_once_with()

def test_setup_initial(self, settings):
expected_indexes = ['baz', 'bar', 'foo']
mock_index_strategys = [
mock.Mock()
for _ in expected_indexes
]
with mock.patch('share.bin.search.index_strategy.all_index_strategies', return_value=mock_index_strategys):
_expected_indexes = ['baz', 'bar', 'foo']
_mock_index_strategys = {
_name: mock.Mock()
for _name in _expected_indexes
}
with patch_index_strategies(_mock_index_strategys):
run_sharectl('search', 'setup', '--initial')
for mock_index_strategy in mock_index_strategys:
for mock_index_strategy in _mock_index_strategys.values():
mock_specific_index = mock_index_strategy.for_current_index.return_value
assert mock_specific_index.pls_setup.mock_calls == [mock.call(skip_backfill=True)]

Expand Down
38 changes: 6 additions & 32 deletions tests/share/search/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,9 @@


@pytest.fixture
def fake_elastic_strategies(settings):
settings.ELASTICSEARCH = {
**settings.ELASTICSEARCH,
'INDEX_STRATEGIES': {
'my_es5_strategy': {
'CLUSTER_SETTINGS': {'URL': 'blah'},
'INDEX_STRATEGY_CLASS': 'share.search.index_strategy.sharev2_elastic5.Sharev2Elastic5IndexStrategy',
},
'my_es8_strategy': {
'CLUSTER_SETTINGS': {'URL': 'bleh'},
'INDEX_STRATEGY_CLASS': 'share.search.index_strategy.sharev2_elastic8.Sharev2Elastic8IndexStrategy',
},
'another_es8_strategy': {
'CLUSTER_SETTINGS': {'URL': 'bluh'},
'INDEX_STRATEGY_CLASS': 'share.search.index_strategy.sharev2_elastic8.Sharev2Elastic8IndexStrategy',
},
},
}
return tuple(settings.ELASTICSEARCH['INDEX_STRATEGIES'].keys())


@pytest.fixture
def mock_elastic_clients(fake_elastic_strategies):
with mock.patch('share.search.index_strategy.sharev2_elastic5.elasticsearch5') as es5_mockpackage:
with mock.patch('share.search.index_strategy.elastic8.elasticsearch8') as es8_mockpackage:
es5_mockclient = es5_mockpackage.Elasticsearch.return_value
es8_mockclient = es8_mockpackage.Elasticsearch.return_value
yield {
'my_es5_strategy': es5_mockclient,
'my_es8_strategy': es8_mockclient,
'another_es8_strategy': es8_mockclient,
}
def mock_elastic_clients(settings):
settings.ELASTICSEARCH5_URL = 'fake://bleh'
settings.ELASTICSEARCH8_URL = 'fake://bluh'
with mock.patch('share.search.index_strategy.sharev2_elastic5.elasticsearch5'):
with mock.patch('share.search.index_strategy.elastic8.elasticsearch8'):
yield
53 changes: 46 additions & 7 deletions tests/share/search/index_strategy/_common_trovesearch_tests.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from typing import Iterable, Iterator
from datetime import date
import itertools
from urllib.parse import urlencode

from primitive_metadata import primitive_rdf as rdf

from tests import factories
from share.search import messages
from trove import models as trove_db
from trove.trovesearch.search_params import CardsearchParams
from trove.trovesearch.search_params import CardsearchParams, ValuesearchParams
from trove.vocab.namespaces import RDFS, TROVE, RDF, DCTERMS, OWL, FOAF
from ._with_real_services import RealElasticTestCase

Expand All @@ -16,10 +17,6 @@


class CommonTrovesearchTests(RealElasticTestCase):
# required attrs on subclasses (from RealElasticTestCase):
strategy_name_for_real: str
strategy_name_for_test: str

_indexcard_focus_by_uuid: dict[str, str]

def setUp(self):
Expand Down Expand Up @@ -56,8 +53,9 @@ def test_for_smoke_with_daemon(self):

def test_cardsearch(self):
self._fill_test_data_for_querying()
for _queryparams, _expected_result_iris in self._cardsearch_cases():
for _queryparams, _expected_result_iris in self.cardsearch_cases():
_cardsearch_params = CardsearchParams.from_querystring(urlencode(_queryparams))
assert isinstance(_cardsearch_params, CardsearchParams)
_cardsearch_response = self.current_index.pls_handle_cardsearch(_cardsearch_params)
# assumes all results fit on one page
_actual_result_iris = {
Expand All @@ -66,6 +64,23 @@ def test_cardsearch(self):
}
self.assertEqual(_expected_result_iris, _actual_result_iris)

def test_valuesearch(self):
self._fill_test_data_for_querying()
_valuesearch_cases = itertools.chain(
self.valuesearch_simple_cases(),
self.valuesearch_complex_cases(),
)
for _queryparams, _expected_values in _valuesearch_cases:
_valuesearch_params = ValuesearchParams.from_querystring(urlencode(_queryparams))
assert isinstance(_valuesearch_params, ValuesearchParams)
_valuesearch_response = self.current_index.pls_handle_valuesearch(_valuesearch_params)
# assumes all results fit on one page
_actual_values = {
_result.value_iri or _result.value_value
for _result in _valuesearch_response.search_result_page
}
self.assertEqual(_expected_values, _actual_values)

def _fill_test_data_for_querying(self):
self._index_indexcards([
self._create_indexcard(BLARG.a, {
Expand Down Expand Up @@ -129,7 +144,7 @@ def _fill_test_data_for_querying(self):
}),
])

def _cardsearch_cases(self) -> Iterator[tuple[dict[str, str], set[str]]]:
def cardsearch_cases(self) -> Iterator[tuple[dict[str, str], set[str]]]:
# using data from _fill_test_data_for_querying
yield (
{'cardSearchFilter[creator]': BLARG.someone},
Expand Down Expand Up @@ -285,6 +300,30 @@ def _cardsearch_cases(self) -> Iterator[tuple[dict[str, str], set[str]]]:
{BLARG.b},
)

def valuesearch_simple_cases(self) -> Iterator[tuple[dict[str, str], set[str]]]:
yield (
{'valueSearchPropertyPath': 'references'},
{BLARG.b, BLARG.c},
)
# TODO: more

def valuesearch_complex_cases(self) -> Iterator[tuple[dict[str, str], set[str]]]:
yield (
{
'valueSearchPropertyPath': 'references',
'valueSearchFilter[resourceType]': BLARG.Thing,
},
{BLARG.b, BLARG.c},
)
yield (
{
'valueSearchPropertyPath': 'references',
'valueSearchText': 'bbbb',
},
{BLARG.b},
)
# TODO: more

def _index_indexcards(self, indexcards: Iterable[trove_db.Indexcard]):
_messages_chunk = messages.MessagesChunk(
messages.MessageType.UPDATE_INDEXCARD,
Expand Down
43 changes: 4 additions & 39 deletions tests/share/search/index_strategy/_with_real_services.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import contextlib
import unittest
from unittest import mock

from django.test import override_settings, TransactionTestCase
from django.conf import settings
from django.test import TransactionTestCase
from django.db import connections

from project.celery import app as celery_app
Expand All @@ -19,17 +17,12 @@ class RealElasticTestCase(TransactionTestCase):
serialized_rollback = True # for TransactionTestCase; restore db after

# required for subclasses
strategy_name_for_real: str
strategy_name_for_test: str

@classmethod
def setUpClass(cls):
cls.__original_es_settings = settings.ELASTICSEARCH
def get_index_strategy(self) -> index_strategy.IndexStrategy:
raise NotImplementedError(f'{self.__class__} must implement `get_index_strategy`')

def setUp(self):
super().setUp()
self.enterContext(mock.patch('share.models.core._setup_user_token_and_groups'))
self.enterContext(self._settings_for_test())
self.index_strategy = self.get_index_strategy()
self.index_messenger = IndexMessenger(
celery_app=celery_app,
Expand All @@ -55,43 +48,15 @@ def enterContext(self, context_manager):
self.addCleanup(lambda: context_manager.__exit__(None, None, None))
return result

def get_index_strategy(self):
return index_strategy.get_index_strategy(self.strategy_name_for_test)

@contextlib.contextmanager
def _daemon_up(self):
_daemon_control = IndexerDaemonControl(
celery_app,
daemonthread_context=self._settings_for_test, # will be called in daemonthread
)
_daemon_control = IndexerDaemonControl(celery_app)
_daemon_control.start_daemonthreads_for_strategy(self.get_index_strategy())
try:
yield _daemon_control
finally:
_daemon_control.stop_daemonthreads(wait=True)

@contextlib.contextmanager
def _settings_for_test(self):
try:
_real_strategy_settings = (
self.__original_es_settings
['INDEX_STRATEGIES']
[self.strategy_name_for_real]
)
except KeyError:
raise unittest.SkipTest(
f'index strategy "{self.strategy_name_for_real}" not configured in'
" ELASTICSEARCH['INDEX_STRATEGIES'] (perhaps missing env)"
)
_new_es_settings = {
**self.__original_es_settings,
'INDEX_STRATEGIES': { # wipe out all configured strategies
self.strategy_name_for_test: _real_strategy_settings,
}
}
with override_settings(ELASTICSEARCH=_new_es_settings):
yield

# for test methods on subclasses to call:
def _assert_happypath_without_daemon(self, messages_chunk, expected_doc_count):
_responses = list(self.index_strategy.pls_handle_messages_chunk(messages_chunk))
Expand Down
11 changes: 6 additions & 5 deletions tests/share/search/index_strategy/test_sharev2_elastic5.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import json
import unittest

from django.conf import settings

from tests import factories
from share.search import messages
from share.search.index_strategy.sharev2_elastic5 import Sharev2Elastic5IndexStrategy
from share.util import IDObfuscator
from ._with_real_services import RealElasticTestCase


@unittest.skipUnless(settings.ELASTICSEARCH5_URL, 'missing ELASTICSEARCH5_URL setting')
class TestSharev2Elastic5(RealElasticTestCase):
# for RealElasticTestCase
strategy_name_for_real = 'sharev2_elastic5'
strategy_name_for_test = 'test_sharev2_elastic5'

# override method from RealElasticTestCase
def get_index_strategy(self):
index_strategy = super().get_index_strategy()
index_strategy = Sharev2Elastic5IndexStrategy('test_sharev2_elastic5')
if not index_strategy.STATIC_INDEXNAME.startswith('test_'):
index_strategy.STATIC_INDEXNAME = f'test_{index_strategy.STATIC_INDEXNAME}'
return index_strategy
Expand Down
5 changes: 3 additions & 2 deletions tests/share/search/index_strategy/test_sharev2_elastic8.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

from tests import factories
from share.search import messages
from share.search.index_strategy.sharev2_elastic8 import Sharev2Elastic8IndexStrategy
from share.util import IDObfuscator
from ._with_real_services import RealElasticTestCase


class TestSharev2Elastic8(RealElasticTestCase):
# for RealElasticTestCase
strategy_name_for_real = 'sharev2_elastic8'
strategy_name_for_test = 'test_sharev2_elastic8'
def get_index_strategy(self):
return Sharev2Elastic8IndexStrategy('test_sharev2_elastic8')

def setUp(self):
super().setUp()
Expand Down
Loading

0 comments on commit 3b2365e

Please sign in to comment.