test_aiohttp.py 9.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  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_output_stream__ok__nominal_case(
  150. self,
  151. aiohttp_client,
  152. loop,
  153. ):
  154. hapic = Hapic(async_=True)
  155. class AsyncGenerator:
  156. def __init__(self):
  157. self._iterator = iter([
  158. {'name': 'Hello, bob'},
  159. {'name': 'Hello, franck'},
  160. ])
  161. async def __aiter__(self):
  162. return self
  163. async def __anext__(self):
  164. return next(self._iterator)
  165. class OuputStreamItemSchema(marshmallow.Schema):
  166. name = marshmallow.fields.String()
  167. @hapic.output_stream(OuputStreamItemSchema())
  168. async def hello(request):
  169. return AsyncGenerator()
  170. app = web.Application(debug=True)
  171. app.router.add_get('/', hello)
  172. hapic.set_context(AiohttpContext(app))
  173. client = await aiohttp_client(app)
  174. resp = await client.get('/')
  175. assert resp.status == 200
  176. line = await resp.content.readline()
  177. assert b'{"name": "Hello, bob"}\n' == line
  178. line = await resp.content.readline()
  179. assert b'{"name": "Hello, franck"}\n' == line
  180. # TODO BS 2018-07-26: How to ensure we are at end of response ?
  181. def test_unit__generate_doc__ok__nominal_case(
  182. self,
  183. aiohttp_client,
  184. loop,
  185. ):
  186. hapic = Hapic(async_=True)
  187. class InputPathSchema(marshmallow.Schema):
  188. username = marshmallow.fields.String(required=True)
  189. class InputQuerySchema(marshmallow.Schema):
  190. show_deleted = marshmallow.fields.Boolean(required=False)
  191. class UserSchema(marshmallow.Schema):
  192. name = marshmallow.fields.String(required=True)
  193. @hapic.with_api_doc()
  194. @hapic.input_path(InputPathSchema())
  195. @hapic.input_query(InputQuerySchema())
  196. @hapic.output_body(UserSchema())
  197. async def get_user(request, hapic_data):
  198. pass
  199. @hapic.with_api_doc()
  200. @hapic.input_path(InputPathSchema())
  201. @hapic.output_body(UserSchema())
  202. async def post_user(request, hapic_data):
  203. pass
  204. app = web.Application(debug=True)
  205. app.router.add_get('/{username}', get_user)
  206. app.router.add_post('/{username}', post_user)
  207. hapic.set_context(AiohttpContext(app))
  208. doc = hapic.generate_doc('aiohttp', 'testing')
  209. assert 'UserSchema' in doc.get('definitions')
  210. assert {
  211. 'name': {'type': 'string'}
  212. } == doc['definitions']['UserSchema'].get('properties')
  213. assert '/{username}' in doc.get('paths')
  214. assert 'get' in doc['paths']['/{username}']
  215. assert 'post' in doc['paths']['/{username}']
  216. assert [
  217. {
  218. 'name': 'username',
  219. 'in': 'path',
  220. 'required': True,
  221. 'type': 'string',
  222. },
  223. {
  224. 'name': 'show_deleted',
  225. 'in': 'query',
  226. 'required': False,
  227. 'type': 'boolean',
  228. }
  229. ] == doc['paths']['/{username}']['get']['parameters']
  230. assert {
  231. 200: {
  232. 'schema': {
  233. '$ref': '#/definitions/UserSchema',
  234. },
  235. 'description': '200',
  236. }
  237. } == doc['paths']['/{username}']['get']['responses']
  238. assert [
  239. {
  240. 'name': 'username',
  241. 'in': 'path',
  242. 'required': True,
  243. 'type': 'string',
  244. }
  245. ] == doc['paths']['/{username}']['post']['parameters']
  246. assert {
  247. 200: {
  248. 'schema': {
  249. '$ref': '#/definitions/UserSchema',
  250. },
  251. 'description': '200',
  252. }
  253. } == doc['paths']['/{username}']['get']['responses']