Skip to content

Commit 2e999fd

Browse files
committed
Add str unit tests
1 parent 2a6a128 commit 2e999fd

File tree

1 file changed

+156
-3
lines changed

1 file changed

+156
-3
lines changed

tests/atlas_search_/test_search.py

Lines changed: 156 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from .models import Article, Location, Writer
3232

3333

34-
def wait_until_index_ready(collection, index_name, timeout: float = 30, interval: float = 0.5):
34+
def wait_until_index_ready(collection, index_name, timeout: float = 5, interval: float = 0.5):
3535
start = monotonic()
3636
while monotonic() - start < timeout:
3737
indexes = list(collection.list_search_indexes())
@@ -42,7 +42,7 @@ def wait_until_index_ready(collection, index_name, timeout: float = 30, interval
4242
raise TimeoutError(f"Index {index_name} not ready after {timeout} seconds")
4343

4444

45-
def _delayed_assertion(timeout: float = 120, interval: float = 0.5):
45+
def _delayed_assertion(timeout: float = 4, interval: float = 0.5):
4646
def decorator(assert_func):
4747
@wraps(assert_func)
4848
def wrapper(self, fetch, *args, **kwargs):
@@ -72,6 +72,14 @@ def wrapper(self, fetch, *args, **kwargs):
7272
class SearchUtilsMixin(TransactionTestCase):
7373
available_apps = None
7474

75+
"""
76+
These assertions include a small delay to account for MongoDB Atlas Search's
77+
eventual consistency and indexing latency. Data inserted into MongoDB is not
78+
immediately available for $search queries because Atlas Search indexes are
79+
updated asynchronously via change streams. While this is usually fast, delays
80+
can occur due to replication lag, system load, index complexity, or a high
81+
number of search indexes.
82+
"""
7583
assertCountEqual = _delayed_assertion(timeout=2)(TransactionTestCase.assertCountEqual)
7684
assertListEqual = _delayed_assertion(timeout=2)(TransactionTestCase.assertListEqual)
7785
assertQuerySetEqual = _delayed_assertion(timeout=2)(TransactionTestCase.assertQuerySetEqual)
@@ -153,6 +161,11 @@ def test_function_score(self):
153161
scored = qs.first()
154162
self.assertAlmostEqual(scored.score, 1.0, places=2)
155163

164+
def test_str_returns_expected_format(self):
165+
score = SearchScoreOption({"constant": {"value": 10}})
166+
se = SearchEquals(path="headline", value="cross", score=score)
167+
self.assertEqual(str(se), f"<SearchEquals(path='headline', value='cross', score={score})>")
168+
156169

157170
class SearchAutocompleteTests(SearchUtilsMixin):
158171
@classmethod
@@ -232,6 +245,15 @@ def test_constant_score(self):
232245
scored = qs.first()
233246
self.assertAlmostEqual(scored.score, 10.0, places=2)
234247

248+
def test_str_returns_expected_format(self):
249+
score = SearchScoreOption({"constant": {"value": 10}})
250+
se = SearchAutocomplete(path="writer__name", query="Joselina", score=score)
251+
self.assertEqual(
252+
str(se),
253+
"<SearchAutocomplete(path='writer__name', query='Joselina', fuzzy=None,"
254+
f" token_order=None, score={score})>",
255+
)
256+
235257

236258
class SearchExistsTests(SearchUtilsMixin):
237259
@classmethod
@@ -257,6 +279,11 @@ def test_constant_score(self):
257279
scored = qs.first()
258280
self.assertAlmostEqual(scored.score, 10.0, places=2)
259281

282+
def test_str_returns_expected_format(self):
283+
score = SearchScoreOption({"constant": {"value": 10}})
284+
se = SearchExists(path="body", score=score)
285+
self.assertEqual(str(se), f"<SearchExists(path='body', score={score})>")
286+
260287

261288
class SearchInTests(SearchUtilsMixin):
262289
@classmethod
@@ -285,6 +312,13 @@ def test_constant_score(self):
285312
scored = qs.first()
286313
self.assertAlmostEqual(scored.score, 10.0, places=2)
287314

315+
def test_str_returns_expected_format(self):
316+
score = SearchScoreOption({"constant": {"value": 10}})
317+
se = SearchIn(path="headline", value=["cross", "river"], score=score)
318+
self.assertEqual(
319+
str(se), f"<SearchIn(path='headline', value=('cross', 'river'), score={score})>"
320+
)
321+
288322

289323
class SearchPhraseTests(SearchUtilsMixin):
290324
@classmethod
@@ -315,6 +349,15 @@ def test_constant_score(self):
315349
scored = qs.first()
316350
self.assertAlmostEqual(scored.score, 10.0, places=2)
317351

352+
def test_str_returns_expected_format(self):
353+
score = SearchScoreOption({"constant": {"value": 10}})
354+
se = SearchPhrase(path="body", query="quick brown", score=score)
355+
self.assertEqual(
356+
str(se),
357+
"<SearchPhrase(path='body', query='quick brown', slop=None, "
358+
f"synonyms=None, score={score})>",
359+
)
360+
318361

319362
class SearchRangeTests(SearchUtilsMixin):
320363
@classmethod
@@ -343,6 +386,14 @@ def test_constant_score(self):
343386
scored = qs.first()
344387
self.assertAlmostEqual(scored.score, 10.0, places=2)
345388

389+
def test_str_returns_expected_format(self):
390+
score = SearchScoreOption({"constant": {"value": 10}})
391+
se = SearchRange(path="number", gte=10, lt=30, score=score)
392+
self.assertEqual(
393+
str(se),
394+
f"<SearchRange(path='number', lt=30, lte=None, gt=None, gte=10, score={score})>",
395+
)
396+
346397

347398
class SearchRegexTests(SearchUtilsMixin):
348399
@classmethod
@@ -380,6 +431,15 @@ def test_constant_score(self):
380431
scored = qs.first()
381432
self.assertAlmostEqual(scored.score, 10.0, places=2)
382433

434+
def test_str_returns_expected_format(self):
435+
score = SearchScoreOption({"constant": {"value": 10}})
436+
se = SearchRegex(path="headline", query="hello.*", allow_analyzed_field=True, score=score)
437+
self.assertEqual(
438+
str(se),
439+
"<SearchRegex(path='headline', query='hello.*', "
440+
f"allow_analyzed_field=True, score={score})>",
441+
)
442+
383443

384444
class SearchTextTests(SearchUtilsMixin):
385445
@classmethod
@@ -428,6 +488,21 @@ def test_constant_score(self):
428488
scored = qs.first()
429489
self.assertAlmostEqual(scored.score, 10.0, places=2)
430490

491+
def test_str_returns_expected_format(self):
492+
score = SearchScoreOption({"constant": {"value": 10}})
493+
se = SearchText(
494+
path="body",
495+
query="lazzy",
496+
fuzzy={"maxEdits": 2},
497+
match_criteria="all",
498+
score=score,
499+
)
500+
self.assertEqual(
501+
str(se),
502+
"<SearchText(path='body', query='lazzy', fuzzy=(('maxEdits', 2),), "
503+
f"match_criteria='all', synonyms=None, score={score})>",
504+
)
505+
431506

432507
class SearchWildcardTests(SearchUtilsMixin):
433508
@classmethod
@@ -461,6 +536,15 @@ def test_constant_score(self):
461536
scored = qs.first()
462537
self.assertAlmostEqual(scored.score, 10.0, places=2)
463538

539+
def test_str_returns_expected_format(self):
540+
score = SearchScoreOption({"constant": {"value": 10}})
541+
se = SearchWildcard(path="headline", query="dark-*", score=score)
542+
self.assertEqual(
543+
str(se),
544+
"<SearchWildcard(path='headline', query='dark-*', "
545+
f"allow_analyzed_field=None, score={score})>",
546+
)
547+
464548

465549
class SearchGeoShapeTests(SearchUtilsMixin):
466550
@classmethod
@@ -513,6 +597,20 @@ def test_constant_score(self):
513597
scored = qs.first()
514598
self.assertAlmostEqual(scored.score, 10.0, places=2)
515599

600+
def test_str_returns_expected_format(self):
601+
score = SearchScoreOption({"constant": {"value": 10}})
602+
polygon = {
603+
"type": "Polygon",
604+
"coordinates": [[[30, 0], [50, 0], [50, 10], [30, 10], [30, 0]]],
605+
}
606+
se = SearchGeoShape(path="location", relation="within", geometry=polygon, score=score)
607+
self.assertEqual(
608+
str(se),
609+
"<SearchGeoShape(path='location', relation='within', geometry=(('type', 'Polygon'), "
610+
"('coordinates', (((30, 0), (50, 0), (50, 10), (30, 10), (30, 0)),))), "
611+
f"score={score})>",
612+
)
613+
516614

517615
class SearchGeoWithinTests(SearchUtilsMixin):
518616
@classmethod
@@ -567,8 +665,29 @@ def test_constant_score(self):
567665
scored = qs.first()
568666
self.assertAlmostEqual(scored.score, 10.0, places=2)
569667

668+
def test_str_returns_expected_format(self):
669+
score = SearchScoreOption({"constant": {"value": 10}})
670+
polygon = {
671+
"type": "Polygon",
672+
"coordinates": [[[30, 0], [50, 0], [50, 10], [30, 10], [30, 0]]],
673+
}
674+
se = SearchGeoWithin(
675+
path="location",
676+
kind="geometry",
677+
geometry=polygon,
678+
score=score,
679+
)
680+
self.assertEqual(
681+
str(se),
682+
"<SearchGeoWithin(path='location', kind='geometry', geometry=(('type', 'Polygon'), "
683+
"('coordinates', (((30, 0), (50, 0), (50, 10), (30, 10), (30, 0)),))), "
684+
f"score={score})>",
685+
)
686+
570687

571-
@unittest.expectedFailure
688+
@unittest.expectedFailure(
689+
"Cannot find a match for the provided reference documents in Atlas Search"
690+
)
572691
class SearchMoreLikeThisTests(SearchUtilsMixin):
573692
@classmethod
574693
def setUpClass(cls):
@@ -770,6 +889,26 @@ def test_search_and_filter(self):
770889
qs = Article.objects.filter(headline__search="space exploration", number__gt=2)
771890
self.assertCountEqual(qs.all, [self.icy_moons])
772891

892+
def test_str_returns_expected_format(self):
893+
must_expr = SearchEquals(path="headline", value="space exploration")
894+
must_not_expr = SearchPhrase(path="body", query="icy moons")
895+
should_expr = SearchPhrase(path="body", query="exoplanets")
896+
897+
se = CompoundExpression(
898+
must=[must_expr or should_expr],
899+
must_not=[must_not_expr],
900+
should=[should_expr],
901+
minimum_should_match=1,
902+
)
903+
self.assertEqual(
904+
str(se),
905+
"<CompoundExpression(must=(<SearchEquals(path='headline', value='space exploration', "
906+
"score=None)>,), must_not=(<SearchPhrase(path='body', query='icy moons', slop=None, "
907+
"synonyms=None, score=None)>,), should=(<SearchPhrase(path='body', "
908+
"query='exoplanets', slop=None, synonyms=None, score=None)>,), "
909+
"filter=None, score=None, minimum_should_match=1)>",
910+
)
911+
773912

774913
class SearchVectorTests(SearchUtilsMixin):
775914
@classmethod
@@ -816,3 +955,17 @@ def test_vector_search(self):
816955
)
817956
qs = Article.objects.annotate(score=expr).order_by("-score")
818957
self.assertCountEqual(qs.all, [self.mars, self.cooking])
958+
959+
def test_str_returns_expected_format(self):
960+
vector_query = [0.1, 0.2, 0.3]
961+
se = SearchVector(
962+
path="plot_embedding",
963+
query_vector=vector_query,
964+
num_candidates=5,
965+
limit=2,
966+
)
967+
self.assertEqual(
968+
str(se),
969+
"<SearchVector(path='plot_embedding', query_vector=(0.1, 0.2, 0.3), limit=2, "
970+
"num_candidates=5, exact=None, filter=None)>",
971+
)

0 commit comments

Comments
 (0)