def test_no_body_custom_status(self): request = MockRequest() actual = helpers.create_response(request, status=HTTPStatus.CREATED) assert isinstance(actual, HttpResponse) assert actual.status == HTTPStatus.CREATED assert actual.body is None
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_no_body(self): request = MockRequest() actual = helpers.create_response(request) assert isinstance(actual, HttpResponse) assert actual.status == HTTPStatus.NO_CONTENT assert actual.body is None
def test_bind_to_instance(self): @decorators.Operation(tags=('eek', 'bar')) def target(binding, request): """ Test target """ assert binding == api return 'foo' class MockApi(object): def __init__(self): self.call_count = defaultdict(int) self.resource = User self.tags = {'bar'} def pre_dispatch(self, request, path_args): self.call_count['pre_dispatch'] += 1 def post_dispatch(self, request, response): self.call_count['post_dispatch'] += 1 return response api = MockApi() request = MockRequest() target.bind_to_instance(api) assert target.binding == api assert target.resource is User assert target.is_bound assert target.tags == {'eek', 'bar'} actual = target(request, {}) assert actual == 'foo' assert api.call_count == {'pre_dispatch': 1, 'post_dispatch': 1}
def test_get_resource__codec_exceptions(body, error_code): request = MockRequest(body=body) with pytest.raises(HttpError) as exc_info: helpers.get_resource(request, User) assert exc_info.value.status == HTTPStatus.BAD_REQUEST assert exc_info.value.resource.code == error_code
def test_execute(self): @decorators.ResourceOperation(resource=User) def my_func(request, user): assert isinstance(user, User) assert user.name == "Stephen" request = MockRequest(body='{"id": 1, "name": "Stephen"}') my_func(request, {})
def test_get_resource(): request = MockRequest(body='{"$": "tests.User", "id":10, "name": "Dave"}') request.request_codec = json_codec user = helpers.get_resource(request, User) assert isinstance(user, User) assert user.id == 10 assert user.name == 'Dave'
def test_content(self): request = MockRequest() actual = helpers.create_response(request, {"foo": "bar"}) assert isinstance(actual, HttpResponse) assert actual.status == HTTPStatus.OK assert actual.headers['Content-Type'] == json_codec.CONTENT_TYPE assert json_codec.json.loads(actual.body) == {"foo": "bar"}
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__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_execute__invalid(self): @decorators.ResourceOperation(resource=User) def my_func(request, user): assert isinstance(user, User) assert user.name == "Stephen" request = MockRequest(body='{"id": 1, "name": "Stephen"') with pytest.raises(HttpError): my_func(request, {})
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_request_headers(self, cors_config, expected): api_interface = ApiInterfaceBase(mock_endpoint) cors.CORS(api_interface, **cors_config) target = api_interface.middleware[0] http_request = MockRequest(headers={'Origin': 'http://my-domain.org'}, current_operation=mock_endpoint) actual = target.request_headers(http_request) assert expected == actual
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_bad_values(self, options, query, expected_errors): mock_request = MockRequest(query=query) @decorators.ListOperation(**options) def my_func(request, **kwargs): return [1, 2, 3] with pytest.raises(ValidationError) as error: my_func(mock_request, {'foo': 'bar'}) assert len(error.value.error_messages) == expected_errors
def test_pre_flight_headers(self, cors_config, expected): api_interface = ApiInterfaceBase(mock_endpoint) cors.CORS(api_interface, **cors_config) target = api_interface.middleware[0] http_request = MockRequest(headers={'Origin': 'http://my-domain.org'}, current_operation=mock_endpoint) actual = target.pre_flight_headers(http_request, mock_endpoint.methods) assert 'GET, HEAD' == actual.pop('Allow') assert 'no-cache, no-store' == actual.pop('Cache-Control') assert expected == actual
def test_get_resource__multiple(): request = MockRequest( body='[{"$": "tests.User", "id":10, "name": "Dave"}]') request.request_codec = json_codec users = helpers.get_resource(request, User, allow_multiple=True) assert len(users) == 1 user = users[0] assert isinstance(user, User) assert user.id == 10 assert user.name == 'Dave'
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_post_request(self, origins, method, expected): api_interface = ApiInterfaceBase(mock_endpoint) cors.CORS(api_interface, origins=origins) target = api_interface.middleware[0] http_request = MockRequest(headers={'Origin': 'http://my-domain.org'}, method=method, current_operation=mock_endpoint) http_response = HttpResponse('') actual = target.post_request(http_request, http_response) assert actual is http_response assert expected == actual.headers.get('Access-Control-Allow-Origin')
def test_unbound(self): @decorators.Operation(tags=('eek', 'bar')) def target(request): """ Test target """ return 'foo' request = MockRequest() assert target.resource is None assert not target.is_bound assert target.tags == {'eek', 'bar'} actual = target(request, {}) assert actual == 'foo'
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_cors_options(self, origins, expected): api_interface = ApiInterfaceBase(mock_endpoint) cors.CORS(api_interface, origins=origins) target = api_interface.middleware[0] http_request = MockRequest(headers={'Origin': 'http://my-domain.org'}, method=Method.OPTIONS) cors._MethodsMiddleware( (Method.GET, Method.HEAD)).pre_dispatch(http_request, None) actual = target.cors_options(http_request) assert isinstance(actual, HttpResponse) assert 'GET, HEAD' == actual.headers.pop('Allow') assert 'no-cache, no-store' == actual.headers.pop('Cache-Control') assert expected == actual.headers.get('Access-Control-Allow-Origin')
def test_returning_total_count(self): mock_request = MockRequest() @decorators.ListOperation def my_func(request, foo, offset, limit): assert foo == 'bar' assert offset == 0 assert limit == 50 return [1, 2, 3], 5 result = my_func(mock_request, {'foo': 'bar'}) assert isinstance(result, HttpResponse) assert result.body == '[1, 2, 3]' assert result['X-Page-Offset'] == '0' assert result['X-Page-Limit'] == '50' assert result['X-Total-Count'] == '5'
def test_returning_total_count(self): mock_request = MockRequest() @decorators.WrappedListOperation def my_func(request, foo, offset, limit): assert foo == 'bar' assert offset == 0 assert limit == 50 return [1, 2, 3], 5 result = my_func(mock_request, {'foo': 'bar'}) assert isinstance(result, decorators.Listing) assert result.results == [1, 2, 3] assert result.offset == 0 assert result.limit == 50 assert result.total_count == 5
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_options_handled(self, options, query, offset, limit): mock_request = MockRequest(query=query) @decorators.ListOperation(**options) def my_func(request, **kwargs): assert request is mock_request assert kwargs['offset'] == offset assert kwargs['limit'] == limit assert kwargs['foo'] == 'bar' return [1, 2, 3] result = my_func(mock_request, {'foo': 'bar'}) assert isinstance(result, HttpResponse) assert result.body == '[1, 2, 3]' assert result['X-Page-Offset'] == str(offset) assert result['X-Page-Limit'] == str(limit) assert 'X-Total-Count' not in result.headers
def test_options_handled(self, options, query, offset, limit, bare): mock_request = MockRequest(query=query) @decorators.WrappedListOperation(**options) def my_func(request, **kwargs): assert request is mock_request assert kwargs['offset'] == offset assert kwargs['limit'] == limit assert kwargs['foo'] == 'bar' return [1, 2, 3] result = my_func(mock_request, {'foo': 'bar'}) if bare: assert result == [1, 2, 3] else: assert isinstance(result, decorators.Listing) assert result.results == [1, 2, 3] assert result.offset == offset assert result.limit == limit assert result.total_count is None
@pytest.mark.parametrize('value, expected', ( (None, ''), ('', ''), ('text/plain', 'text/plain'), ('text/plain; encoding=UTF-8', 'text/plain'), ('text/plain; encoding=UTF-8; x=y', 'text/plain'), )) def test_parse_content_type(value, expected): actual = helpers.parse_content_type(value) assert actual == expected @pytest.mark.parametrize('http_request, expected', ( (MockRequest(), 'application/json'), (MockRequest(headers={'accepts': 'text/html'}), 'text/html'), (MockRequest(headers={'Content-Type': 'text/plain'}), 'text/plain'), (MockRequest(headers={ 'accepts': 'text/html', 'Content-Type': 'text/plain' }), 'text/html'), (MockRequest(headers={'x-auth': '123'}), 'application/json'), )) def test_resolve_content_type(http_request, expected): actual = helpers.resolve_content_type([ content_type_resolvers.accepts_header(), content_type_resolvers.content_type_header(), content_type_resolvers.specific_default('application/json'), ], http_request)
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)), } }
def test_get_swagger(self, monkeypatch): monkeypatch.setattr( swagger, 'CODECS', { 'application/json': None # Only the Keys are used. }) request = MockRequest() target = swagger.SwaggerSpec("Example", schemes='http') base = ApiInterfaceBase(ApiVersion(target, ), ) base.registered_codecs.clear() base.registered_codecs[ 'application/yaml'] = None # Only the keys are used. actual = target.get_swagger(request) expected = { 'swagger': '2.0', 'info': { 'title': 'Example', 'version': '1' }, 'host': '127.0.0.1', 'schemes': ['http'], 'basePath': '/api/v1', 'consumes': ['application/yaml'], 'produces': ['application/yaml'], 'paths': {}, 'definitions': { 'Error': { 'type': 'object', 'properties': { 'code': { 'description': 'Custom application specific error code that references into the application.', 'type': 'integer', 'format': 'int64', }, 'developer_message': { 'description': 'An error message suitable for the application developer', 'type': 'string' }, 'message': { 'description': 'A message that can be displayed to an end user', 'type': 'string' }, 'meta': { 'description': 'Additional meta information that can help solve errors.', }, 'status': { 'description': 'HTTP status code of the response.', 'type': 'integer', 'format': 'int64', } }, }, 'Listing': { 'type': 'object', 'properties': { 'limit': { 'description': 'Limit or page size of the result set', 'type': 'integer', 'format': 'int64', }, 'offset': { 'description': 'Offset within the result set.', 'type': 'integer', 'format': 'int64', }, 'results': { 'description': 'List of resources.', }, 'total_count': { 'description': 'The total number of items in the result set.', 'type': 'integer', 'format': 'int64', } }, } } } assert actual == expected