123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- # -*- coding: utf-8 -*-
- import json
-
- import pytest
- import typing
-
- from hapic.exception import OutputValidationException
-
- try: # Python 3.5+
- from http import HTTPStatus
- except ImportError:
- from http import client as HTTPStatus
-
- import marshmallow
- from multidict import MultiDict
-
- from hapic.data import HapicData
- from hapic.decorator import ExceptionHandlerControllerWrapper
- from hapic.decorator import InputQueryControllerWrapper
- from hapic.decorator import InputControllerWrapper
- from hapic.decorator import InputOutputControllerWrapper
- from hapic.decorator import OutputControllerWrapper
- from hapic.error import DefaultErrorBuilder
- from hapic.processor import MarshmallowOutputProcessor
- from hapic.processor import ProcessValidationError
- from hapic.processor import ProcessorInterface
- from hapic.processor import RequestParameters
- from tests.base import Base
- from tests.base import MyContext
-
-
- class MyProcessor(ProcessorInterface):
- def process(self, value):
- return value + 1
-
- def get_validation_error(
- self,
- request_context: RequestParameters,
- ) -> ProcessValidationError:
- return ProcessValidationError(
- details={
- 'original_request_context': request_context,
- },
- message='ERROR',
- )
-
-
- class MySimpleProcessor(ProcessorInterface):
- def process(self, value):
- return value
-
- def get_validation_error(
- self,
- request_context: RequestParameters,
- ) -> ProcessValidationError:
- return ProcessValidationError(
- details={
- 'original_request_context': request_context,
- },
- message='ERROR',
- )
-
-
- class MyControllerWrapper(InputOutputControllerWrapper):
- def before_wrapped_func(
- self,
- func_args: typing.Tuple[typing.Any, ...],
- func_kwargs: typing.Dict[str, typing.Any],
- ) -> typing.Union[None, typing.Any]:
- if func_args and func_args[0] == 666:
- return {
- 'error_response': 'we are testing'
- }
-
- func_kwargs['added_parameter'] = 'a value'
-
- def after_wrapped_function(self, response: typing.Any) -> typing.Any:
- return response * 2
-
-
- class MyInputQueryControllerWrapper(InputControllerWrapper):
- def get_processed_data(
- self,
- request_parameters: RequestParameters,
- ) -> typing.Any:
- return request_parameters.query_parameters
-
- def update_hapic_data(
- self,
- hapic_data: HapicData,
- processed_data: typing.Dict[str, typing.Any],
- ) -> typing.Any:
- hapic_data.query = processed_data
-
-
- class MySchema(marshmallow.Schema):
- name = marshmallow.fields.String(required=True)
-
-
- class TestControllerWrapper(Base):
- def test_unit__base_controller_wrapper__ok__no_behaviour(self):
- context = MyContext(app=None)
- processor = MyProcessor()
- wrapper = InputOutputControllerWrapper(context, processor)
-
- @wrapper.get_wrapper
- def func(foo):
- return foo
-
- result = func(42)
- assert result == 42
-
- def test_unit__base_controller__ok__replaced_response(self):
- context = MyContext(app=None)
- processor = MyProcessor()
- wrapper = MyControllerWrapper(context, processor)
-
- @wrapper.get_wrapper
- def func(foo):
- return foo
-
- # see MyControllerWrapper#before_wrapped_func
- result = func(666)
- # result have been replaced by MyControllerWrapper#before_wrapped_func
- assert {'error_response': 'we are testing'} == result
-
- def test_unit__controller_wrapper__ok__overload_input(self):
- context = MyContext(app=None)
- processor = MyProcessor()
- wrapper = MyControllerWrapper(context, processor)
-
- @wrapper.get_wrapper
- def func(foo, added_parameter=None):
- # see MyControllerWrapper#before_wrapped_func
- assert added_parameter == 'a value'
- return foo
-
- result = func(42)
- # See MyControllerWrapper#after_wrapped_function
- assert result == 84
-
-
- class TestInputControllerWrapper(Base):
- def test_unit__input_data_wrapping__ok__nominal_case(self):
- context = MyContext(
- app=None,
- fake_query_parameters=MultiDict(
- (
- ('foo', 'bar',),
- )
- )
- )
- processor = MyProcessor()
- wrapper = MyInputQueryControllerWrapper(context, processor)
-
- @wrapper.get_wrapper
- def func(foo, hapic_data=None):
- assert hapic_data
- assert isinstance(hapic_data, HapicData)
- # see MyControllerWrapper#before_wrapped_func
- assert hapic_data.query == {'foo': 'bar'}
- return foo
-
- result = func(42)
- assert result == 42
-
- def test_unit__multi_query_param_values__ok__use_as_list(self):
- context = MyContext(
- app=None,
- fake_query_parameters=MultiDict(
- (
- ('user_id', 'abc'),
- ('user_id', 'def'),
- ),
- )
- )
- processor = MySimpleProcessor()
- wrapper = InputQueryControllerWrapper(
- context,
- processor,
- as_list=['user_id'],
- )
-
- @wrapper.get_wrapper
- def func(hapic_data=None):
- assert hapic_data
- assert isinstance(hapic_data, HapicData)
- # see MyControllerWrapper#before_wrapped_func
- assert ['abc', 'def'] == hapic_data.query.get('user_id')
- return hapic_data.query.get('user_id')
-
- result = func()
- assert result == ['abc', 'def']
-
- def test_unit__multi_query_param_values__ok__without_as_list(self):
- context = MyContext(
- app=None,
- fake_query_parameters=MultiDict(
- (
- ('user_id', 'abc'),
- ('user_id', 'def'),
- ),
- )
- )
- processor = MySimpleProcessor()
- wrapper = InputQueryControllerWrapper(
- context,
- processor,
- )
-
- @wrapper.get_wrapper
- def func(hapic_data=None):
- assert hapic_data
- assert isinstance(hapic_data, HapicData)
- # see MyControllerWrapper#before_wrapped_func
- assert 'abc' == hapic_data.query.get('user_id')
- return hapic_data.query.get('user_id')
-
- result = func()
- assert result == 'abc'
-
-
- class TestOutputControllerWrapper(Base):
- def test_unit__output_data_wrapping__ok__nominal_case(self):
- context = MyContext(app=None)
- processor = MyProcessor()
- wrapper = OutputControllerWrapper(context, processor)
-
- @wrapper.get_wrapper
- def func(foo, hapic_data=None):
- # If no use of input wrapper, no hapic_data is given
- assert not hapic_data
- return foo
-
- result = func(42)
- # see MyProcessor#process
- assert {
- 'http_code': HTTPStatus.OK,
- 'original_response': '43',
- } == result
-
- def test_unit__output_data_wrapping__fail__error_response(self):
- context = MyContext(app=None)
- processor = MarshmallowOutputProcessor()
- processor.schema = MySchema()
- wrapper = OutputControllerWrapper(context, processor)
-
- @wrapper.get_wrapper
- def func(foo):
- return 'wrong result format'
-
- result = func(42)
- # see MyProcessor#process
- assert isinstance(result, dict)
- assert 'http_code' in result
- assert result['http_code'] == HTTPStatus.INTERNAL_SERVER_ERROR
- assert 'original_error' in result
- assert result['original_error'].details == {
- 'name': ['Missing data for required field.']
- }
-
-
- class TestExceptionHandlerControllerWrapper(Base):
- def test_unit__exception_handled__ok__nominal_case(self):
- context = MyContext(app=None)
- wrapper = ExceptionHandlerControllerWrapper(
- ZeroDivisionError,
- context,
- error_builder=DefaultErrorBuilder(),
- http_code=HTTPStatus.INTERNAL_SERVER_ERROR,
- )
-
- @wrapper.get_wrapper
- def func(foo):
- raise ZeroDivisionError('We are testing')
-
- response = func(42)
- assert 'http_code' in response
- assert response['http_code'] == HTTPStatus.INTERNAL_SERVER_ERROR
- assert 'original_response' in response
- assert json.loads(response['original_response']) == {
- 'message': 'We are testing',
- 'details': {},
- 'code': None,
- }
-
- def test_unit__exception_handled__ok__exception_error_dict(self):
- class MyException(Exception):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.error_dict = {}
-
- context = MyContext(app=None)
- wrapper = ExceptionHandlerControllerWrapper(
- MyException,
- context,
- error_builder=DefaultErrorBuilder(),
- http_code=HTTPStatus.INTERNAL_SERVER_ERROR,
- )
-
- @wrapper.get_wrapper
- def func(foo):
- exc = MyException('We are testing')
- exc.error_detail = {'foo': 'bar'}
- raise exc
-
- response = func(42)
- assert 'http_code' in response
- assert response['http_code'] == HTTPStatus.INTERNAL_SERVER_ERROR
- assert 'original_response' in response
- assert json.loads(response['original_response']) == {
- 'message': 'We are testing',
- 'details': {'foo': 'bar'},
- 'code': None,
- }
-
- def test_unit__exception_handler__error__error_content_malformed(self):
- class MyException(Exception):
- pass
-
- class MyErrorBuilder(DefaultErrorBuilder):
- def build_from_exception(self, exception: Exception) -> dict:
- # this is not matching with DefaultErrorBuilder schema
- return {}
-
- context = MyContext(app=None)
- wrapper = ExceptionHandlerControllerWrapper(
- MyException,
- context,
- error_builder=MyErrorBuilder(),
- )
-
- def raise_it():
- raise MyException()
-
- wrapper = wrapper.get_wrapper(raise_it)
- with pytest.raises(OutputValidationException):
- wrapper()
|