Commit e0cd1654 by Clinton Blackburn Committed by GitHub

Ignoring empty query string parameters sent to search endpoint (#352)

These empty parameters cause errors, resulting in failed responses.

ECOM-5685
parent d37aba7e
...@@ -59,14 +59,34 @@ class FacetQueryBuilderWithQueries(FacetQueryBuilder): ...@@ -59,14 +59,34 @@ class FacetQueryBuilderWithQueries(FacetQueryBuilder):
return query return query
class HaystackFacetFilterWithQueries(HaystackFacetFilter): class HaystackRequestFilterMixin:
@staticmethod
def get_request_filters(request):
filters = HaystackFacetFilter.get_request_filters(request)
# Remove items with empty values.
#
# NOTE 1: We copy the keys list to avoid a RuntimeError that occurs when modifying a dict while iterating.
#
# NOTE 2: We filter in this fashion, as opposed to dictionary comprehension, due to the fact that filters
# is a `QueryDict` object, not a `dict`. Dictionary comprehension will not preserve the values of
# `QueryDict.getlist()`. Since we support multiple values for a single parameter, dictionary comprehension is a
# dealbreaker (and production breaker).
for key in list(filters.keys()):
if not filters[key]:
del filters[key]
return filters
class HaystackFacetFilterWithQueries(HaystackRequestFilterMixin, HaystackFacetFilter):
query_builder_class = FacetQueryBuilderWithQueries query_builder_class = FacetQueryBuilderWithQueries
class HaystackFilter(DefaultHaystackFilter): class HaystackFilter(HaystackRequestFilterMixin, DefaultHaystackFilter):
@staticmethod @staticmethod
def get_request_filters(request): def get_request_filters(request):
filters = HaystackFacetFilter.get_request_filters(request) filters = HaystackRequestFilterMixin.get_request_filters(request)
# Return data for the default partner, if no partner is requested # Return data for the default partner, if no partner is requested
if not any(field in filters for field in ('partner', 'partner_exact')): if not any(field in filters for field in ('partner', 'partner_exact')):
......
import ddt
from django.test import TestCase
from rest_framework.test import APIRequestFactory
from rest_framework.views import APIView
from course_discovery.apps.api.filters import HaystackRequestFilterMixin
@ddt.ddt
class HaystackRequestFilterMixinTests(TestCase):
def test_get_request_filters(self):
""" Verify the method removes query parameters with empty values """
request = APIRequestFactory().get('/?q=')
request = APIView().initialize_request(request)
filters = HaystackRequestFilterMixin.get_request_filters(request)
self.assertDictEqual(filters, {})
def test_get_request_filters_with_list(self):
""" Verify the method does not affect list values. """
request = APIRequestFactory().get('/?q=&content_type=courserun&content_type=program')
request = APIView().initialize_request(request)
filters = HaystackRequestFilterMixin.get_request_filters(request)
self.assertNotIn('q', filters)
self.assertEqual(filters.getlist('content_type'), ['courserun', 'program'])
def test_get_request_filters_with_falsey_values(self):
""" Verify the method does not strip valid falsey values. """
request = APIRequestFactory().get('/?q=&test=0')
request = APIView().initialize_request(request)
filters = HaystackRequestFilterMixin.get_request_filters(request)
self.assertNotIn('q', filters)
self.assertEqual(filters.get('test'), '0')
...@@ -236,3 +236,15 @@ class AggregateSearchViewSet(DefaultPartnerMixin, SerializationMixin, LoginMixin ...@@ -236,3 +236,15 @@ class AggregateSearchViewSet(DefaultPartnerMixin, SerializationMixin, LoginMixin
response_data = json.loads(response.content.decode('utf-8')) response_data = json.loads(response.content.decode('utf-8'))
self.assertListEqual(response_data['objects']['results'], self.assertListEqual(response_data['objects']['results'],
[self.serialize_course_run(other_course_run), self.serialize_program(other_program)]) [self.serialize_course_run(other_course_run), self.serialize_program(other_program)])
def test_empty_query(self):
""" Verify, when the query (q) parameter is empty, the endpoint behaves as if the parameter
was not provided. """
course_run = CourseRunFactory(course__partner=self.partner, status=CourseRunStatus.Published)
program = ProgramFactory(partner=self.partner, status=ProgramStatus.Active)
response = self.get_search_response({'q': '', 'content_type': ['courserun', 'program']})
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content.decode('utf-8'))
self.assertListEqual(response_data['objects']['results'],
[self.serialize_course_run(course_run), self.serialize_program(program)])
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment