Fixing bugs and returning RestController to 100% test coverage

This commit is contained in:
Yoann Roman
2011-01-07 22:07:02 -05:00
parent d2b8fb9462
commit 582adec3cd
2 changed files with 285 additions and 17 deletions

View File

@@ -7,11 +7,13 @@ from routing import iscontroller, lookup_controller
class RestController(object):
_custom_actions = {}
@expose()
def _route(self, args):
# convention uses "_method" to handle browser-unsupported methods
method = request.GET.get('_method', request.method).lower()
method = request.params.get('_method', request.method).lower()
# make sure DELETE/PUT requests don't use GET
if request.method == 'GET' and method in ('delete', 'put'):
@@ -108,13 +110,13 @@ class RestController(object):
return controller, remainder[:-1]
# check for custom GET requests
if method_name in getattr(self, '_custom_actions', []):
controller = self._find_controller(method_name, 'get_%s' % method_name)
if method.upper() in self._custom_actions.get(method_name, []):
controller = self._find_controller('get_%s' % method_name, method_name)
if controller:
return controller, remainder[:-1]
controller = getattr(self, remainder[0], None)
if controller and not ismethod(controller):
return controller, remainder[1:]
return lookup_controller(controller, remainder[1:])
# finally, check for the regular get_one/get requests
controller = self._find_controller('get_one', 'get')
@@ -142,20 +144,23 @@ class RestController(object):
abort(404)
def _handle_post(self, method, remainder):
# check for custom controllers or sub-controllers
# check for custom POST/PUT requests
if remainder:
controller = self._find_controller(remainder[0])
if controller:
return controller, remainder[1:]
sub_controller = getattr(self, remainder[0], None)
if sub_controller and not ismethod(sub_controller):
return lookup_controller(sub_controller, remainder[1:])
method_name = remainder[-1]
if method.upper() in self._custom_actions.get(method_name, []):
controller = self._find_controller('%s_%s' % (method, method_name), method_name)
if controller:
return controller, remainder[:-1]
controller = getattr(self, remainder[0], None)
if controller and not ismethod(controller):
return lookup_controller(controller, remainder[1:])
# check for regular post/put requests
# check for regular POST/PUT requests
controller = self._find_controller(method)
if controller:
return controller, remainder
abort(404)
_handle_put = _handle_post

View File

