Skip to content

Commit 4158e57

Browse files
committed
Add tests for full text SearchFilter
1 parent 775fcca commit 4158e57

1 file changed

Lines changed: 107 additions & 0 deletions

File tree

tests/test_filters.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,109 @@ class SearchListView(generics.ListAPIView):
291291
]
292292

293293

294+
@pytest.mark.requires_postgres
295+
class SearchFilterFullTextTests(TestCase):
296+
@classmethod
297+
def setUpTestData(cls):
298+
SearchFilterModel.objects.create(title='The quick brown fox', text='jumps over the lazy dog')
299+
SearchFilterModel.objects.create(title='The slow brown turtle', text='crawls under the fence')
300+
SearchFilterModel.objects.create(title='A bright sunny day', text='in the park with friends')
301+
302+
def test_full_text_search_single_term(self):
303+
class SearchListView(generics.ListAPIView):
304+
queryset = SearchFilterModel.objects.all()
305+
serializer_class = SearchFilterSerializer
306+
filter_backends = (filters.SearchFilter,)
307+
search_fields = ('@title',)
308+
309+
view = SearchListView.as_view()
310+
request = factory.get('/', {'search': 'fox'})
311+
response = view(request)
312+
assert len(response.data) == 1
313+
assert response.data[0]['title'] == 'The quick brown fox'
314+
315+
def test_full_text_search_multiple_results(self):
316+
class SearchListView(generics.ListAPIView):
317+
queryset = SearchFilterModel.objects.all()
318+
serializer_class = SearchFilterSerializer
319+
filter_backends = (filters.SearchFilter,)
320+
search_fields = ('@title',)
321+
322+
view = SearchListView.as_view()
323+
request = factory.get('/', {'search': 'brown'})
324+
response = view(request)
325+
assert len(response.data) == 2
326+
titles = {item['title'] for item in response.data}
327+
assert titles == {'The quick brown fox', 'The slow brown turtle'}
328+
329+
def test_full_text_search_no_results(self):
330+
class SearchListView(generics.ListAPIView):
331+
queryset = SearchFilterModel.objects.all()
332+
serializer_class = SearchFilterSerializer
333+
filter_backends = (filters.SearchFilter,)
334+
search_fields = ('@title',)
335+
336+
view = SearchListView.as_view()
337+
request = factory.get('/', {'search': 'elephant'})
338+
response = view(request)
339+
assert len(response.data) == 0
340+
341+
def test_full_text_search_multiple_fields(self):
342+
class SearchListView(generics.ListAPIView):
343+
queryset = SearchFilterModel.objects.all()
344+
serializer_class = SearchFilterSerializer
345+
filter_backends = (filters.SearchFilter,)
346+
search_fields = ('@title', '@text')
347+
348+
view = SearchListView.as_view()
349+
request = factory.get('/', {'search': 'lazy'})
350+
response = view(request)
351+
assert len(response.data) == 1
352+
assert response.data[0]['title'] == 'The quick brown fox'
353+
354+
def test_full_text_search_stemming(self):
355+
"""Full text search should match stemmed words (e.g. 'jumping' matches 'jumps')."""
356+
class SearchListView(generics.ListAPIView):
357+
queryset = SearchFilterModel.objects.all()
358+
serializer_class = SearchFilterSerializer
359+
filter_backends = (filters.SearchFilter,)
360+
search_fields = ('@text',)
361+
362+
view = SearchListView.as_view()
363+
request = factory.get('/', {'search': 'jumping'})
364+
response = view(request)
365+
assert len(response.data) == 1
366+
assert response.data[0]['text'] == 'jumps over the lazy dog'
367+
368+
def test_full_text_search_multiple_terms(self):
369+
"""Each search term must match (AND semantics across terms)."""
370+
class SearchListView(generics.ListAPIView):
371+
queryset = SearchFilterModel.objects.all()
372+
serializer_class = SearchFilterSerializer
373+
filter_backends = (filters.SearchFilter,)
374+
search_fields = ('@title', '@text')
375+
376+
view = SearchListView.as_view()
377+
request = factory.get('/', {'search': 'brown lazy'})
378+
response = view(request)
379+
assert len(response.data) == 1
380+
assert response.data[0]['title'] == 'The quick brown fox'
381+
382+
def test_full_text_search_mixed_with_icontains(self):
383+
"""Full text search fields can be mixed with regular icontains fields."""
384+
class SearchListView(generics.ListAPIView):
385+
queryset = SearchFilterModel.objects.all()
386+
serializer_class = SearchFilterSerializer
387+
filter_backends = (filters.SearchFilter,)
388+
search_fields = ('@title', 'text')
389+
390+
view = SearchListView.as_view()
391+
request = factory.get('/', {'search': 'park'})
392+
response = view(request)
393+
assert len(response.data) == 1
394+
assert response.data[0]['title'] == 'A bright sunny day'
395+
396+
294397
class AttributeModel(models.Model):
295398
label = models.CharField(max_length=32)
296399

@@ -339,6 +442,10 @@ def test_custom_lookup_to_related_model(self):
339442
assert 'attribute__label__icontains' == filter_.construct_search('attribute__label', SearchFilterModelFk._meta)
340443
assert 'attribute__label__iendswith' == filter_.construct_search('attribute__label__iendswith', SearchFilterModelFk._meta)
341444

445+
def test_construct_search_with_at_prefix(self):
446+
filter_ = filters.SearchFilter()
447+
assert 'title__search' == filter_.construct_search('@title', SearchFilterModelFk._meta)
448+
342449

343450
class SearchFilterModelM2M(models.Model):
344451
title = models.CharField(max_length=20)

0 commit comments

Comments
 (0)