test_decorator.py 7.6KB

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