test_decorator.py 7.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. # -*- coding: utf-8 -*-
  2. import typing
  3. from http import HTTPStatus
  4. import marshmallow
  5. from hapic.data import HapicData
  6. from hapic.decorator import ExceptionHandlerControllerWrapper
  7. from hapic.decorator import InputControllerWrapper
  8. from hapic.decorator import InputOutputControllerWrapper
  9. from hapic.decorator import OutputControllerWrapper
  10. from hapic.hapic import ErrorResponseSchema
  11. from hapic.processor import MarshmallowOutputProcessor
  12. from hapic.processor import ProcessValidationError
  13. from hapic.processor import ProcessorInterface
  14. from hapic.processor import RequestParameters
  15. from tests.base import Base
  16. from tests.base import MyContext
  17. class MyProcessor(ProcessorInterface):
  18. def process(self, value):
  19. return value + 1
  20. def get_validation_error(
  21. self,
  22. request_context: RequestParameters,
  23. ) -> ProcessValidationError:
  24. return ProcessValidationError(
  25. details={
  26. 'original_request_context': request_context,
  27. },
  28. message='ERROR',
  29. )
  30. class MyControllerWrapper(InputOutputControllerWrapper):
  31. def before_wrapped_func(
  32. self,
  33. func_args: typing.Tuple[typing.Any, ...],
  34. func_kwargs: typing.Dict[str, typing.Any],
  35. ) -> typing.Union[None, typing.Any]:
  36. if func_args and func_args[0] == 666:
  37. return {
  38. 'error_response': 'we are testing'
  39. }
  40. func_kwargs['added_parameter'] = 'a value'
  41. def after_wrapped_function(self, response: typing.Any) -> typing.Any:
  42. return response * 2
  43. class MyInputQueryControllerWrapper(InputControllerWrapper):
  44. def get_processed_data(
  45. self,
  46. request_parameters: RequestParameters,
  47. ) -> typing.Any:
  48. return request_parameters.query_parameters
  49. def update_hapic_data(
  50. self,
  51. hapic_data: HapicData,
  52. processed_data: typing.Dict[str, typing.Any],
  53. ) -> typing.Any:
  54. hapic_data.query = processed_data
  55. class MySchema(marshmallow.Schema):
  56. name = marshmallow.fields.String(required=True)
  57. class TestControllerWrapper(Base):
  58. def test_unit__base_controller_wrapper__ok__no_behaviour(self):
  59. context = MyContext()
  60. processor = MyProcessor()
  61. wrapper = InputOutputControllerWrapper(context, processor)
  62. @wrapper.get_wrapper
  63. def func(foo):
  64. return foo
  65. result = func(42)
  66. assert result == 42
  67. def test_unit__base_controller__ok__replaced_response(self):
  68. context = MyContext()
  69. processor = MyProcessor()
  70. wrapper = MyControllerWrapper(context, processor)
  71. @wrapper.get_wrapper
  72. def func(foo):
  73. return foo
  74. # see MyControllerWrapper#before_wrapped_func
  75. result = func(666)
  76. # result have been replaced by MyControllerWrapper#before_wrapped_func
  77. assert {'error_response': 'we are testing'} == result
  78. def test_unit__controller_wrapper__ok__overload_input(self):
  79. context = MyContext()
  80. processor = MyProcessor()
  81. wrapper = MyControllerWrapper(context, processor)
  82. @wrapper.get_wrapper
  83. def func(foo, added_parameter=None):
  84. # see MyControllerWrapper#before_wrapped_func
  85. assert added_parameter == 'a value'
  86. return foo
  87. result = func(42)
  88. # See MyControllerWrapper#after_wrapped_function
  89. assert result == 84
  90. class TestInputControllerWrapper(Base):
  91. def test_unit__input_data_wrapping__ok__nominal_case(self):
  92. context = MyContext(fake_query_parameters={
  93. 'foo': 'bar',
  94. })
  95. processor = MyProcessor()
  96. wrapper = MyInputQueryControllerWrapper(context, processor)
  97. @wrapper.get_wrapper
  98. def func(foo, hapic_data=None):
  99. assert hapic_data
  100. assert isinstance(hapic_data, HapicData)
  101. # see MyControllerWrapper#before_wrapped_func
  102. assert hapic_data.query == {'foo': 'bar'}
  103. return foo
  104. result = func(42)
  105. assert result == 42
  106. class TestOutputControllerWrapper(Base):
  107. def test_unit__output_data_wrapping__ok__nominal_case(self):
  108. context = MyContext()
  109. processor = MyProcessor()
  110. wrapper = OutputControllerWrapper(context, processor)
  111. @wrapper.get_wrapper
  112. def func(foo, hapic_data=None):
  113. # If no use of input wrapper, no hapic_data is given
  114. assert not hapic_data
  115. return foo
  116. result = func(42)
  117. # see MyProcessor#process
  118. assert {
  119. 'http_code': HTTPStatus.OK,
  120. 'original_response': 43,
  121. } == result
  122. def test_unit__output_data_wrapping__fail__error_response(self):
  123. context = MyContext()
  124. processor = MarshmallowOutputProcessor()
  125. processor.schema = MySchema()
  126. wrapper = OutputControllerWrapper(context, processor)
  127. @wrapper.get_wrapper
  128. def func(foo):
  129. return 'wrong result format'
  130. result = func(42)
  131. # see MyProcessor#process
  132. assert isinstance(result, dict)
  133. assert 'http_code' in result
  134. assert result['http_code'] == HTTPStatus.INTERNAL_SERVER_ERROR
  135. assert 'original_error' in result
  136. assert result['original_error'].details == {
  137. 'name': ['Missing data for required field.']
  138. }
  139. class TestExceptionHandlerControllerWrapper(Base):
  140. def test_unit__exception_handled__ok__nominal_case(self):
  141. context = MyContext()
  142. wrapper = ExceptionHandlerControllerWrapper(
  143. ZeroDivisionError,
  144. context,
  145. schema=ErrorResponseSchema(),
  146. http_code=HTTPStatus.INTERNAL_SERVER_ERROR,
  147. )
  148. @wrapper.get_wrapper
  149. def func(foo):
  150. raise ZeroDivisionError('We are testing')
  151. response = func(42)
  152. assert 'http_code' in response
  153. assert response['http_code'] == HTTPStatus.INTERNAL_SERVER_ERROR
  154. assert 'original_response' in response
  155. assert response['original_response'] == {
  156. 'message': 'We are testing',
  157. 'code': None,
  158. 'detail': {},
  159. }
  160. def test_unit__exception_handled__ok__exception_error_dict(self):
  161. class MyException(Exception):
  162. def __init__(self, *args, **kwargs):
  163. super().__init__(*args, **kwargs)
  164. self.error_dict = {}
  165. context = MyContext()
  166. wrapper = ExceptionHandlerControllerWrapper(
  167. MyException,
  168. context,
  169. schema=ErrorResponseSchema(),
  170. http_code=HTTPStatus.INTERNAL_SERVER_ERROR,
  171. )
  172. @wrapper.get_wrapper
  173. def func(foo):
  174. exc = MyException('We are testing')
  175. exc.error_detail = {'foo': 'bar'}
  176. raise exc
  177. response = func(42)
  178. assert 'http_code' in response
  179. assert response['http_code'] == HTTPStatus.INTERNAL_SERVER_ERROR
  180. assert 'original_response' in response
  181. assert response['original_response'] == {
  182. 'message': 'We are testing',
  183. 'code': None,
  184. 'detail': {'foo': 'bar'},
  185. }