@@ -1,17 +1,30 @@
from pecan import expose, make_app, request, response
from pecan import abort, expose, make_app, request, response
from pecan.rest import RestController
from webob import exc
from webtest import TestApp, AppError
from py.test import raises
from webtest import TestApp
from json import dumps, loads
class TestRestController(object):
def test_basic_rest(self):
class OthersController(object):
@expose()
def index(self):
return 'OTHERS'
@expose()
def echo(self, value):
return str(value)
class ThingsController(RestController):
data = ['zero', 'one', 'two', 'three']
_custom_actions = {'count': ['GET'], 'length': ['GET', 'POST']}
others = OthersController()
@expose()
def get_one(self, id):
return self.data[int(id)]
@@ -20,6 +33,17 @@ class TestRestController(object):
def get_all(self):
return dict(items=self.data)
@expose()
def length(self, id, value=None):
length = len(self.data[int(id)])
if value:
length += len(value)
return str(length)
@expose()
def get_count(self):
return str(len(self.data))
@expose()
def new(self):
return 'NEW'
@@ -47,6 +71,22 @@ class TestRestController(object):
def delete(self, id):
del self.data[int(id)]
return 'DELETED'
@expose()
def reset(self):
return 'RESET'
@expose()
def post_options(self):
return 'OPTIONS'
@expose()
def options(self):
abort(500)
@expose()
def other(self):
abort(500)
class RootController(object):
things = ThingsController()
@@ -142,6 +182,73 @@ class TestRestController(object):
r = app.get('/things')
assert r.status_int == 200
assert len(loads(r.body)['items']) == 3
# test "RESET" custom action
r = app.request('/things', method='RESET')
assert r.status_int == 200
assert r.body == 'RESET'
# test "RESET" custom action with _method parameter
r = app.get('/things?_method=RESET')
assert r.status_int == 200
assert r.body == 'RESET'
# test the "OPTIONS" custom action
r = app.request('/things', method='OPTIONS')
assert r.status_int == 200
assert r.body == 'OPTIONS'
# test the "OPTIONS" custom action with the _method parameter
r = app.post('/things', {'_method': 'OPTIONS'})
assert r.status_int == 200
assert r.body == 'OPTIONS'
# test the "other" custom action
r = app.request('/things/other', method='MISC', status=405)
assert r.status_int == 405
# test the "other" custom action with the _method parameter
r = app.post('/things/other', {'_method': 'MISC'}, status=405)
assert r.status_int == 405
# test the "others" custom action
r = app.request('/things/others', method='MISC')
assert r.status_int == 200
assert r.body == 'OTHERS'
# test the "others" custom action with the _method parameter
r = app.get('/things/others?_method=MISC')
assert r.status_int == 200
assert r.body == 'OTHERS'
# test an invalid custom action
r = app.get('/things?_method=BAD', status=404)
assert r.status_int == 404
# test custom "GET" request "count"
r = app.get('/things/count')
assert r.status_int == 200
assert r.body == '3'
# test custom "GET" request "length"
r = app.get('/things/1/length')
assert r.status_int == 200
assert r.body == str(len('one'))
# test custom "GET" request through subcontroller
r = app.get('/things/others/echo?value=test')
assert r.status_int == 200
assert r.body == 'test'
# test custom "POST" request "length"
r = app.post('/things/1/length', {'value':'test'})
assert r.status_int == 200
assert r.body == str(len('onetest'))
# test custom "POST" request through subcontroller
r = app.post('/things/others/echo', {'value':'test'})
assert r.status_int == 200
assert r.body == 'test'
def test_nested_rest(self):
@@ -414,3 +521,159 @@ class TestRestController(object):
r = app.get('/foos')
assert r.status_int == 200
assert len(loads(r.body)['items']) == 1
def test_bad_rest(self):
class ThingsController(RestController):
pass
class RootController(object):
things = ThingsController()
# create the app
app = TestApp(make_app(RootController()))
# test get_all
r = app.get('/things', status=404)
assert r.status_int == 404
# test get_one
r = app.get('/things/1', status=404)
assert r.status_int == 404
# test post
r = app.post('/things', {'value':'one'}, status=404)
assert r.status_int == 404
# test edit
r = app.get('/things/1/edit', status=404)
assert r.status_int == 404
# test put
r = app.put('/things/1', {'value':'ONE'}, status=404)
# test put with _method parameter and GET
r = app.get('/things/1?_method=put', {'value':'ONE!'}, status=405)
assert r.status_int == 405
# test put with _method parameter and POST
r = app.post('/things/1?_method=put', {'value':'ONE!'}, status=404)
assert r.status_int == 404
# test get delete
r = app.get('/things/1/delete', status=404)
assert r.status_int == 404
# test delete
r = app.delete('/things/1', status=404)
assert r.status_int == 404
# test delete with _method parameter and GET
r = app.get('/things/1?_method=DELETE', status=405)
assert r.status_int == 405
# test delete with _method parameter and POST
r = app.post('/things/1?_method=DELETE', status=404)
assert r.status_int == 404
# test "RESET" custom action
r = app.request('/things', method='RESET', status=404)
assert r.status_int == 404
def test_custom_delete(self):
class OthersController(object):
@expose()
def index(self):
return 'DELETE'
@expose()
def reset(self, id):
return str(id)
class ThingsController(RestController):
others = OthersController()
@expose()
def delete_fail(self):
abort(500)
class RootController(object):
things = ThingsController()
# create the app
app = TestApp(make_app(RootController()))
# test bad delete
r = app.delete('/things/delete_fail', status=405)
assert r.status_int == 405
# test bad delete with _method parameter and GET
r = app.get('/things/delete_fail?_method=delete', status=405)
assert r.status_int == 405
# test bad delete with _method parameter and POST
r = app.post('/things/delete_fail', {'_method':'delete'}, status=405)
assert r.status_int == 405
# test custom delete without ID
r = app.delete('/things/others')
assert r.status_int == 200
assert r.body == 'DELETE'
# test custom delete without ID with _method parameter and GET
r = app.get('/things/others?_method=delete', status=405)
assert r.status_int == 405
# test custom delete without ID with _method parameter and POST
r = app.post('/things/others', {'_method':'delete'})
assert r.status_int == 200
assert r.body == 'DELETE'
# test custom delete with ID
r = app.delete('/things/others/reset/1')
assert r.status_int == 200
assert r.body == '1'
# test custom delete with ID with _method parameter and GET
r = app.get('/things/others/reset/1?_method=delete', status=405)
assert r.status_int == 405
# test custom delete with ID with _method parameter and POST
r = app.post('/things/others/reset/1', {'_method':'delete'})
assert r.status_int == 200
assert r.body == '1'
def test_get_with_var_args(self):
class OthersController(object):
@expose()
def index(self, one, two, three):
return 'NESTED: %s, %s, %s' % (one, two, three)
class ThingsController(RestController):
others = OthersController()
@expose()
def get_one(self, *args):
return ', '.join(args)
class RootController(object):
things = ThingsController()
# create the app
app = TestApp(make_app(RootController()))
# test get request
r = app.get('/things/one/two/three')
assert r.status_int == 200
assert r.body == 'one, two, three'
# test nested get request
r = app.get('/things/one/two/three/others')
assert r.status_int == 200
assert r.body == 'NESTED: one, two, three'