Skip to content

Commit

Permalink
fix(web) issue2551382 - 409 not 400 errors returned
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
rouilj committed Dec 15, 2024
1 parent 8b8d1e3 commit 0e10c25
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 8 deletions.
2 changes: 2 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ Fixed:
for client.py-Client::_serve_file(). (John Rouillard)
- issue2551381 - roundup-server parses URI's with multiple '?"
incorrectly. (John Rouillard)
- issue2551382 - invalid @verbose, @page_* values in rest uri's
generate 409 not 400 error. (John Rouillard)

Features:

Expand Down
18 changes: 15 additions & 3 deletions roundup/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -811,10 +811,18 @@ def get_collection(self, class_name, input_payload):
value = form_field.value
if key.startswith("@page_"): # serve the paging purpose
key = key[6:]
value = int(value)
try:
value = int(value)
except ValueError as e:
raise UsageError("When using @page_%s: %s" %
(key, e.args[0]))
page[key] = value
elif key == "@verbose":
verbose = int(value)
try:
verbose = int(value)
except ValueError as e:
raise UsageError("When using @verbose: %s" %
(e.args[0]))
elif key in ["@fields", "@attrs"]:
f = value.split(",")
if len(f) == 1:
Expand Down Expand Up @@ -1129,7 +1137,11 @@ def get_element(self, class_name, item_id, input_payload):
# used only if no @fields/@attrs
protected = value.lower() == "true"
elif key == "@verbose":
verbose = int(value)
try:
verbose = int(value)
except ValueError as e:
raise UsageError("When using @verbose: %s" %
(e.args[0]))

result = {}
if props is None:
Expand Down
96 changes: 91 additions & 5 deletions test/test_liveserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,37 @@
skip_requests = mark_class(pytest.mark.skip(
reason='Skipping liveserver tests: requests library not available'))

try:
import hypothesis
skip_hypothesis = lambda func, *args, **kwargs: func

# ruff: noqa: E402
from hypothesis import example, given, settings
from hypothesis.strategies import binary, characters, none, one_of, sampled_from, text

except ImportError:
from .pytest_patcher import mark_class
skip_hypothesis = mark_class(pytest.mark.skip(
reason='Skipping hypothesis liveserver tests: hypothesis library not available'))

# define a dummy decorator that can take args
def noop_decorators_with_args(*args, **kwargs):
def noop_decorators(func):
def internal():
pass
return internal
return noop_decorators

# define a dummy strategy
def noop_strategy(*args, **kwargs):
pass

# define the decorator functions
example = given = settings = noop_decorators_with_args
# and stratgies using in decorators
binary = characters = none = one_of = sampled_from = text = noop_strategy


try:
import brotli
skip_brotli = lambda func, *args, **kwargs: func
Expand Down Expand Up @@ -149,11 +180,9 @@ def create_app(self):
# doesn't support the max bytes to read argument.
return RequestDispatcher(self.dirname)


@skip_requests
class BaseTestCases(WsgiSetup):
"""Class with all tests to run against wsgi server. Is reused when
wsgi server is started with various feature flags
class ClientSetup():
""" Utility programs for the client querying a server.
Just a login session at the moment but more to come I am sure.
"""

def create_login_session(self, username="admin", password="sekrit",
Expand All @@ -176,6 +205,63 @@ def create_login_session(self, username="admin", password="sekrit",
return session
return session, response


@skip_hypothesis
class FuzzGetUrls(WsgiSetup, ClientSetup):

_max_examples = 100

@given(sampled_from(['@verbose', '@page_size', '@page_index']),
one_of(characters(),text(min_size=1)))
@settings(max_examples=_max_examples,
deadline=10000) # 10000ms
def test_class_url_param_accepting_integer_values(self, param, value):
"""Tests all integer args for rest url. @page_* is the
same code for all *.
"""
session, _response = self.create_login_session()
url = '%s/rest/data/status' % (self.url_base())
query = '%s=%s' % (param, value)
f = session.get(url, params=query)
try:
if int(value) >= 0:
self.assertEqual(f.status_code, 200)
except ValueError:
if value in ['#', '&']:
self.assertEqual(f.status_code, 200)
else:
# invalid value for param
self.assertEqual(f.status_code, 400)

@given(sampled_from(['@verbose']),
one_of(characters(),text(min_size=1)))
@settings(max_examples=_max_examples,
deadline=10000) # 10000ms
def test_element_url_param_accepting_integer_values(self, param, value):
"""Tests all integer args for rest url. @page_* is the
same code for all *.
"""
session, _response = self.create_login_session()
url = '%s/rest/data/status/1' % (self.url_base())
query = '%s=%s' % (param, value)
f = session.get(url, params=query)
try:
if int(value) >= 0:
self.assertEqual(f.status_code, 200)
except ValueError:
if value in ['#', '&']:
self.assertEqual(f.status_code, 200)
else:
# invalid value for param
self.assertEqual(f.status_code, 400)


@skip_requests
class BaseTestCases(WsgiSetup, ClientSetup):
"""Class with all tests to run against wsgi server. Is reused when
wsgi server is started with various feature flags
"""

def test_cookie_attributes(self):
session, _response = self.create_login_session()

Expand Down

0 comments on commit 0e10c25

Please sign in to comment.