from datetime import date, datetime try: import ujson as json except ImportError: import json import pytest import falcon from falcon.errors import HTTPInvalidParam import falcon.testing as testing class Resource(testing.SimpleTestResource): @falcon.before(testing.capture_responder_args) @falcon.before(testing.set_resp_defaults) def on_put(self, req, resp, **kwargs): pass @falcon.before(testing.capture_responder_args) @falcon.before(testing.set_resp_defaults) def on_patch(self, req, resp, **kwargs): pass @falcon.before(testing.capture_responder_args) @falcon.before(testing.set_resp_defaults) def on_delete(self, req, resp, **kwargs): pass @falcon.before(testing.capture_responder_args) @falcon.before(testing.set_resp_defaults) def on_head(self, req, resp, **kwargs): pass @falcon.before(testing.capture_responder_args) @falcon.before(testing.set_resp_defaults) def on_options(self, req, resp, **kwargs): pass @pytest.fixture def resource(): return Resource() @pytest.fixture def client(): app = falcon.API() app.req_options.auto_parse_form_urlencoded = True return testing.TestClient(app) def simulate_request_get_query_params(client, path, query_string, **kwargs): return client.simulate_request(path=path, query_string=query_string, **kwargs) def simulate_request_post_query_params(client, path, query_string, **kwargs): headers = kwargs.setdefault('headers', {}) headers['Content-Type'] = 'application/x-www-form-urlencoded' if 'method' not in kwargs: kwargs['method'] = 'POST' return client.simulate_request(path=path, body=query_string, **kwargs) @pytest.mark.parametrize('simulate_request', [ simulate_request_get_query_params, simulate_request_post_query_params, ]) class TestQueryParams(object): def test_none(self, simulate_request, client, resource): query_string = '' client.app.add_route('/', resource) simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req store = {} assert req.get_param('marker') is None assert req.get_param('limit', store) is None assert 'limit' not in store assert req.get_param_as_int('limit') is None assert req.get_param_as_bool('limit') is None assert req.get_param_as_list('limit') is None def test_blank(self, simulate_request, client, resource): query_string = 'marker=' client.app.add_route('/', resource) simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req assert req.get_param('marker') is None store = {} assert req.get_param('marker', store=store) is None assert 'marker' not in store def test_simple(self, simulate_request, client, resource): query_string = 'marker=deadbeef&limit=25' client.app.add_route('/', resource) simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req store = {} assert req.get_param('marker', store=store) or 'nada' == 'deadbeef' assert req.get_param('limit', store=store) or '0' == '25' assert store['marker'] == 'deadbeef' assert store['limit'] == '25' def test_percent_encoded(self, simulate_request, client, resource): query_string = 'id=23,42&q=%e8%b1%86+%e7%93%a3' client.app.add_route('/', resource) simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req # NOTE(kgriffs): For lists, get_param will return one of the # elements, but which one it will choose is undefined. assert req.get_param('id') in [u'23', u'42'] assert req.get_param_as_list('id', int) == [23, 42] assert req.get_param('q') == u'\u8c46 \u74e3' def test_option_auto_parse_qs_csv_simple_false(self, simulate_request, client, resource): client.app.add_route('/', resource) client.app.req_options.auto_parse_qs_csv = False query_string = 'id=23,42,,&id=2' simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req assert req.params['id'] == [u'23,42,,', u'2'] assert req.get_param('id') in [u'23,42,,', u'2'] assert req.get_param_as_list('id') == [u'23,42,,', u'2'] def test_option_auto_parse_qs_csv_simple_true(self, simulate_request, client, resource): client.app.add_route('/', resource) client.app.req_options.auto_parse_qs_csv = True query_string = 'id=23,42,,&id=2' simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req assert req.params['id'] == [u'23', u'42', u'2'] assert req.get_param('id') in [u'23', u'42', u'2'] assert req.get_param_as_list('id', int) == [23, 42, 2] def test_option_auto_parse_qs_csv_complex_false(self, simulate_request, client, resource): client.app.add_route('/', resource) client.app.req_options.auto_parse_qs_csv = False encoded_json = '%7B%22msg%22:%22Testing%201,2,3...%22,%22code%22:857%7D' decoded_json = '{"msg":"Testing 1,2,3...","code":857}' query_string = ('colors=red,green,blue&limit=1' '&list-ish1=f,,x&list-ish2=,0&list-ish3=a,,,b' '&empty1=&empty2=,&empty3=,,' '&thing=' + encoded_json) simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req assert req.get_param('colors') in 'red,green,blue' assert req.get_param_as_list('colors') == [u'red,green,blue'] assert req.get_param_as_list('limit') == ['1'] assert req.get_param_as_list('empty1') is None assert req.get_param_as_list('empty2') == [u','] assert req.get_param_as_list('empty3') == [u',,'] assert req.get_param_as_list('list-ish1') == [u'f,,x'] assert req.get_param_as_list('list-ish2') == [u',0'] assert req.get_param_as_list('list-ish3') == [u'a,,,b'] assert req.get_param('thing') == decoded_json def test_bad_percentage(self, simulate_request, client, resource): client.app.add_route('/', resource) query_string = 'x=%%20%+%&y=peregrine&z=%a%z%zz%1%20e' response = simulate_request(client=client, path='/', query_string=query_string) assert response.status == falcon.HTTP_200 req = resource.captured_req assert req.get_param('x') == '% % %' assert req.get_param('y') == 'peregrine' assert req.get_param('z') == '%a%z%zz%1 e' def test_allowed_names(self, simulate_request, client, resource): client.app.add_route('/', resource) query_string = ('p=0&p1=23&2p=foo&some-thing=that&blank=&' 'some_thing=x&-bogus=foo&more.things=blah&' '_thing=42&_charset_=utf-8') simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req assert req.get_param('p') == '0' assert req.get_param('p1') == '23' assert req.get_param('2p') == 'foo' assert req.get_param('some-thing') == 'that' assert req.get_param('blank') is None assert req.get_param('some_thing') == 'x' assert req.get_param('-bogus') == 'foo' assert req.get_param('more.things') == 'blah' assert req.get_param('_thing') == '42' assert req.get_param('_charset_') == 'utf-8' @pytest.mark.parametrize('method_name', [ 'get_param', 'get_param_as_int', 'get_param_as_bool', 'get_param_as_list', ]) def test_required(self, simulate_request, client, resource, method_name): client.app.add_route('/', resource) query_string = '' simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req try: getattr(req, method_name)('marker', required=True) pytest.fail('falcon.HTTPMissingParam not raised') except falcon.HTTPMissingParam as ex: assert isinstance(ex, falcon.HTTPBadRequest) assert ex.title == 'Missing parameter' expected_desc = 'The "marker" parameter is required.' assert ex.description == expected_desc def test_int(self, simulate_request, client, resource): client.app.add_route('/', resource) query_string = 'marker=deadbeef&limit=25' simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req try: req.get_param_as_int('marker') except Exception as ex: assert isinstance(ex, falcon.HTTPBadRequest) assert isinstance(ex, falcon.HTTPInvalidParam) assert ex.title == 'Invalid parameter' expected_desc = ('The "marker" parameter is invalid. ' 'The value must be an integer.') assert ex.description == expected_desc assert req.get_param_as_int('limit') == 25 store = {} assert req.get_param_as_int('limit', store=store) == 25 assert store['limit'] == 25 assert req.get_param_as_int('limit', min=1, max=50) == 25 with pytest.raises(falcon.HTTPBadRequest): req.get_param_as_int('limit', min=0, max=10) with pytest.raises(falcon.HTTPBadRequest): req.get_param_as_int('limit', min=0, max=24) with pytest.raises(falcon.HTTPBadRequest): req.get_param_as_int('limit', min=30, max=24) with pytest.raises(falcon.HTTPBadRequest): req.get_param_as_int('limit', min=30, max=50) assert req.get_param_as_int('limit', min=1) == 25 assert req.get_param_as_int('limit', max=50) == 25 assert req.get_param_as_int('limit', max=25) == 25 assert req.get_param_as_int('limit', max=26) == 25 assert req.get_param_as_int('limit', min=25) == 25 assert req.get_param_as_int('limit', min=24) == 25 assert req.get_param_as_int('limit', min=-24) == 25 def test_int_neg(self, simulate_request, client, resource): client.app.add_route('/', resource) query_string = 'marker=deadbeef&pos=-7' simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req assert req.get_param_as_int('pos') == -7 assert req.get_param_as_int('pos', min=-10, max=10) == -7 assert req.get_param_as_int('pos', max=10) == -7 with pytest.raises(falcon.HTTPBadRequest): req.get_param_as_int('pos', min=-6, max=0) with pytest.raises(falcon.HTTPBadRequest): req.get_param_as_int('pos', min=-6) with pytest.raises(falcon.HTTPBadRequest): req.get_param_as_int('pos', min=0, max=10) with pytest.raises(falcon.HTTPBadRequest): req.get_param_as_int('pos', min=0, max=10) def test_boolean(self, simulate_request, client, resource): client.app.add_route('/', resource) query_string = ('echo=true&doit=false&bogus=bar&bogus2=foo&' 't1=True&f1=False&t2=yes&f2=no&blank&one=1&zero=0&' 'checkbox1=on&checkbox2=off') simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req with pytest.raises(falcon.HTTPBadRequest): req.get_param_as_bool('bogus') try: req.get_param_as_bool('bogus2') except Exception as ex: assert isinstance(ex, falcon.HTTPInvalidParam) assert ex.title == 'Invalid parameter' expected_desc = ('The "bogus2" parameter is invalid. ' 'The value of the parameter must be "true" ' 'or "false".') assert ex.description == expected_desc assert req.get_param_as_bool('echo') is True assert req.get_param_as_bool('doit') is False assert req.get_param_as_bool('t1') is True assert req.get_param_as_bool('t2') is True assert req.get_param_as_bool('f1') is False assert req.get_param_as_bool('f2') is False assert req.get_param_as_bool('one') is True assert req.get_param_as_bool('zero') is False assert req.get_param('blank') is None assert req.get_param_as_bool('checkbox1') is True assert req.get_param_as_bool('checkbox2') is False store = {} assert req.get_param_as_bool('echo', store=store) is True assert store['echo'] is True def test_boolean_blank(self, simulate_request, client, resource): client.app.add_route('/', resource) client.app.req_options.keep_blank_qs_values = True simulate_request(client=client, path='/', query_string='blank&blank2=') req = resource.captured_req assert req.get_param('blank') == '' assert req.get_param('blank2') == '' with pytest.raises(falcon.HTTPInvalidParam): req.get_param_as_bool('blank') with pytest.raises(falcon.HTTPInvalidParam): req.get_param_as_bool('blank2') assert req.get_param_as_bool('blank', blank_as_true=True) assert req.get_param_as_bool('blank3', blank_as_true=True) is None def test_list_type(self, simulate_request, client, resource): client.app.add_route('/', resource) query_string = ('colors=red,green,blue&limit=1' '&list-ish1=f,,x&list-ish2=,0&list-ish3=a,,,b' '&empty1=&empty2=,&empty3=,,' '&thing_one=1,,3' '&thing_two=1&thing_two=&thing_two=3') simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req # NOTE(kgriffs): For lists, get_param will return one of the # elements, but which one it will choose is undefined. assert req.get_param('colors') in ('red', 'green', 'blue') assert req.get_param_as_list('colors') == ['red', 'green', 'blue'] assert req.get_param_as_list('limit') == ['1'] assert req.get_param_as_list('marker') is None assert req.get_param_as_list('empty1') is None assert req.get_param_as_list('empty2') == [] assert req.get_param_as_list('empty3') == [] assert req.get_param_as_list('list-ish1') == ['f', 'x'] # Ensure that '0' doesn't get translated to None assert req.get_param_as_list('list-ish2') == ['0'] # Ensure that '0' doesn't get translated to None assert req.get_param_as_list('list-ish3') == ['a', 'b'] # Ensure consistency between list conventions assert req.get_param_as_list('thing_one') == ['1', '3'] assert ( req.get_param_as_list('thing_one') == req.get_param_as_list('thing_two') ) store = {} assert req.get_param_as_list('limit', store=store) == ['1'] assert store['limit'] == ['1'] def test_list_type_blank(self, simulate_request, client, resource): client.app.add_route('/', resource) query_string = ('colors=red,green,blue&limit=1' '&list-ish1=f,,x&list-ish2=,0&list-ish3=a,,,b' '&empty1=&empty2=,&empty3=,,' '&thing_one=1,,3' '&thing_two=1&thing_two=&thing_two=3' '&empty4=&empty4&empty4=' '&empty5&empty5&empty5') client.app.req_options.keep_blank_qs_values = True simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req # NOTE(kgriffs): For lists, get_param will return one of the # elements, but which one it will choose is undefined. assert req.get_param('colors') in ('red', 'green', 'blue') assert req.get_param_as_list('colors') == ['red', 'green', 'blue'] assert req.get_param_as_list('limit') == ['1'] assert req.get_param_as_list('marker') is None assert req.get_param_as_list('empty1') == [''] assert req.get_param_as_list('empty2') == ['', ''] assert req.get_param_as_list('empty3') == ['', '', ''] assert req.get_param_as_list('list-ish1') == ['f', '', 'x'] # Ensure that '0' doesn't get translated to None assert req.get_param_as_list('list-ish2') == ['', '0'] # Ensure that '0' doesn't get translated to None assert req.get_param_as_list('list-ish3') == ['a', '', '', 'b'] # Ensure consistency between list conventions assert req.get_param_as_list('thing_one') == ['1', '', '3'] assert req.get_param_as_list('thing_one') == req.get_param_as_list('thing_two') store = {} assert req.get_param_as_list('limit', store=store) == ['1'] assert store['limit'] == ['1'] # Test empty elements assert req.get_param_as_list('empty4') == ['', '', ''] assert req.get_param_as_list('empty5') == ['', '', ''] assert req.get_param_as_list('empty4') == req.get_param_as_list('empty5') def test_list_transformer(self, simulate_request, client, resource): client.app.add_route('/', resource) query_string = 'coord=1.4,13,15.1&limit=100&things=4,,1' simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req # NOTE(kgriffs): For lists, get_param will return one of the # elements, but which one it will choose is undefined. assert req.get_param('coord') in ('1.4', '13', '15.1') expected = [1.4, 13.0, 15.1] actual = req.get_param_as_list('coord', transform=float) assert actual == expected expected = ['4', '1'] actual = req.get_param_as_list('things', transform=str) assert actual == expected expected = [4, 1] actual = req.get_param_as_list('things', transform=int) assert actual == expected try: req.get_param_as_list('coord', transform=int) except Exception as ex: assert isinstance(ex, falcon.HTTPInvalidParam) assert ex.title == 'Invalid parameter' expected_desc = ('The "coord" parameter is invalid. ' 'The value is not formatted correctly.') assert ex.description == expected_desc def test_param_property(self, simulate_request, client, resource): client.app.add_route('/', resource) query_string = 'ant=4&bee=3&cat=2&dog=1' simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req assert ( sorted(req.params.items()) == [('ant', '4'), ('bee', '3'), ('cat', '2'), ('dog', '1')] ) def test_multiple_form_keys(self, simulate_request, client, resource): client.app.add_route('/', resource) query_string = 'ant=1&ant=2&bee=3&cat=6&cat=5&cat=4' simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req # By definition, we cannot guarantee which of the multiple keys will # be returned by .get_param(). assert req.get_param('ant') in ('1', '2') # There is only one 'bee' key so it remains a scalar. assert req.get_param('bee') == '3' # There are three 'cat' keys; order is preserved. assert req.get_param('cat') in ('6', '5', '4') def test_multiple_keys_as_bool(self, simulate_request, client, resource): client.app.add_route('/', resource) query_string = 'ant=true&ant=yes&ant=True' simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req assert req.get_param_as_bool('ant') is True def test_multiple_keys_as_int(self, simulate_request, client, resource): client.app.add_route('/', resource) query_string = 'ant=1&ant=2&ant=3' simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req assert req.get_param_as_int('ant') in (1, 2, 3) def test_multiple_form_keys_as_list(self, simulate_request, client, resource): client.app.add_route('/', resource) query_string = 'ant=1&ant=2&bee=3&cat=6&cat=5&cat=4' simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req # There are two 'ant' keys. assert req.get_param_as_list('ant') == ['1', '2'] # There is only one 'bee' key.. assert req.get_param_as_list('bee') == ['3'] # There are three 'cat' keys; order is preserved. assert req.get_param_as_list('cat') == ['6', '5', '4'] def test_get_date_valid(self, simulate_request, client, resource): client.app.add_route('/', resource) date_value = '2015-04-20' query_string = 'thedate={0}'.format(date_value) simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req assert req.get_param_as_date('thedate') == date(2015, 4, 20) def test_get_date_missing_param(self, simulate_request, client, resource): client.app.add_route('/', resource) query_string = 'notthedate=2015-04-20' simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req assert req.get_param_as_date('thedate') is None def test_get_date_valid_with_format(self, simulate_request, client, resource): client.app.add_route('/', resource) date_value = '20150420' query_string = 'thedate={0}'.format(date_value) format_string = '%Y%m%d' simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req assert req.get_param_as_date('thedate', format_string=format_string) == date(2015, 4, 20) def test_get_date_store(self, simulate_request, client, resource): client.app.add_route('/', resource) date_value = '2015-04-20' query_string = 'thedate={0}'.format(date_value) simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req store = {} req.get_param_as_date('thedate', store=store) assert len(store) != 0 def test_get_date_invalid(self, simulate_request, client, resource): client.app.add_route('/', resource) date_value = 'notarealvalue' query_string = 'thedate={0}'.format(date_value) format_string = '%Y%m%d' simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req with pytest.raises(HTTPInvalidParam): req.get_param_as_date('thedate', format_string=format_string) def test_get_datetime_valid(self, simulate_request, client, resource): client.app.add_route('/', resource) date_value = '2015-04-20T10:10:10Z' query_string = 'thedate={0}'.format(date_value) simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req assert req.get_param_as_datetime('thedate') == datetime(2015, 4, 20, 10, 10, 10) def test_get_datetime_missing_param(self, simulate_request, client, resource): client.app.add_route('/', resource) query_string = 'notthedate=2015-04-20T10:10:10Z' simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req assert req.get_param_as_datetime('thedate') is None def test_get_datetime_valid_with_format(self, simulate_request, client, resource): client.app.add_route('/', resource) date_value = '20150420 10:10:10' query_string = 'thedate={0}'.format(date_value) format_string = '%Y%m%d %H:%M:%S' simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req assert req.get_param_as_datetime( 'thedate', format_string=format_string) == datetime(2015, 4, 20, 10, 10, 10) def test_get_datetime_store(self, simulate_request, client, resource): client.app.add_route('/', resource) datetime_value = '2015-04-20T10:10:10Z' query_string = 'thedate={0}'.format(datetime_value) simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req store = {} req.get_param_as_datetime('thedate', store=store) assert len(store) != 0 assert store.get('thedate') == datetime(2015, 4, 20, 10, 10, 10) def test_get_datetime_invalid(self, simulate_request, client, resource): client.app.add_route('/', resource) date_value = 'notarealvalue' query_string = 'thedate={0}'.format(date_value) format_string = '%Y%m%dT%H:%M:%S' simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req with pytest.raises(HTTPInvalidParam): req.get_param_as_datetime('thedate', format_string=format_string) def test_get_dict_valid(self, simulate_request, client, resource): client.app.add_route('/', resource) payload_dict = {'foo': 'bar'} query_string = 'payload={0}'.format(json.dumps(payload_dict)) simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req assert req.get_param_as_dict('payload') == payload_dict def test_get_dict_missing_param(self, simulate_request, client, resource): client.app.add_route('/', resource) payload_dict = {'foo': 'bar'} query_string = 'notthepayload={0}'.format(json.dumps(payload_dict)) simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req assert req.get_param_as_dict('payload') is None def test_get_dict_store(self, simulate_request, client, resource): client.app.add_route('/', resource) payload_dict = {'foo': 'bar'} query_string = 'payload={0}'.format(json.dumps(payload_dict)) simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req store = {} req.get_param_as_dict('payload', store=store) assert len(store) != 0 def test_get_dict_invalid(self, simulate_request, client, resource): client.app.add_route('/', resource) payload_dict = 'foobar' query_string = 'payload={0}'.format(payload_dict) simulate_request(client=client, path='/', query_string=query_string) req = resource.captured_req with pytest.raises(HTTPInvalidParam): req.get_param_as_dict('payload') class TestPostQueryParams(object): @pytest.mark.parametrize('http_method', ('POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS')) def test_http_methods_body_expected(self, client, resource, http_method): client.app.add_route('/', resource) query_string = 'marker=deadbeef&limit=25' simulate_request_post_query_params(client=client, path='/', query_string=query_string, method=http_method) req = resource.captured_req assert req.get_param('marker') == 'deadbeef' assert req.get_param('limit') == '25' @pytest.mark.parametrize('http_method', ('GET', 'HEAD')) def test_http_methods_body_not_expected(self, client, resource, http_method): client.app.add_route('/', resource) query_string = 'marker=deadbeef&limit=25' simulate_request_post_query_params(client=client, path='/', query_string=query_string, method=http_method) req = resource.captured_req assert req.get_param('marker') is None assert req.get_param('limit') is None def test_non_ascii(self, client, resource): client.app.add_route('/', resource) value = u'\u8c46\u74e3' query_string = b'q=' + value.encode('utf-8') simulate_request_post_query_params(client=client, path='/', query_string=query_string) req = resource.captured_req assert req.get_param('q') is None def test_empty_body(self, client, resource): client.app.add_route('/', resource) simulate_request_post_query_params(client=client, path='/', query_string=None) req = resource.captured_req assert req.get_param('q') is None def test_empty_body_no_content_length(self, client, resource): client.app.add_route('/', resource) simulate_request_post_query_params(client=client, path='/', query_string=None) req = resource.captured_req assert req.get_param('q') is None def test_explicitly_disable_auto_parse(self, client, resource): client.app.add_route('/', resource) client.app.req_options.auto_parse_form_urlencoded = False simulate_request_post_query_params(client=client, path='/', query_string='q=42') req = resource.captured_req assert req.get_param('q') is None class TestPostQueryParamsDefaultBehavior(object): def test_dont_auto_parse_by_default(self): app = falcon.API() resource = testing.SimpleTestResource() app.add_route('/', resource) client = testing.TestClient(app) headers = {'Content-Type': 'application/x-www-form-urlencoded'} client.simulate_request(path='/', body='q=42', headers=headers) req = resource.captured_req assert req.get_param('q') is None