def test_base_path(self): target = swagger.SwaggerSpec(title="") ApiInterfaceBase( ApiContainer(ApiContainer(ApiContainer(target), name='b'), name='a')) assert UrlPath.parse("/api/a/b") == target.base_path assert UrlPath.parse("/api/a/b/swagger") == target.swagger_path
def test_op_paths(self): target = containers.ApiInterfaceBase(MockResourceApi()) actual = list(target.op_paths()) assert actual == [ (UrlPath.parse('/api/a/b'), Operation(mock_callback, 'a/b', Method.GET)), (UrlPath.parse('/api/a/b'), Operation(mock_callback, 'a/b', Method.POST)), (UrlPath.parse('/api/d/e'), Operation(mock_callback, 'd/e', (Method.POST, Method.PATCH))), ]
def test_op_paths__no_sub_path(self): target = containers.ApiContainer(MockResourceApi()) actual = dict(target.op_paths()) assert actual == { UrlPath.parse('a/b'): Operation(mock_callback, 'a/b', Method.GET), UrlPath.parse('a/b'): Operation(mock_callback, 'a/b', Method.POST), UrlPath.parse('d/e'): Operation(mock_callback, 'd/e', (Method.POST, Method.PATCH)), }
def test_op_paths(self): target = UserApi() actual = dict(target.op_paths(NoPath)) assert actual == { UrlPath.parse('user'): Operation(mock_callback, NoPath, methods=Method.GET), UrlPath.parse('user/{resource_id}'): Operation(mock_callback, '{resource_id}', methods=Method.GET), UrlPath.parse('user/start'): Operation(mock_callback, 'start', methods=Method.POST), }
def test_op_paths__collate_methods(self): target = containers.ApiInterfaceBase(MockResourceApi()) actual = target.op_paths(collate_methods=True) assert actual == { UrlPath.parse('/api/a/b'): { Method.GET: Operation(mock_callback, 'a/b', Method.GET), Method.POST: Operation(mock_callback, 'a/b', Method.POST), }, UrlPath.parse('/api/d/e'): { Method.POST: Operation(mock_callback, 'd/e', (Method.POST, Method.PATCH)), Method.PATCH: Operation(mock_callback, 'd/e', (Method.POST, Method.PATCH)), } }
def test_no_key(self): request = MockRequest.from_uri('/foo/bar') target = signing.FixedSignedAuth(None) @Operation(path=UrlPath.parse('/foo/bar'), middleware=[target]) def callback(request): return 'ok' with pytest.raises(PermissionDenied): callback(request, {})
def test_error(self, url, kwargs): request = MockRequest.from_uri('/foo/bar') target = signing.FixedSignedAuth(base64.b32decode('DEADBEEF')) @Operation(path=UrlPath.parse('/foo/bar'), middleware=[target]) def callback(request): return 'ok' with pytest.raises(PermissionDenied): callback(request, {})
def test_valid_signature(self, uri): request = MockRequest.from_uri(uri) target = signing.FixedSignedAuth(base64.b32decode('DEADBEEF')) @Operation(path=UrlPath.parse('/foo/bar'), middleware=[target]) def callback(r): return 'ok' actual = callback(request, {}) assert actual == 'ok'
def op_paths(self, path_base): yield path_base + UrlPath.parse('a/b'), Operation( mock_callback, UrlPath.parse('a/b'), Method.GET) yield path_base + UrlPath.parse('a/b'), Operation( mock_callback, UrlPath.parse('a/b'), Method.POST) yield path_base + UrlPath.parse('d/e'), Operation( mock_callback, UrlPath.parse('d/e'), (Method.POST, Method.PATCH))
def test_register_operation(self): target = containers.ApiCollection() @target.operation("a/b") def my_operation(request): pass actual = dict(target.op_paths()) assert len(actual) == 1 assert actual == { UrlPath.parse("a/b"): Operation(mock_callback, 'a/b') }
def test_generate_parameters(self): actual = swagger.SwaggerSpec.generate_parameters( UrlPath.parse('/api/a/{b}/c/{d:String}')) assert actual == [{ 'name': 'b', 'in': 'path', 'type': 'integer', 'required': True, }, { 'name': 'd', 'in': 'path', 'type': 'string', 'required': True, }]
def test_startswith__invalid_other(self, other): target = UrlPath.parse('/a/b/c') with pytest.raises(TypeError): target.startswith(other)
def test_startswith(self, other, expected): target = UrlPath.parse('/a/b/c') assert expected == target.startswith(other)
def test_getitem(self, path, item, expected): target = UrlPath.parse(path) actual = str(target[item]) assert actual == expected
def test_parse__raises_error(self, path): with pytest.raises(ValueError): UrlPath.parse(path)
def test_parse(self, path, expected): target = UrlPath.parse(path) assert target._nodes == expected
class TestUrlPath(object): @pytest.mark.parametrize('obj, expected', ( (UrlPath('', 'foo'), ('', 'foo')), ('/foo', ('', 'foo')), (PathParam('name'), (PathParam('name'), )), (('', 'foo'), ('', 'foo')), )) def test_from_object(self, obj, expected): target = UrlPath.from_object(obj) assert target._nodes == expected @pytest.mark.parametrize('obj', (1, None, 1.2, object())) def test_from_object__value_error(self, obj): with pytest.raises(ValueError): UrlPath.from_object(obj) @pytest.mark.parametrize('path, expected', ( ('', ()), ('/', ('', )), ('a', ('a', )), ('a/b', ('a', 'b')), ('/a/b', ('', 'a', 'b')), ('/a/b/', ('', 'a', 'b')), ('a/{b}/c', ('a', PathParam('b'), 'c')), ('a/{b:Integer}/c', ('a', PathParam('b', Type.Integer), 'c')), ('a/{b:Regex:abc:123}/c', ('a', PathParam('b', Type.Regex, 'abc:123'), 'c')), )) def test_parse(self, path, expected): target = UrlPath.parse(path) assert target._nodes == expected @pytest.mark.parametrize('path', ( 'a/{b/c', 'a/b}/c', 'a/{b:}/c', 'a/{b:int}/c', 'a/{b:eek}/c', )) def test_parse__raises_error(self, path): with pytest.raises(ValueError): UrlPath.parse(path) @pytest.mark.parametrize('path, expected', ( (('', ), '/'), (('a', ), 'a'), (('', 'a'), '/a'), (('', 'a', 'b'), '/a/b'), (('', 'a', PathParam('b', None), 'c'), '/a/{b}/c'), (('', 'a', PathParam('b', Type.String), 'c'), '/a/{b:String}/c'), )) def test_str(self, path, expected): target = UrlPath(*path) assert str(target) == expected @pytest.mark.parametrize('a, b, expected', ( (UrlPath.parse('a/b/c'), UrlPath.parse('d'), ('a', 'b', 'c', 'd')), (UrlPath.parse(''), UrlPath.parse('a/b'), ('a', 'b')), (UrlPath.parse('/a/b'), UrlPath.parse('c/d'), ('', 'a', 'b', 'c', 'd')), (UrlPath.parse('/a/b'), 'c', ('', 'a', 'b', 'c')), (UrlPath.parse('/a/b'), 'c/d', ('', 'a', 'b', 'c', 'd')), (UrlPath.parse('/a/b'), PathParam('c'), ('', 'a', 'b', PathParam('c'))), ('c', UrlPath.parse('a/b'), ('c', 'a', 'b')), ('c/d', UrlPath.parse('a/b'), ('c', 'd', 'a', 'b')), (PathParam('c'), UrlPath.parse('a/b'), (PathParam('c'), 'a', 'b')), )) def test_add(self, a, b, expected): actual = a + b assert actual._nodes == expected @pytest.mark.parametrize('a, b', ( (UrlPath.parse('a/b/c'), UrlPath.parse('/d')), ('a/b/c', UrlPath.parse('/d')), (PathParam('c'), UrlPath.parse('/d')), (UrlPath.parse('/d'), 1), (1, UrlPath.parse('/d')), )) def test_add__error(self, a, b): with pytest.raises((ValueError, TypeError)): a + b @pytest.mark.parametrize('a, b, expected', ( (UrlPath.parse('a/b/c'), UrlPath.parse('a/b/c'), True), (UrlPath.parse('/a/b/c'), UrlPath.parse('/a/b/c'), True), (UrlPath('a', 'b', 'c'), UrlPath.parse('a/b/c'), True), (UrlPath('', 'a', 'b', 'c'), UrlPath.parse('/a/b/c'), True), (UrlPath('a', 'b', 'c'), UrlPath.parse('/a/b/c'), False), (UrlPath('a', 'b', 'c'), 123, False), )) def test_eq(self, a, b, expected): assert (a == b) is expected @pytest.mark.parametrize('path, item, expected', ( ('/a/b/c', 0, '/'), ('/a/b/c', 1, 'a'), ('/a/b/c', slice(1), '/'), ('/a/b/c', slice(None, 1), '/'), ('/a/b/c', slice(1, None), 'a/b/c'), ('/a/b/c', slice(-1, None), 'c'), )) def test_getitem(self, path, item, expected): target = UrlPath.parse(path) actual = str(target[item]) assert actual == expected @pytest.mark.parametrize('other, expected', ( ('/', True), ('/a', True), ('/a/', True), ('/a/b/c', True), ('/b', False), )) def test_startswith(self, other, expected): target = UrlPath.parse('/a/b/c') assert expected == target.startswith(other) @pytest.mark.parametrize('other', (None, 123, object(), True)) def test_startswith__invalid_other(self, other): target = UrlPath.parse('/a/b/c') with pytest.raises(TypeError): target.startswith(other) @pytest.mark.parametrize('target, expected', ( (UrlPath.parse('/a/b/c'), True), (UrlPath.parse('a/b/c'), False), (UrlPath(), False), )) def test_is_absolute(self, target, expected): assert target.is_absolute == expected @pytest.mark.parametrize('args, expected', ( (('a', 'b', 'c'), ()), (('a', PathParam('b'), 'c'), (PathParam('b'), )), (('a', PathParam('b'), PathParam('c')), (PathParam('b'), PathParam('c'))), )) def test_path_nodes(self, args, expected): target = UrlPath(*args) actual = tuple(target.path_nodes) assert actual == expected @pytest.mark.parametrize('path_node, expected', ( (PathParam('name'), '{name:Integer}'), (PathParam('name', Type.String), '{name:String}'), (PathParam('name', None, None), '{name}'), )) def test_odinweb_node_formatter(self, path_node, expected): assert UrlPath.odinweb_node_formatter(path_node) == expected @pytest.mark.parametrize('url_path, formatter, expected', ( (UrlPath('a', 'b', 'c'), None, 'a/b/c'), (UrlPath('', 'a', 'b', 'c'), None, '/a/b/c'), (UrlPath('', 'a', PathParam('b'), 'c'), None, '/a/{b:Integer}/c'), (UrlPath('', 'a', PathParam('b', None), 'c'), None, '/a/{b}/c'), (UrlPath('', 'a', PathParam('b', Type.String), 'c'), None, '/a/{b:String}/c'), (UrlPath('', 'a', PathParam('b', Type.String), 'c'), UrlPath.odinweb_node_formatter, '/a/{b:String}/c'), (UrlPath('', 'a', PathParam('b', Type.Regex, "abc"), 'c'), UrlPath.odinweb_node_formatter, '/a/{b:Regex:abc}/c'), )) def test_format(self, url_path, formatter, expected): actual = url_path.format(formatter) assert actual == expected
class TestApiInterfaceBase(object): @pytest.mark.parametrize('options,name,debug_enabled,path_prefix', ( ({}, 'api', False, UrlPath.parse('/api')), ({ 'name': '!api' }, '!api', False, UrlPath.parse('/!api')), ({ 'path_prefix': '/my-app/' }, 'api', False, UrlPath.parse('/my-app')), ({ 'debug_enabled': True }, 'api', True, UrlPath.parse('/api')), )) def test_options(self, options, name, debug_enabled, path_prefix): target = containers.ApiInterfaceBase(**options) assert target.name == name assert target.debug_enabled == debug_enabled assert target.path_prefix == path_prefix def test_init_non_absolute(self): with pytest.raises(ValueError): containers.ApiInterfaceBase(path_prefix='ab/c') def test_dispatch(self): pass @pytest.mark.parametrize('r, status, message', ( (MockRequest(headers={ 'content-type': 'application/xml', 'accepts': 'application/json' }), 422, 'Unprocessable Entity'), (MockRequest(headers={ 'content-type': 'application/json', 'accepts': 'application/xml' }), 406, 'URI not available in preferred format'), (MockRequest(method=Method.POST), 405, 'Specified method is invalid for this resource'), )) def test_dispatch__invalid_headers(self, r, status, message): target = containers.ApiInterfaceBase() operation = Operation(mock_callback) actual = target.dispatch(operation, r) assert actual.status == status assert actual.body == message @pytest.mark.parametrize('error,status', ( (api.ImmediateHttpResponse(None, HTTPStatus.NOT_MODIFIED, {}), HTTPStatus.NOT_MODIFIED), (ValidationError("Error"), 400), (ValidationError({}), 400), (NotImplementedError, 501), (ValueError, 500), (api.ImmediateHttpResponse(ValueError, HTTPStatus.NOT_MODIFIED, {}), 500), )) def test_dispatch__exceptions(self, error, status): def callback(request): raise error target = containers.ApiInterfaceBase() operation = Operation(callback) actual = target.dispatch(operation, MockRequest()) assert actual.status == status def test_dispatch__with_middleware(self): calls = [] class Middleware(object): def pre_request(self, request, path_args): calls.append('pre_request') def pre_dispatch(self, request, path_args): calls.append('pre_dispatch') path_args['foo'] = 'bar' def post_dispatch(self, request, response): calls.append('post_dispatch') return 'eek' + response def post_request(self, request, response): calls.append('post_request') response['test'] = 'header' return response def callback(request, **args): assert args['foo'] == 'bar' return 'boo' target = containers.ApiInterfaceBase(middleware=[Middleware()]) operation = Operation(callback) actual = target.dispatch(operation, MockRequest()) assert actual.body == '"eekboo"' assert actual.status == 200 assert 'test' in actual.headers assert calls == [ 'pre_request', 'pre_dispatch', 'post_dispatch', 'post_request' ] def test_dispatch__with_middleware_pre_request_response(self): """ Test scenario where pre-request hook returns a HTTP Response object """ class Middleware(object): def pre_request(self, request, path_args): return HttpResponse('eek!', status=HTTPStatus.FORBIDDEN) def callback(request, **args): assert False, "Response should have already occurred!" target = containers.ApiInterfaceBase(middleware=[Middleware()]) operation = Operation(callback) actual = target.dispatch(operation, MockRequest()) assert actual.body == 'eek!' assert actual.status == 403 def test_dispatch__error_with_debug_enabled(self): def callback(request): raise ValueError() target = containers.ApiInterfaceBase(debug_enabled=True) operation = Operation(callback) with pytest.raises(ValueError): target.dispatch(operation, MockRequest()) def test_dispatch__error_handled_by_middleware(self): class ErrorMiddleware(object): def handle_500(self, request, exception): assert isinstance(exception, ValueError) return Error.from_status(HTTPStatus.SEE_OTHER, 0, "Quick over there...") def callback(request): raise ValueError() target = containers.ApiInterfaceBase(middleware=[ErrorMiddleware()]) operation = Operation(callback) actual = target.dispatch(operation, MockRequest()) assert actual.status == 303 def test_dispatch__error_handled_by_middleware_raises_exception(self): class ErrorMiddleware(object): def handle_500(self, request, exception): assert isinstance(exception, ValueError) raise ValueError def callback(request): raise ValueError() target = containers.ApiInterfaceBase(middleware=[ErrorMiddleware()]) operation = Operation(callback) actual = target.dispatch(operation, MockRequest()) assert actual.status == 500 def test_dispatch__encode_error_with_debug_enabled(self): def callback(request): raise api.ImmediateHttpResponse(ValueError, HTTPStatus.NOT_MODIFIED, {}) target = containers.ApiInterfaceBase(debug_enabled=True) operation = Operation(callback) with pytest.raises(TypeError): target.dispatch(operation, MockRequest()) def test_dispatch__http_response(self): def callback(request): return HttpResponse("eek") target = containers.ApiInterfaceBase() operation = Operation(callback) actual = target.dispatch(operation, MockRequest()) assert actual.body == 'eek' assert actual.status == 200 def test_op_paths(self): target = containers.ApiInterfaceBase(MockResourceApi()) actual = list(target.op_paths()) assert actual == [ (UrlPath.parse('/api/a/b'), Operation(mock_callback, 'a/b', Method.GET)), (UrlPath.parse('/api/a/b'), Operation(mock_callback, 'a/b', Method.POST)), (UrlPath.parse('/api/d/e'), Operation(mock_callback, 'd/e', (Method.POST, Method.PATCH))), ] def test_op_paths__collate_methods(self): target = containers.ApiInterfaceBase(MockResourceApi()) actual = target.op_paths(collate_methods=True) assert actual == { UrlPath.parse('/api/a/b'): { Method.GET: Operation(mock_callback, 'a/b', Method.GET), Method.POST: Operation(mock_callback, 'a/b', Method.POST), }, UrlPath.parse('/api/d/e'): { Method.POST: Operation(mock_callback, 'd/e', (Method.POST, Method.PATCH)), Method.PATCH: Operation(mock_callback, 'd/e', (Method.POST, Method.PATCH)), } }