Пример #1
0
class TestApiCollection(object):
    """
    Actually test with ApiVersion is this is a thin layer over a collection.
    """
    @pytest.mark.parametrize('options,version,name,path_prefix', (
        ({}, 1, 'v1', UrlPath('v1')),
        ({
            'version': 2
        }, 2, 'v2', UrlPath('v2')),
        ({
            'version': 3,
            'name': 'version-3'
        }, 3, 'version-3', UrlPath('version-3')),
    ))
    def test_version_options(self, options, version, name, path_prefix):
        target = containers.ApiVersion(**options)

        assert target.version == version
        assert target.name == name
        assert target.path_prefix == path_prefix

    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')
        }
Пример #2
0
    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
Пример #3
0
class TestApiContainer(object):
    @pytest.mark.parametrize('options,attr,value', (
        ({}, 'name', None),
        ({
            'name': 'foo'
        }, 'name', 'foo'),
        ({}, 'path_prefix', UrlPath()),
        ({
            'name': 'foo'
        }, 'path_prefix', UrlPath('foo')),
        ({
            'path_prefix': ['bar']
        }, 'path_prefix', UrlPath('bar')),
    ))
    def test_options(self, options, attr, value):
        target = containers.ApiContainer(**options)

        assert hasattr(target, attr)
        assert getattr(target, attr) == value

    def test_extra_option(self):
        with pytest.raises(TypeError,
                           message="Got an unexpected keyword argument 'foo'"):
            containers.ApiContainer(foo=1, name='test')

        with pytest.raises(TypeError):
            containers.ApiContainer(foo=1, bar=2)

    def test_op_paths(self):
        target = containers.ApiContainer(MockResourceApi())

        actual = dict(target.op_paths('c'))

        assert actual == {
            UrlPath.parse('c/a/b'):
            Operation(mock_callback, 'a/b', Method.GET),
            UrlPath.parse('c/a/b'):
            Operation(mock_callback, 'a/b', Method.POST),
            UrlPath.parse('c/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)),
        }
Пример #4
0
    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)),
        }
Пример #5
0
    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),
        }
Пример #6
0
    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))),
        ]
Пример #7
0
    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)),
            }
        }
Пример #8
0
    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, {})
Пример #9
0
    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, {})
Пример #10
0
    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'
Пример #11
0
 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))
Пример #12
0
    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')
        }
Пример #13
0
 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,
     }]
Пример #14
0
 def test_odinweb_node_formatter(self, path_node, expected):
     assert UrlPath.odinweb_node_formatter(path_node) == expected
Пример #15
0
 def test_path_nodes(self, args, expected):
     target = UrlPath(*args)
     actual = tuple(target.path_nodes)
     assert actual == expected
Пример #16
0
 def test_startswith__invalid_other(self, other):
     target = UrlPath.parse('/a/b/c')
     with pytest.raises(TypeError):
         target.startswith(other)
Пример #17
0
 def test_startswith(self, other, expected):
     target = UrlPath.parse('/a/b/c')
     assert expected == target.startswith(other)
Пример #18
0
 def test_getitem(self, path, item, expected):
     target = UrlPath.parse(path)
     actual = str(target[item])
     assert actual == expected
Пример #19
0
    def test_str(self, path, expected):
        target = UrlPath(*path)

        assert str(target) == expected
Пример #20
0
 def test_parse__raises_error(self, path):
     with pytest.raises(ValueError):
         UrlPath.parse(path)
Пример #21
0
 def test_parse(self, path, expected):
     target = UrlPath.parse(path)
     assert target._nodes == expected
Пример #22
0
 def test_from_object__value_error(self, obj):
     with pytest.raises(ValueError):
         UrlPath.from_object(obj)
Пример #23
0
    def test_from_object(self, obj, expected):
        target = UrlPath.from_object(obj)

        assert target._nodes == expected
Пример #24
0
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
Пример #25
0
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)),
            }
        }