test: 100% Code coverage FTW!
This commit is contained in:
3
cover.sh
3
cover.sh
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
nosetests --with-coverage --cover-package=falcon --cover-min-percentage=90
|
||||
nosetests --with-coverage --cover-package=falcon
|
||||
|
||||
|
||||
@@ -104,9 +104,6 @@ class API(object):
|
||||
|
||||
"""
|
||||
|
||||
if not uri_template:
|
||||
uri_template = '/'
|
||||
|
||||
path_template = compile_uri_template(uri_template)
|
||||
method_map = create_http_method_map(resource)
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ def prepare_wsgi_content(resp):
|
||||
return []
|
||||
|
||||
|
||||
def compile_uri_template(template):
|
||||
def compile_uri_template(template=None):
|
||||
"""Compile the given URI template string into a pattern matcher.
|
||||
|
||||
Currently only recognizes Level 1 URI templates, and only for the path
|
||||
@@ -128,14 +128,16 @@ def compile_uri_template(template):
|
||||
See also: http://tools.ietf.org/html/rfc6570
|
||||
|
||||
Args:
|
||||
template: A Level 1 URI template. Method responders can retrieve values
|
||||
for the fields specified as part of the template path by calling
|
||||
req.get_param(field_name)
|
||||
template: A Level 1 URI template. Method responders must accept, as
|
||||
arguments, all fields specified in the template (default '/').
|
||||
|
||||
"""
|
||||
if not isinstance(template, str):
|
||||
raise TypeError('uri_template is not a string')
|
||||
|
||||
if not template:
|
||||
template = '/'
|
||||
|
||||
# Convert Level 1 var patterns to equivalent named regex groups
|
||||
escaped = re.sub(r'([\.\(\)\[\]\?\*\+\^\|])', r'\.', template)
|
||||
pattern = re.sub(r'{([a-zA-Z][a-zA-Z_]*)}', r'(?P<\1>[^/]+)', escaped)
|
||||
|
||||
@@ -33,7 +33,7 @@ class HTTPBadRequest(HTTPError):
|
||||
modifications."
|
||||
|
||||
Args:
|
||||
Same as for HTTPError, exept status is set for you.
|
||||
Same as for HTTPError, except status is set for you.
|
||||
|
||||
"""
|
||||
|
||||
@@ -74,7 +74,7 @@ class HTTPForbidden(HTTPError):
|
||||
to access the requested resource.
|
||||
|
||||
Args:
|
||||
Same as for HTTPError, exept status is set for you.
|
||||
Same as for HTTPError, except status is set for you.
|
||||
|
||||
Note from RFC 2616:
|
||||
|
||||
@@ -149,7 +149,7 @@ class HTTPConflict(HTTPError):
|
||||
Content-Type."
|
||||
|
||||
Args:
|
||||
Same as for HTTPError, exept status is set for you.
|
||||
Same as for HTTPError, except status is set for you.
|
||||
|
||||
"""
|
||||
def __init__(self, title, description, **kwargs):
|
||||
@@ -168,7 +168,7 @@ class HTTPPreconditionFailed(HTTPError):
|
||||
method from being applied to a resource other than the one intended."
|
||||
|
||||
Args:
|
||||
Same as for HTTPError, exept status is set for you.
|
||||
Same as for HTTPError, except status is set for you.
|
||||
|
||||
"""
|
||||
def __init__(self, title, description, **kwargs):
|
||||
@@ -215,16 +215,11 @@ class HTTPRangeNotSatisfiable(HTTPError):
|
||||
class HTTPUpgradeRequired(HTTPError):
|
||||
"""426 Upgrade Required
|
||||
|
||||
Sets title to "Upgrade Required".
|
||||
|
||||
Args:
|
||||
description: Human-friendly description of the error, along with a
|
||||
helpful suggestion or two.
|
||||
|
||||
The remaining (optional) args are the same as for HTTPError.
|
||||
Same as for HTTPError, except status is set for you.
|
||||
|
||||
"""
|
||||
def __init__(self, description, **kwargs):
|
||||
def __init__(self, title, description, **kwargs):
|
||||
HTTPError.__init__(self, status.HTTP_426, 'Upgrade Required',
|
||||
description, **kwargs)
|
||||
|
||||
@@ -233,7 +228,7 @@ class HTTPInternalServerError(HTTPError):
|
||||
"""500 Internal Server Error
|
||||
|
||||
Args:
|
||||
Same as for HTTPError, exept status is set for you.
|
||||
Same as for HTTPError, except status is set for you.
|
||||
|
||||
"""
|
||||
def __init__(self, title, description, **kwargs):
|
||||
@@ -244,7 +239,7 @@ class HTTPBadGateway(HTTPError):
|
||||
"""502 Bad Gateway
|
||||
|
||||
Args:
|
||||
Same as for HTTPError, exept status is set for you.
|
||||
Same as for HTTPError, except status is set for you.
|
||||
|
||||
"""
|
||||
def __init__(self, title, description, **kwargs):
|
||||
@@ -281,4 +276,6 @@ class HTTPServiceUnavailable(HTTPError):
|
||||
|
||||
"""
|
||||
def __init__(self, title, description, retry_after, **kwargs):
|
||||
headers = kwargs.setdefault('headers', {})
|
||||
headers['Retry-After'] = str(retry_after)
|
||||
HTTPError.__init__(self, status.HTTP_503, title, description, **kwargs)
|
||||
|
||||
@@ -92,10 +92,8 @@ class Request(object):
|
||||
"""Return True if the Accept header indicates JSON support"""
|
||||
|
||||
accept = self.get_header('Accept')
|
||||
if accept is not None:
|
||||
return ('application/json' in accept) or ('*/*' in accept)
|
||||
|
||||
return False
|
||||
return ((accept is not None) and
|
||||
(('application/json' in accept) or ('*/*' in accept)))
|
||||
|
||||
def get_header(self, name, default=None, required=False):
|
||||
"""Return a header value as a string
|
||||
|
||||
@@ -29,11 +29,6 @@ def bad_request(req, resp):
|
||||
resp.status = HTTP_400
|
||||
|
||||
|
||||
def server_error(req, resp):
|
||||
"""Sets response to "500 Internal Server Error", no body."""
|
||||
resp.status = HTTP_500
|
||||
|
||||
|
||||
def create_method_not_allowed(allowed_methods):
|
||||
"""Creates a responder for "405 Method Not Allowed".ipyth
|
||||
|
||||
|
||||
@@ -133,6 +133,12 @@ def create_environ(path='/', query_string='', protocol='HTTP/1.1', port='80',
|
||||
for name, value in headers.items():
|
||||
name = name.upper().replace('-', '_')
|
||||
|
||||
if value is None:
|
||||
if name == 'ACCEPT' or name == 'USER_AGENT':
|
||||
del env['HTTP_' + name]
|
||||
|
||||
continue
|
||||
|
||||
if name == 'CONTENT_TYPE':
|
||||
env[name] = value.strip()
|
||||
elif name == 'CONTENT_LENGTH':
|
||||
|
||||
@@ -12,8 +12,9 @@ class FaultyResource:
|
||||
status = req.get_header('X-Error-Status')
|
||||
title = req.get_header('X-Error-Title')
|
||||
description = req.get_header('X-Error-Description')
|
||||
code = 10042
|
||||
|
||||
raise falcon.HTTPError(status, title, description)
|
||||
raise falcon.HTTPError(status, title, description, code=code)
|
||||
|
||||
def on_post(self, req, resp):
|
||||
raise falcon.HTTPForbidden(
|
||||
@@ -30,6 +31,19 @@ class FaultyResource:
|
||||
href_text='Drill baby drill!')
|
||||
|
||||
|
||||
class MiscErrorsResource:
|
||||
|
||||
def __init__(self, exception, needs_title):
|
||||
self.needs_title = needs_title
|
||||
self._exception = exception
|
||||
|
||||
def on_get(self, req, resp):
|
||||
if self.needs_title:
|
||||
raise self._exception('Excuse Us', 'Something went boink!')
|
||||
else:
|
||||
raise self._exception('Something went boink!')
|
||||
|
||||
|
||||
class UnauthorizedResource:
|
||||
|
||||
def on_get(self, req, resp):
|
||||
@@ -50,13 +64,6 @@ class MethodNotAllowedResource:
|
||||
raise falcon.HTTPMethodNotAllowed(['PUT'])
|
||||
|
||||
|
||||
class InternalServerErrorResource:
|
||||
|
||||
def on_get(self, req, resp):
|
||||
raise falcon.HTTPInternalServerError('Excuse Us', 'Something went'
|
||||
'boink!')
|
||||
|
||||
|
||||
class RangeNotSatisfiableResource:
|
||||
|
||||
def on_get(self, req, resp):
|
||||
@@ -66,12 +73,24 @@ class RangeNotSatisfiableResource:
|
||||
raise falcon.HTTPRangeNotSatisfiable(123456, 'x-falcon/peregrine')
|
||||
|
||||
|
||||
class ServiceUnavailableResource:
|
||||
|
||||
def on_get(self, req, resp):
|
||||
raise falcon.HTTPServiceUnavailable('Oops', 'Stand by...', 60)
|
||||
|
||||
|
||||
class TestHTTPError(testing.TestSuite):
|
||||
|
||||
def prepare(self):
|
||||
self.resource = FaultyResource()
|
||||
self.api.add_route('/fail', self.resource)
|
||||
|
||||
def _misc_test(self, exception, status, needs_title=True):
|
||||
self.api.add_route('/misc', MiscErrorsResource(exception, needs_title))
|
||||
|
||||
self._simulate_request('/misc')
|
||||
self.assertEqual(self.srmock.status, status)
|
||||
|
||||
def test_base_class(self):
|
||||
headers = {
|
||||
'X-Error-Title': 'Storage service down',
|
||||
@@ -85,7 +104,8 @@ class TestHTTPError(testing.TestSuite):
|
||||
b'{\n'
|
||||
b' "title": "Storage service down",\n'
|
||||
b' "description": "The configured storage service is not '
|
||||
b'responding to requests. Please contact your service provider"\n'
|
||||
b'responding to requests. Please contact your service provider",\n'
|
||||
b' "code": 10042\n'
|
||||
b'}'
|
||||
]
|
||||
|
||||
@@ -94,7 +114,7 @@ class TestHTTPError(testing.TestSuite):
|
||||
body = self._simulate_request('/fail', headers=headers)
|
||||
self.assertEqual(self.srmock.status, headers['X-Error-Status'])
|
||||
self.assertThat(lambda: json.loads(body[0]), Not(raises(ValueError)))
|
||||
self.assertEqual(body, expected_body)
|
||||
self.assertEqual(expected_body, body)
|
||||
|
||||
# Now try it with application/json
|
||||
headers['Accept'] = 'application/json'
|
||||
@@ -117,6 +137,20 @@ class TestHTTPError(testing.TestSuite):
|
||||
self.assertEqual(self.srmock.status, headers['X-Error-Status'])
|
||||
self.assertEqual(body, [])
|
||||
|
||||
def test_client_does_not_accept_anything(self):
|
||||
headers = {
|
||||
'Accept': None,
|
||||
'X-Error-Title': 'Storage service down',
|
||||
'X-Error-Description': ('The configured storage service is not '
|
||||
'responding to requests. Please contact '
|
||||
'your service provider'),
|
||||
'X-Error-Status': falcon.HTTP_503
|
||||
}
|
||||
|
||||
body = self._simulate_request('/fail', headers=headers)
|
||||
self.assertEqual(self.srmock.status, headers['X-Error-Status'])
|
||||
self.assertEqual(body, [])
|
||||
|
||||
def test_forbidden(self):
|
||||
headers = {
|
||||
'Accept': 'application/json'
|
||||
@@ -207,8 +241,23 @@ class TestHTTPError(testing.TestSuite):
|
||||
self.assertIn(('Content-Type', 'x-falcon/peregrine'),
|
||||
self.srmock.headers)
|
||||
|
||||
def test_500(self):
|
||||
self.api.add_route('/500', InternalServerErrorResource())
|
||||
self._simulate_request('/500')
|
||||
def test_503(self):
|
||||
self.api.add_route('/503', ServiceUnavailableResource())
|
||||
body = self._simulate_request('/503')
|
||||
|
||||
self.assertEqual(self.srmock.status, falcon.HTTP_500)
|
||||
expected_body = (b'{\n "title": "Oops",\n "description": '
|
||||
b'"Stand by..."\n}')
|
||||
|
||||
self.assertEqual(self.srmock.status, falcon.HTTP_503)
|
||||
self.assertEqual(body, [expected_body])
|
||||
self.assertIn(('Retry-After', '60'), self.srmock.headers)
|
||||
|
||||
def test_misc(self):
|
||||
self._misc_test(falcon.HTTPBadRequest, falcon.HTTP_400)
|
||||
self._misc_test(falcon.HTTPConflict, falcon.HTTP_409)
|
||||
self._misc_test(falcon.HTTPPreconditionFailed, falcon.HTTP_412)
|
||||
self._misc_test(falcon.HTTPUnsupportedMediaType, falcon.HTTP_415,
|
||||
needs_title=False)
|
||||
self._misc_test(falcon.HTTPUpgradeRequired, falcon.HTTP_426)
|
||||
self._misc_test(falcon.HTTPInternalServerError, falcon.HTTP_500)
|
||||
self._misc_test(falcon.HTTPBadGateway, falcon.HTTP_502)
|
||||
|
||||
@@ -27,6 +27,9 @@ class TestUriTemplates(testing.TestSuite):
|
||||
|
||||
self.assertEquals(req.get_param('id'), None)
|
||||
|
||||
def test_not_str(self):
|
||||
self.assertRaises(TypeError, self.api.add_route, {}, self.resource)
|
||||
|
||||
def test_no_vars(self):
|
||||
self.api.add_route('/hello/world', self.resource)
|
||||
self._simulate_request('/hello/world')
|
||||
|
||||
Reference in New Issue
Block a user