test_aiohttp.py 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  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. async def test_aiohttp_output_stream__error__ignore(
  181. self,
  182. aiohttp_client,
  183. loop,
  184. ):
  185. hapic = Hapic(async_=True)
  186. class AsyncGenerator:
  187. def __init__(self):
  188. self._iterator = iter([
  189. {'name': 'Hello, bob'},
  190. {'nameZ': 'Hello, Z'}, # This line is incorrect
  191. {'name': 'Hello, franck'},
  192. ])
  193. async def __aiter__(self):
  194. return self
  195. async def __anext__(self):
  196. return next(self._iterator)
  197. class OuputStreamItemSchema(marshmallow.Schema):
  198. name = marshmallow.fields.String(required=True)
  199. @hapic.output_stream(OuputStreamItemSchema(), ignore_on_error=True)
  200. async def hello(request):
  201. return AsyncGenerator()
  202. app = web.Application(debug=True)
  203. app.router.add_get('/', hello)
  204. hapic.set_context(AiohttpContext(app))
  205. client = await aiohttp_client(app)
  206. resp = await client.get('/')
  207. assert resp.status == 200
  208. line = await resp.content.readline()
  209. assert b'{"name": "Hello, bob"}\n' == line
  210. line = await resp.content.readline()
  211. assert b'{"name": "Hello, franck"}\n' == line
  212. async def test_aiohttp_output_stream__error__interrupt(
  213. self,
  214. aiohttp_client,
  215. loop,
  216. ):
  217. hapic = Hapic(async_=True)
  218. class AsyncGenerator:
  219. def __init__(self):
  220. self._iterator = iter([
  221. {'name': 'Hello, bob'},
  222. {'nameZ': 'Hello, Z'}, # This line is incorrect
  223. {'name': 'Hello, franck'}, # This line must not be reached
  224. ])
  225. async def __aiter__(self):
  226. return self
  227. async def __anext__(self):
  228. return next(self._iterator)
  229. class OuputStreamItemSchema(marshmallow.Schema):
  230. name = marshmallow.fields.String(required=True)
  231. @hapic.output_stream(OuputStreamItemSchema(), ignore_on_error=False)
  232. async def hello(request):
  233. return AsyncGenerator()
  234. app = web.Application(debug=True)
  235. app.router.add_get('/', hello)
  236. hapic.set_context(AiohttpContext(app))
  237. client = await aiohttp_client(app)
  238. resp = await client.get('/')
  239. assert resp.status == 200
  240. line = await resp.content.readline()
  241. assert b'{"name": "Hello, bob"}\n' == line
  242. line = await resp.content.readline()
  243. assert b'' == line
  244. def test_unit__generate_doc__ok__nominal_case(
  245. self,
  246. aiohttp_client,
  247. loop,
  248. ):
  249. hapic = Hapic(async_=True)
  250. class InputPathSchema(marshmallow.Schema):
  251. username = marshmallow.fields.String(required=True)
  252. class InputQuerySchema(marshmallow.Schema):
  253. show_deleted = marshmallow.fields.Boolean(required=False)
  254. class UserSchema(marshmallow.Schema):
  255. name = marshmallow.fields.String(required=True)
  256. @hapic.with_api_doc()
  257. @hapic.input_path(InputPathSchema())
  258. @hapic.input_query(InputQuerySchema())
  259. @hapic.output_body(UserSchema())
  260. async def get_user(request, hapic_data):
  261. pass
  262. @hapic.with_api_doc()
  263. @hapic.input_path(InputPathSchema())
  264. @hapic.output_body(UserSchema())
  265. async def post_user(request, hapic_data):
  266. pass
  267. app = web.Application(debug=True)
  268. app.router.add_get('/{username}', get_user)
  269. app.router.add_post('/{username}', post_user)
  270. hapic.set_context(AiohttpContext(app))
  271. doc = hapic.generate_doc('aiohttp', 'testing')
  272. assert 'UserSchema' in doc.get('definitions')
  273. assert {
  274. 'name': {'type': 'string'}
  275. } == doc['definitions']['UserSchema'].get('properties')
  276. assert '/{username}' in doc.get('paths')
  277. assert 'get' in doc['paths']['/{username}']
  278. assert 'post' in doc['paths']['/{username}']
  279. assert [
  280. {
  281. 'name': 'username',
  282. 'in': 'path',
  283. 'required': True,
  284. 'type': 'string',
  285. },
  286. {
  287. 'name': 'show_deleted',
  288. 'in': 'query',
  289. 'required': False,
  290. 'type': 'boolean',
  291. }
  292. ] == doc['paths']['/{username}']['get']['parameters']
  293. assert {
  294. 200: {
  295. 'schema': {
  296. '$ref': '#/definitions/UserSchema',
  297. },
  298. 'description': '200',
  299. }
  300. } == doc['paths']['/{username}']['get']['responses']
  301. assert [
  302. {
  303. 'name': 'username',
  304. 'in': 'path',
  305. 'required': True,
  306. 'type': 'string',
  307. }
  308. ] == doc['paths']['/{username}']['post']['parameters']
  309. assert {
  310. 200: {
  311. 'schema': {
  312. '$ref': '#/definitions/UserSchema',
  313. },
  314. 'description': '200',
  315. }
  316. } == doc['paths']['/{username}']['get']['responses']
  317. def test_unit__generate_output_stream_doc__ok__nominal_case(
  318. self,
  319. aiohttp_client,
  320. loop,
  321. ):
  322. hapic = Hapic(async_=True)
  323. class OuputStreamItemSchema(marshmallow.Schema):
  324. name = marshmallow.fields.String(required=True)
  325. @hapic.with_api_doc()
  326. @hapic.output_stream(OuputStreamItemSchema())
  327. async def get_users(request, hapic_data):
  328. pass
  329. app = web.Application(debug=True)
  330. app.router.add_get('/', get_users)
  331. hapic.set_context(AiohttpContext(app))
  332. doc = hapic.generate_doc('aiohttp', 'testing')
  333. assert '/' in doc.get('paths')
  334. assert 'get' in doc['paths']['/']
  335. assert 200 in doc['paths']['/']['get'].get('responses', {})
  336. assert {
  337. 'items': {
  338. '$ref': '#/definitions/OuputStreamItemSchema'
  339. },
  340. 'type': 'array',
  341. } == doc['paths']['/']['get']['responses'][200]['schema']