Skip to content

Commit 34dd3c3

Browse files
committed
INTPYTHON-506 - Add django-rest-framework tests
1 parent 6ffaa4e commit 34dd3c3

File tree

2 files changed

+276
-0
lines changed

2 files changed

+276
-0
lines changed

tests/rest_framework_/__init__.py

Whitespace-only changes.

tests/rest_framework_/test_filters.py

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
# Based on https://github.com/encode/django-rest-framework/blob/0e1c7d3613905a8f9db69abb82f883e22e967119/tests/test_filters.py
2+
3+
from collections import OrderedDict
4+
from importlib import reload as reload_module
5+
6+
7+
from django.db import models
8+
from django.db.models import CharField, Transform
9+
from django.test import TestCase
10+
from django.test.utils import override_settings
11+
12+
from rest_framework.exceptions import ValidationError
13+
from rest_framework import filters, generics, serializers
14+
from rest_framework.test import APIRequestFactory
15+
16+
factory = APIRequestFactory()
17+
18+
19+
class SearchFilterModel(models.Model):
20+
title = models.CharField(max_length=20)
21+
text = models.CharField(max_length=100)
22+
23+
24+
class SearchFilterSerializer(serializers.ModelSerializer):
25+
class Meta:
26+
model = SearchFilterModel
27+
fields = '__all__'
28+
29+
30+
class SearchFilterTests(TestCase):
31+
@classmethod
32+
def setUpTestData(cls):
33+
# Sequence of title/text is:
34+
#
35+
# z abc
36+
# zz bcd
37+
# zzz cde
38+
# ...
39+
searchfilters = OrderedDict()
40+
for idx in range(10):
41+
title = 'z' * (idx + 1)
42+
text = (
43+
chr(idx + ord('a')) +
44+
chr(idx + ord('b')) +
45+
chr(idx + ord('c'))
46+
)
47+
searchfilter = SearchFilterModel(title=title, text=text)
48+
searchfilter.save()
49+
searchfilters[idx] = searchfilter
50+
51+
52+
idx += 1
53+
searchfilter = SearchFilterModel(title='A title', text='The long text')
54+
searchfilter.save()
55+
searchfilters[idx] = searchfilter
56+
57+
idx += 1
58+
searchfilter = SearchFilterModel(title='The title', text='The "text')
59+
searchfilter.save()
60+
searchfilters[idx] = searchfilter
61+
62+
cls.searchfilters = searchfilters
63+
64+
def test_search(self):
65+
class SearchListView(generics.ListAPIView):
66+
queryset = SearchFilterModel.objects.all()
67+
serializer_class = SearchFilterSerializer
68+
filter_backends = (filters.SearchFilter,)
69+
search_fields = ('title', 'text')
70+
71+
view = SearchListView.as_view()
72+
request = factory.get('/', {'search': 'b'})
73+
response = view(request)
74+
assert response.data == [
75+
{'id': self.searchfilters.get(0).id, 'title': 'z', 'text': 'abc'},
76+
{'id': self.searchfilters.get(1).id, 'title': 'zz', 'text': 'bcd'}
77+
]
78+
79+
def test_search_returns_same_queryset_if_no_search_fields_or_terms_provided(self):
80+
class SearchListView(generics.ListAPIView):
81+
queryset = SearchFilterModel.objects.all()
82+
serializer_class = SearchFilterSerializer
83+
filter_backends = (filters.SearchFilter,)
84+
85+
view = SearchListView.as_view()
86+
request = factory.get('/')
87+
response = view(request)
88+
expected = SearchFilterSerializer(SearchFilterModel.objects.all(),
89+
many=True).data
90+
assert response.data == expected
91+
92+
def test_exact_search(self):
93+
class SearchListView(generics.ListAPIView):
94+
queryset = SearchFilterModel.objects.all()
95+
serializer_class = SearchFilterSerializer
96+
filter_backends = (filters.SearchFilter,)
97+
search_fields = ('=title', 'text')
98+
99+
view = SearchListView.as_view()
100+
request = factory.get('/', {'search': 'zzz'})
101+
response = view(request)
102+
assert response.data == [
103+
{'id': self.searchfilters.get(2).id, 'title': 'zzz', 'text': 'cde'}
104+
]
105+
106+
def test_startswith_search(self):
107+
class SearchListView(generics.ListAPIView):
108+
queryset = SearchFilterModel.objects.all()
109+
serializer_class = SearchFilterSerializer
110+
filter_backends = (filters.SearchFilter,)
111+
search_fields = ('title', '^text')
112+
113+
view = SearchListView.as_view()
114+
request = factory.get('/', {'search': 'b'})
115+
response = view(request)
116+
assert response.data == [
117+
{'id': self.searchfilters.get(1).id, 'title': 'zz', 'text': 'bcd'}
118+
]
119+
120+
def test_regexp_search(self):
121+
class SearchListView(generics.ListAPIView):
122+
queryset = SearchFilterModel.objects.all()
123+
serializer_class = SearchFilterSerializer
124+
filter_backends = (filters.SearchFilter,)
125+
search_fields = ('$title', '$text')
126+
127+
view = SearchListView.as_view()
128+
request = factory.get('/', {'search': 'z{2} ^b'})
129+
response = view(request)
130+
assert response.data == [
131+
{'id': self.searchfilters.get(1).id, 'title': 'zz', 'text': 'bcd'}
132+
]
133+
134+
def test_search_with_nonstandard_search_param(self):
135+
with override_settings(REST_FRAMEWORK={'SEARCH_PARAM': 'query'}):
136+
reload_module(filters)
137+
138+
class SearchListView(generics.ListAPIView):
139+
queryset = SearchFilterModel.objects.all()
140+
serializer_class = SearchFilterSerializer
141+
filter_backends = (filters.SearchFilter,)
142+
search_fields = ('title', 'text')
143+
144+
view = SearchListView.as_view()
145+
request = factory.get('/', {'query': 'b'})
146+
response = view(request)
147+
assert response.data == [
148+
{'id': self.searchfilters.get(0).id, 'title': 'z', 'text': 'abc'},
149+
{'id': self.searchfilters.get(1).id, 'title': 'zz', 'text': 'bcd'}
150+
]
151+
152+
reload_module(filters)
153+
154+
def test_search_with_filter_subclass(self):
155+
class CustomSearchFilter(filters.SearchFilter):
156+
# Filter that dynamically changes search fields
157+
def get_search_fields(self, view, request):
158+
if request.query_params.get('title_only'):
159+
return ('$title',)
160+
return super().get_search_fields(view, request)
161+
162+
class SearchListView(generics.ListAPIView):
163+
queryset = SearchFilterModel.objects.all()
164+
serializer_class = SearchFilterSerializer
165+
filter_backends = (CustomSearchFilter,)
166+
search_fields = ('$title', '$text')
167+
168+
view = SearchListView.as_view()
169+
request = factory.get('/', {'search': r'^\w{3}$'})
170+
response = view(request)
171+
assert len(response.data) == 10
172+
173+
request = factory.get('/', {'search': r'^\w{3}$', 'title_only': 'true'})
174+
response = view(request)
175+
assert response.data == [
176+
{'id': self.searchfilters.get(2).id, 'title': 'zzz', 'text': 'cde'}
177+
]
178+
179+
def test_search_field_with_null_characters(self):
180+
view = generics.GenericAPIView()
181+
request = factory.get('/?search=\0as%00d\x00f')
182+
request = view.initialize_request(request)
183+
184+
with self.assertRaises(ValidationError):
185+
filters.SearchFilter().get_search_terms(request)
186+
187+
def test_search_field_with_custom_lookup(self):
188+
class SearchListView(generics.ListAPIView):
189+
queryset = SearchFilterModel.objects.all()
190+
serializer_class = SearchFilterSerializer
191+
filter_backends = (filters.SearchFilter,)
192+
search_fields = ('text__iendswith',)
193+
view = SearchListView.as_view()
194+
request = factory.get('/', {'search': 'c'})
195+
response = view(request)
196+
assert response.data == [
197+
{'id': self.searchfilters.get(0).id, 'title': 'z', 'text': 'abc'},
198+
]
199+
200+
def test_search_field_with_additional_transforms(self):
201+
from django.test.utils import register_lookup
202+
203+
class SearchListView(generics.ListAPIView):
204+
queryset = SearchFilterModel.objects.all()
205+
serializer_class = SearchFilterSerializer
206+
filter_backends = (filters.SearchFilter,)
207+
search_fields = ('text__trim', )
208+
209+
view = SearchListView.as_view()
210+
211+
# an example custom transform, that trims `a` from the string.
212+
class TrimA(Transform):
213+
function = 'TRIM'
214+
lookup_name = 'trim'
215+
216+
def as_sql(self, compiler, connection):
217+
sql, params = compiler.compile(self.lhs)
218+
return "trim(%s, 'a')" % sql, params
219+
220+
with register_lookup(CharField, TrimA):
221+
# Search including `a`
222+
request = factory.get('/', {'search': 'abc'})
223+
224+
response = view(request)
225+
assert response.data == []
226+
227+
# Search excluding `a`
228+
request = factory.get('/', {'search': 'bc'})
229+
response = view(request)
230+
assert response.data == [
231+
{'id': 1, 'title': 'z', 'text': 'abc'},
232+
{'id': 2, 'title': 'zz', 'text': 'bcd'},
233+
]
234+
235+
def test_search_field_with_multiple_words(self):
236+
class SearchListView(generics.ListAPIView):
237+
queryset = SearchFilterModel.objects.all()
238+
serializer_class = SearchFilterSerializer
239+
filter_backends = (filters.SearchFilter,)
240+
search_fields = ('title', 'text')
241+
242+
search_query = 'foo bar,baz'
243+
view = SearchListView()
244+
request = factory.get('/', {'search': search_query})
245+
request = view.initialize_request(request)
246+
247+
rendered_search_field = filters.SearchFilter().to_html(
248+
request=request, queryset=view.queryset, view=view
249+
)
250+
assert search_query in rendered_search_field
251+
252+
def test_search_field_with_escapes(self):
253+
class SearchListView(generics.ListAPIView):
254+
queryset = SearchFilterModel.objects.all()
255+
serializer_class = SearchFilterSerializer
256+
filter_backends = (filters.SearchFilter,)
257+
search_fields = ('title', 'text',)
258+
view = SearchListView.as_view()
259+
request = factory.get('/', {'search': '"\\\"text"'})
260+
response = view(request)
261+
assert response.data == [
262+
{'id': self.searchfilters.get(11).id, 'title': 'The title', 'text': 'The "text'},
263+
]
264+
265+
def test_search_field_with_quotes(self):
266+
class SearchListView(generics.ListAPIView):
267+
queryset = SearchFilterModel.objects.all()
268+
serializer_class = SearchFilterSerializer
269+
filter_backends = (filters.SearchFilter,)
270+
search_fields = ('title', 'text',)
271+
view = SearchListView.as_view()
272+
request = factory.get('/', {'search': '"long text"'})
273+
response = view(request)
274+
assert response.data == [
275+
{'id': self.searchfilters.get(10).id, 'title': 'A title', 'text': 'The long text'},
276+
]

0 commit comments

Comments
 (0)