test_aiohttp.py 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. # coding: utf-8
  2. from aiohttp import web
  3. import marshmallow
  4. from hapic import Hapic
  5. from hapic import HapicData
  6. from hapic.ext.aiohttp.context import AiohttpContext
  7. class TestAiohttpExt(object):
  8. async def test_aiohttp_only__ok__nominal_case(
  9. self,
  10. aiohttp_client,
  11. loop,
  12. ):
  13. async def hello(request):
  14. return web.Response(text='Hello, world')
  15. app = web.Application(debug=True)
  16. app.router.add_get('/', hello)
  17. client = await aiohttp_client(app)
  18. resp = await client.get('/')
  19. assert resp.status == 200
  20. text = await resp.text()
  21. assert 'Hello, world' in text
  22. async def test_aiohttp_input_path__ok__nominal_case(
  23. self,
  24. aiohttp_client,
  25. loop,
  26. ):
  27. hapic = Hapic(async_=True)
  28. class InputPathSchema(marshmallow.Schema):
  29. name = marshmallow.fields.String()
  30. @hapic.input_path(InputPathSchema())
  31. async def hello(request, hapic_data: HapicData):
  32. name = hapic_data.path.get('name')
  33. return web.Response(text='Hello, {}'.format(name))
  34. app = web.Application(debug=True)
  35. app.router.add_get('/{name}', hello)
  36. hapic.set_context(AiohttpContext(app))
  37. client = await aiohttp_client(app)
  38. resp = await client.get('/bob')
  39. assert resp.status == 200
  40. text = await resp.text()
  41. assert 'Hello, bob' in text
  42. async def test_aiohttp_input_path__error_wrong_input_parameter(
  43. self,
  44. aiohttp_client,
  45. loop,
  46. ):
  47. hapic = Hapic(async_=True)
  48. class InputPathSchema(marshmallow.Schema):
  49. i = marshmallow.fields.Integer()
  50. @hapic.input_path(InputPathSchema())
  51. async def hello(request, hapic_data: HapicData):
  52. i = hapic_data.path.get('i')
  53. return web.Response(text='integer: {}'.format(str(i)))
  54. app = web.Application(debug=True)
  55. app.router.add_get('/{i}', hello)
  56. hapic.set_context(AiohttpContext(app))
  57. client = await aiohttp_client(app)
  58. resp = await client.get('/bob') # NOTE: should be integer here
  59. assert resp.status == 400
  60. error = await resp.json()
  61. assert 'Validation error of input data' in error.get('message')
  62. assert {'i': ['Not a valid integer.']} == error.get('details')
  63. async def test_aiohttp_input_body__ok_nominal_case(
  64. self,
  65. aiohttp_client,
  66. loop,
  67. ):
  68. hapic = Hapic(async_=True)
  69. class InputBodySchema(marshmallow.Schema):
  70. name = marshmallow.fields.String()
  71. @hapic.input_body(InputBodySchema())
  72. async def hello(request, hapic_data: HapicData):
  73. name = hapic_data.body.get('name')
  74. return web.Response(text='Hello, {}'.format(name))
  75. app = web.Application(debug=True)
  76. app.router.add_post('/', hello)
  77. hapic.set_context(AiohttpContext(app))
  78. client = await aiohttp_client(app)
  79. resp = await client.post('/', data={'name': 'bob'})
  80. assert resp.status == 200
  81. text = await resp.text()
  82. assert 'Hello, bob' in text
  83. async def test_aiohttp_input_body__error__incorrect_input_body(
  84. self,
  85. aiohttp_client,
  86. loop,
  87. ):
  88. hapic = Hapic(async_=True)
  89. class InputBodySchema(marshmallow.Schema):
  90. i = marshmallow.fields.Integer()
  91. @hapic.input_body(InputBodySchema())
  92. async def hello(request, hapic_data: HapicData):
  93. i = hapic_data.body.get('i')
  94. return web.Response(text='integer, {}'.format(i))
  95. app = web.Application(debug=True)
  96. app.router.add_post('/', hello)
  97. hapic.set_context(AiohttpContext(app))
  98. client = await aiohttp_client(app)
  99. resp = await client.post('/', data={'i': 'bob'}) # NOTE: should be int
  100. assert resp.status == 400
  101. error = await resp.json()
  102. assert 'Validation error of input data' in error.get('message')
  103. assert {'i': ['Not a valid integer.']} == error.get('details')
  104. async def test_aiohttp_output_body__ok__nominal_case(
  105. self,
  106. aiohttp_client,
  107. loop,
  108. ):
  109. hapic = Hapic(async_=True)
  110. class OuputBodySchema(marshmallow.Schema):
  111. name = marshmallow.fields.String()
  112. @hapic.output_body(OuputBodySchema())
  113. async def hello(request):
  114. return {
  115. 'name': 'bob',
  116. }
  117. app = web.Application(debug=True)
  118. app.router.add_get('/', hello)
  119. hapic.set_context(AiohttpContext(app))
  120. client = await aiohttp_client(app)
  121. resp = await client.get('/')
  122. assert resp.status == 200
  123. data = await resp.json()
  124. assert 'bob' == data.get('name')
  125. async def test_aiohttp_output_body__error__incorrect_output_body(
  126. self,
  127. aiohttp_client,
  128. loop,
  129. ):
  130. hapic = Hapic(async_=True)
  131. class OuputBodySchema(marshmallow.Schema):
  132. i = marshmallow.fields.Integer(required=True)
  133. @hapic.output_body(OuputBodySchema())
  134. async def hello(request):
  135. return {
  136. 'i': 'bob', # NOTE: should be integer
  137. }
  138. app = web.Application(debug=True)
  139. app.router.add_get('/', hello)
  140. hapic.set_context(AiohttpContext(app))
  141. client = await aiohttp_client(app)
  142. resp = await client.get('/')
  143. assert resp.status == 500
  144. data = await resp.json()
  145. assert 'Validation error of output data' == data.get('message')
  146. assert {
  147. 'i': ['Missing data for required field.'],
  148. } == data.get('details')
  149. async def test_aiohttp_handle_excpetion__ok__nominal_case(
  150. self,
  151. aiohttp_client,
  152. loop,
  153. ):
  154. hapic = Hapic(async_=True)
  155. @hapic.handle_exception(ZeroDivisionError, http_code=400)
  156. async def hello(request):
  157. 1 / 0
  158. app = web.Application(debug=True)
  159. app.router.add_get('/', hello)
  160. hapic.set_context(AiohttpContext(app))
  161. client = await aiohttp_client(app)
  162. resp = await client.get('/')
  163. assert resp.status == 400
  164. data = await resp.json()
  165. assert 'division by zero' == data.get('message')
  166. async def test_aiohttp_output_stream__ok__nominal_case(
  167. self,
  168. aiohttp_client,
  169. loop,
  170. ):
  171. hapic = Hapic(async_=True)
  172. class AsyncGenerator:
  173. def __init__(self):
  174. self._iterator = iter([
  175. {'name': 'Hello, bob'},
  176. {'name': 'Hello, franck'},
  177. ])
  178. async def __aiter__(self):
  179. return self
  180. async def __anext__(self):
  181. return next(self._iterator)
  182. class OuputStreamItemSchema(marshmallow.Schema):
  183. name = marshmallow.fields.String()
  184. @hapic.output_stream(OuputStreamItemSchema())
  185. async def hello(request):
  186. return AsyncGenerator()
  187. app = web.Application(debug=True)
  188. app.router.add_get('/', hello)
  189. hapic.set_context(AiohttpContext(app))
  190. client = await aiohttp_client(app)
  191. resp = await client.get('/')
  192. assert resp.status == 200
  193. line = await resp.content.readline()
  194. assert b'{"name": "Hello, bob"}\n' == line
  195. line = await resp.content.readline()
  196. assert b'{"name": "Hello, franck"}\n' == line
  197. # TODO BS 2018-07-26: How to ensure we are at end of response ?
  198. def test_unit__generate_doc__ok__nominal_case(
  199. self,
  200. aiohttp_client,
  201. loop,
  202. ):
  203. hapic = Hapic(async_=True)
  204. class InputPathSchema(marshmallow.Schema):
  205. username = marshmallow.fields.String(required=True)
  206. class InputQuerySchema(marshmallow.Schema):
  207. show_deleted = marshmallow.fields.Boolean(required=False)
  208. class UserSchema(marshmallow.Schema):
  209. name = marshmallow.fields.String(required=True)
  210. @hapic.with_api_doc()
  211. @hapic.input_path(InputPathSchema())
  212. @hapic.input_query(InputQuerySchema())
  213. @hapic.output_body(UserSchema())
  214. async def get_user(request, hapic_data):
  215. pass
  216. @hapic.with_api_doc()
  217. @hapic.input_path(InputPathSchema())
  218. @hapic.output_body(UserSchema())
  219. async def post_user(request, hapic_data):
  220. pass
  221. app = web.Application(debug=True)
  222. app.router.add_get('/{username}', get_user)
  223. app.router.add_post('/{username}', post_user)
  224. hapic.set_context(AiohttpContext(app))
  225. doc = hapic.generate_doc('aiohttp', 'testing')
  226. assert 'UserSchema' in doc.get('definitions')
  227. assert {
  228. 'name': {'type': 'string'}
  229. } == doc['definitions']['UserSchema'].get('properties')
  230. assert '/{username}' in doc.get('paths')
  231. assert 'get' in doc['paths']['/{username}']
  232. assert 'post' in doc['paths']['/{username}']
  233. assert [
  234. {
  235. 'name': 'username',
  236. 'in': 'path',
  237. 'required': True,
  238. 'type': 'string',
  239. },
  240. {
  241. 'name': 'show_deleted',
  242. 'in': 'query',
  243. 'required': False,
  244. 'type': 'boolean',
  245. }
  246. ] == doc['paths']['/{username}']['get']['parameters']
  247. assert {
  248. 200: {
  249. 'schema': {
  250. '$ref': '#/definitions/UserSchema',
  251. },
  252. 'description': '200',
  253. }
  254. } == doc['paths']['/{username}']['get']['responses']
  255. assert [
  256. {
  257. 'name': 'username',
  258. 'in': 'path',
  259. 'required': True,
  260. 'type': 'string',
  261. }
  262. ] == doc['paths']['/{username}']['post']['parameters']
  263. assert {
  264. 200: {
  265. 'schema': {
  266. '$ref': '#/definitions/UserSchema',
  267. },
  268. 'description': '200',
  269. }
  270. } == doc['paths']['/{username}']['get']['responses']