Skip to content

Commit 0e10c25

Browse files
committed
fix(web) issue2551382 - 409 not 400 errors returned
invalid integer values for @verbose, @page_* values in rest uri's generated a 409 (Update Conflict) error not a generic 400 error. Found it when I was working on adding fuzz testing to check error handling for query parameters in REST url's. This also ads the tests in test_liveserver that found the error. Also refactored tst_liveserver to allow resuse of session login method for the new fuzz testing class as well.
1 parent 8b8d1e3 commit 0e10c25

File tree

3 files changed

+108
-8
lines changed

3 files changed

+108
-8
lines changed

CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ Fixed:
4141
for client.py-Client::_serve_file(). (John Rouillard)
4242
- issue2551381 - roundup-server parses URI's with multiple '?"
4343
incorrectly. (John Rouillard)
44+
- issue2551382 - invalid @verbose, @page_* values in rest uri's
45+
generate 409 not 400 error. (John Rouillard)
4446

4547
Features:
4648

roundup/rest.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -811,10 +811,18 @@ def get_collection(self, class_name, input_payload):
811811
value = form_field.value
812812
if key.startswith("@page_"): # serve the paging purpose
813813
key = key[6:]
814-
value = int(value)
814+
try:
815+
value = int(value)
816+
except ValueError as e:
817+
raise UsageError("When using @page_%s: %s" %
818+
(key, e.args[0]))
815819
page[key] = value
816820
elif key == "@verbose":
817-
verbose = int(value)
821+
try:
822+
verbose = int(value)
823+
except ValueError as e:
824+
raise UsageError("When using @verbose: %s" %
825+
(e.args[0]))
818826
elif key in ["@fields", "@attrs"]:
819827
f = value.split(",")
820828
if len(f) == 1:
@@ -1129,7 +1137,11 @@ def get_element(self, class_name, item_id, input_payload):
11291137
# used only if no @fields/@attrs
11301138
protected = value.lower() == "true"
11311139
elif key == "@verbose":
1132-
verbose = int(value)
1140+
try:
1141+
verbose = int(value)
1142+
except ValueError as e:
1143+
raise UsageError("When using @verbose: %s" %
1144+
(e.args[0]))
11331145

11341146
result = {}
11351147
if props is None:

test/test_liveserver.py

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,37 @@
2020
skip_requests = mark_class(pytest.mark.skip(
2121
reason='Skipping liveserver tests: requests library not available'))
2222

23+
try:
24+
import hypothesis
25+
skip_hypothesis = lambda func, *args, **kwargs: func
26+
27+
# ruff: noqa: E402
28+
from hypothesis import example, given, settings
29+
from hypothesis.strategies import binary, characters, none, one_of, sampled_from, text
30+
31+
except ImportError:
32+
from .pytest_patcher import mark_class
33+
skip_hypothesis = mark_class(pytest.mark.skip(
34+
reason='Skipping hypothesis liveserver tests: hypothesis library not available'))
35+
36+
# define a dummy decorator that can take args
37+
def noop_decorators_with_args(*args, **kwargs):
38+
def noop_decorators(func):
39+
def internal():
40+
pass
41+
return internal
42+
return noop_decorators
43+
44+
# define a dummy strategy
45+
def noop_strategy(*args, **kwargs):
46+
pass
47+
48+
# define the decorator functions
49+
example = given = settings = noop_decorators_with_args
50+
# and stratgies using in decorators
51+
binary = characters = none = one_of = sampled_from = text = noop_strategy
52+
53+
2354
try:
2455
import brotli
2556
skip_brotli = lambda func, *args, **kwargs: func
@@ -149,11 +180,9 @@ def create_app(self):
149180
# doesn't support the max bytes to read argument.
150181
return RequestDispatcher(self.dirname)
151182

152-
153-
@skip_requests
154-
class BaseTestCases(WsgiSetup):
155-
"""Class with all tests to run against wsgi server. Is reused when
156-
wsgi server is started with various feature flags
183+
class ClientSetup():
184+
""" Utility programs for the client querying a server.
185+
Just a login session at the moment but more to come I am sure.
157186
"""
158187

159188
def create_login_session(self, username="admin", password="sekrit",
@@ -176,6 +205,63 @@ def create_login_session(self, username="admin", password="sekrit",
176205
return session
177206
return session, response
178207

208+
209+
@skip_hypothesis
210+
class FuzzGetUrls(WsgiSetup, ClientSetup):
211+
212+
_max_examples = 100
213+
214+
@given(sampled_from(['@verbose', '@page_size', '@page_index']),
215+
one_of(characters(),text(min_size=1)))
216+
@settings(max_examples=_max_examples,
217+
deadline=10000) # 10000ms
218+
def test_class_url_param_accepting_integer_values(self, param, value):
219+
"""Tests all integer args for rest url. @page_* is the
220+
same code for all *.
221+
"""
222+
session, _response = self.create_login_session()
223+
url = '%s/rest/data/status' % (self.url_base())
224+
query = '%s=%s' % (param, value)
225+
f = session.get(url, params=query)
226+
try:
227+
if int(value) >= 0:
228+
self.assertEqual(f.status_code, 200)
229+
except ValueError:
230+
if value in ['#', '&']:
231+
self.assertEqual(f.status_code, 200)
232+
else:
233+
# invalid value for param
234+
self.assertEqual(f.status_code, 400)
235+
236+
@given(sampled_from(['@verbose']),
237+
one_of(characters(),text(min_size=1)))
238+
@settings(max_examples=_max_examples,
239+
deadline=10000) # 10000ms
240+
def test_element_url_param_accepting_integer_values(self, param, value):
241+
"""Tests all integer args for rest url. @page_* is the
242+
same code for all *.
243+
"""
244+
session, _response = self.create_login_session()
245+
url = '%s/rest/data/status/1' % (self.url_base())
246+
query = '%s=%s' % (param, value)
247+
f = session.get(url, params=query)
248+
try:
249+
if int(value) >= 0:
250+
self.assertEqual(f.status_code, 200)
251+
except ValueError:
252+
if value in ['#', '&']:
253+
self.assertEqual(f.status_code, 200)
254+
else:
255+
# invalid value for param
256+
self.assertEqual(f.status_code, 400)
257+
258+
259+
@skip_requests
260+
class BaseTestCases(WsgiSetup, ClientSetup):
261+
"""Class with all tests to run against wsgi server. Is reused when
262+
wsgi server is started with various feature flags
263+
"""
264+
179265
def test_cookie_attributes(self):
180266
session, _response = self.create_login_session()
181267

0 commit comments

Comments
 (0)