test_decorator.py 9.3KB

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