test: 100% Code coverage FTW!

This commit is contained in:
Kurt Griffiths
2013-02-08 12:04:01 -05:00
parent aabc32ebcd
commit cfda5751bc
9 changed files with 92 additions and 44 deletions

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env bash
nosetests --with-coverage --cover-package=falcon --cover-min-percentage=90
nosetests --with-coverage --cover-package=falcon

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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':

View File

@@ -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)

View File

@@ -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')