test_doc.py 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  1. # coding: utf-8
  2. import marshmallow
  3. import bottle
  4. from marshmallow.validate import OneOf
  5. from hapic import Hapic
  6. from tests.base import Base
  7. from tests.base import MyContext
  8. class TestDocGeneration(Base):
  9. def test_func__input_files_doc__ok__one_file(self):
  10. hapic = Hapic()
  11. # TODO BS 20171113: Make this test non-bottle
  12. app = bottle.Bottle()
  13. hapic.set_context(MyContext(app=app))
  14. class MySchema(marshmallow.Schema):
  15. file_abc = marshmallow.fields.Raw(required=True)
  16. @hapic.with_api_doc()
  17. @hapic.input_files(MySchema())
  18. def my_controller(hapic_data=None):
  19. assert hapic_data
  20. assert hapic_data.files
  21. app.route('/upload', method='POST', callback=my_controller)
  22. doc = hapic.generate_doc()
  23. assert doc
  24. assert '/upload' in doc['paths']
  25. assert 'consumes' in doc['paths']['/upload']['post']
  26. assert 'multipart/form-data' in doc['paths']['/upload']['post']['consumes'] # nopep8
  27. assert 'parameters' in doc['paths']['/upload']['post']
  28. assert {
  29. 'name': 'file_abc',
  30. 'required': True,
  31. 'in': 'formData',
  32. 'type': 'file',
  33. } in doc['paths']['/upload']['post']['parameters']
  34. def test_func__input_files_doc__ok__two_file(self):
  35. hapic = Hapic()
  36. # TODO BS 20171113: Make this test non-bottle
  37. app = bottle.Bottle()
  38. hapic.set_context(MyContext(app=app))
  39. class MySchema(marshmallow.Schema):
  40. file_abc = marshmallow.fields.Raw(required=True)
  41. file_def = marshmallow.fields.Raw(required=False)
  42. @hapic.with_api_doc()
  43. @hapic.input_files(MySchema())
  44. def my_controller(hapic_data=None):
  45. assert hapic_data
  46. assert hapic_data.files
  47. app.route('/upload', method='POST', callback=my_controller)
  48. doc = hapic.generate_doc()
  49. assert doc
  50. assert '/upload' in doc['paths']
  51. assert 'consumes' in doc['paths']['/upload']['post']
  52. assert 'multipart/form-data' in doc['paths']['/upload']['post']['consumes'] # nopep8
  53. assert 'parameters' in doc['paths']['/upload']['post']
  54. assert {
  55. 'name': 'file_abc',
  56. 'required': True,
  57. 'in': 'formData',
  58. 'type': 'file',
  59. } in doc['paths']['/upload']['post']['parameters']
  60. assert {
  61. 'name': 'file_def',
  62. 'required': False,
  63. 'in': 'formData',
  64. 'type': 'file',
  65. } in doc['paths']['/upload']['post']['parameters']
  66. def test_func__output_file_doc__ok__nominal_case(self):
  67. hapic = Hapic()
  68. # TODO BS 20171113: Make this test non-bottle
  69. app = bottle.Bottle()
  70. hapic.set_context(MyContext(app=app))
  71. @hapic.with_api_doc()
  72. @hapic.output_file(['image/jpeg'])
  73. def my_controller():
  74. return b'101010100101'
  75. app.route('/avatar', method='GET', callback=my_controller)
  76. doc = hapic.generate_doc()
  77. assert doc
  78. assert '/avatar' in doc['paths']
  79. assert 'produces' in doc['paths']['/avatar']['get']
  80. assert 'image/jpeg' in doc['paths']['/avatar']['get']['produces']
  81. assert 200 in doc['paths']['/avatar']['get']['responses']
  82. def test_func__input_files_doc__ok__one_file_and_text(self):
  83. hapic = Hapic()
  84. # TODO BS 20171113: Make this test non-bottle
  85. app = bottle.Bottle()
  86. hapic.set_context(MyContext(app=app))
  87. class MySchema(marshmallow.Schema):
  88. name = marshmallow.fields.String(required=True)
  89. class MyFilesSchema(marshmallow.Schema):
  90. file_abc = marshmallow.fields.Raw(required=True)
  91. @hapic.with_api_doc()
  92. @hapic.input_files(MyFilesSchema())
  93. @hapic.input_body(MySchema())
  94. def my_controller(hapic_data=None):
  95. assert hapic_data
  96. assert hapic_data.files
  97. app.route('/upload', method='POST', callback=my_controller)
  98. doc = hapic.generate_doc()
  99. assert doc
  100. assert '/upload' in doc['paths']
  101. assert 'consumes' in doc['paths']['/upload']['post']
  102. assert 'multipart/form-data' in doc['paths']['/upload']['post']['consumes'] # nopep8
  103. assert 'parameters' in doc['paths']['/upload']['post']
  104. assert {
  105. 'name': 'file_abc',
  106. 'required': True,
  107. 'in': 'formData',
  108. 'type': 'file',
  109. } in doc['paths']['/upload']['post']['parameters']
  110. def test_func__docstring__ok__simple_case(self):
  111. hapic = Hapic()
  112. app = bottle.Bottle()
  113. hapic.set_context(MyContext(app=app))
  114. # TODO BS 20171113: Make this test non-bottle
  115. @hapic.with_api_doc()
  116. def my_controller(hapic_data=None):
  117. """
  118. Hello doc
  119. """
  120. assert hapic_data
  121. assert hapic_data.files
  122. app.route('/upload', method='POST', callback=my_controller)
  123. doc = hapic.generate_doc()
  124. assert doc.get('paths')
  125. assert '/upload' in doc['paths']
  126. assert 'post' in doc['paths']['/upload']
  127. assert 'description' in doc['paths']['/upload']['post']
  128. assert 'Hello doc' == doc['paths']['/upload']['post']['description']
  129. def test_func__tags__ok__nominal_case(self):
  130. hapic = Hapic()
  131. app = bottle.Bottle()
  132. hapic.set_context(MyContext(app=app))
  133. @hapic.with_api_doc(tags=['foo', 'bar'])
  134. def my_controller(hapic_data=None):
  135. assert hapic_data
  136. assert hapic_data.files
  137. app.route('/upload', method='POST', callback=my_controller)
  138. doc = hapic.generate_doc()
  139. assert doc.get('paths')
  140. assert '/upload' in doc['paths']
  141. assert 'post' in doc['paths']['/upload']
  142. assert 'tags' in doc['paths']['/upload']['post']
  143. assert ['foo', 'bar'] == doc['paths']['/upload']['post']['tags']
  144. def test_func__errors__nominal_case(self):
  145. hapic = Hapic()
  146. app = bottle.Bottle()
  147. hapic.set_context(MyContext(app=app))
  148. @hapic.with_api_doc()
  149. @hapic.handle_exception()
  150. def my_controller(hapic_data=None):
  151. assert hapic_data
  152. app.route('/upload', method='POST', callback=my_controller)
  153. doc = hapic.generate_doc()
  154. assert doc.get('paths')
  155. assert '/upload' in doc['paths']
  156. assert 'post' in doc['paths']['/upload']
  157. assert 'responses' in doc['paths']['/upload']['post']
  158. assert 500 in doc['paths']['/upload']['post']['responses']
  159. assert {
  160. 'description': "500",
  161. 'schema': {
  162. '$ref': '#/definitions/DefaultErrorBuilder'
  163. }
  164. } == doc['paths']['/upload']['post']['responses'][500]
  165. def test_func__enum__nominal_case(self):
  166. hapic = Hapic()
  167. # TODO BS 20171113: Make this test non-bottle
  168. app = bottle.Bottle()
  169. hapic.set_context(MyContext(app=app))
  170. class MySchema(marshmallow.Schema):
  171. category = marshmallow.fields.String(
  172. validate=OneOf(['foo', 'bar'])
  173. )
  174. @hapic.with_api_doc()
  175. @hapic.input_body(MySchema())
  176. def my_controller():
  177. return
  178. app.route('/paper', method='POST', callback=my_controller)
  179. doc = hapic.generate_doc()
  180. assert ['foo', 'bar'] == doc.get('definitions', {})\
  181. .get('MySchema', {})\
  182. .get('properties', {})\
  183. .get('category', {})\
  184. .get('enum')
  185. def test_func__schema_in_doc__ok__nominal_case(self):
  186. hapic = Hapic()
  187. app = bottle.Bottle()
  188. hapic.set_context(MyContext(app=app))
  189. class MySchema(marshmallow.Schema):
  190. name = marshmallow.fields.String(required=True)
  191. @hapic.with_api_doc()
  192. @hapic.input_body(MySchema())
  193. def my_controller():
  194. return {'name': 'test',}
  195. app.route('/paper', method='POST', callback=my_controller)
  196. doc = hapic.generate_doc()
  197. assert doc.get('definitions', {}).get('MySchema', {})
  198. schema_def = doc.get('definitions', {}).get('MySchema', {})
  199. assert schema_def.get('properties', {}).get('name', {}).get('type')
  200. assert doc.get('paths').get('/paper').get('post').get('parameters')[0]
  201. schema_ref = doc.get('paths').get('/paper').get('post').get('parameters')[0]
  202. assert schema_ref.get('in') == 'body'
  203. assert schema_ref.get('name') == 'body'
  204. assert schema_ref['schema']['$ref'] == '#/definitions/MySchema'
  205. def test_func__schema_in_doc__ok__many_case(self):
  206. hapic = Hapic()
  207. app = bottle.Bottle()
  208. hapic.set_context(MyContext(app=app))
  209. class MySchema(marshmallow.Schema):
  210. name = marshmallow.fields.String(required=True)
  211. @hapic.with_api_doc()
  212. @hapic.input_body(MySchema(many=True))
  213. def my_controller():
  214. return {'name': 'test'}
  215. app.route('/paper', method='POST', callback=my_controller)
  216. doc = hapic.generate_doc()
  217. assert doc.get('definitions', {}).get('MySchema', {})
  218. schema_def = doc.get('definitions', {}).get('MySchema', {})
  219. assert schema_def.get('properties', {}).get('name', {}).get('type')
  220. assert doc.get('paths').get('/paper').get('post').get('parameters')[0]
  221. schema_ref = doc.get('paths').get('/paper').get('post').get('parameters')[0]
  222. assert schema_ref.get('in') == 'body'
  223. assert schema_ref.get('name') == 'body'
  224. assert schema_ref['schema'] == {
  225. 'items': {'$ref': '#/definitions/MySchema'},
  226. 'type': 'array'
  227. }
  228. def test_func__schema_in_doc__ok__exclude_case(self):
  229. hapic = Hapic()
  230. app = bottle.Bottle()
  231. hapic.set_context(MyContext(app=app))
  232. class MySchema(marshmallow.Schema):
  233. name = marshmallow.fields.String(required=True)
  234. name2 = marshmallow.fields.String(required=True)
  235. @hapic.with_api_doc()
  236. @hapic.input_body(MySchema(exclude=('name2',)))
  237. def my_controller():
  238. return {'name': 'test',}
  239. app.route('/paper', method='POST', callback=my_controller)
  240. doc = hapic.generate_doc()
  241. definitions = doc.get('definitions', {})
  242. # TODO - G-M - Find better way to find our new schema
  243. # Do Better test when we were able to set correctly schema name
  244. # according to content
  245. schema_name = None
  246. for elem in definitions.keys():
  247. if elem != 'MySchema':
  248. schema_name = elem
  249. break
  250. assert schema_name
  251. schema_def = definitions[schema_name]
  252. assert schema_def.get('properties', {}).get('name', {}).get('type') == 'string'
  253. assert doc.get('paths').get('/paper').get('post').get('parameters')[0]
  254. schema_ref = doc.get('paths').get('/paper').get('post').get('parameters')[0]
  255. assert schema_ref.get('in') == 'body'
  256. assert schema_ref['schema']['$ref'] == '#/definitions/{}'.format(schema_name)
  257. @hapic.with_api_doc()
  258. @hapic.input_body(MySchema(only=('name',)))
  259. def my_controller():
  260. return {'name': 'test'}
  261. app.route('/paper', method='POST', callback=my_controller)
  262. doc = hapic.generate_doc()
  263. # TODO - G-M - Find better way to find our new schema
  264. # Do Better test when we were able to set correctly schema name
  265. # according to content
  266. definitions = doc.get('definitions', {})
  267. schema_name = None
  268. for elem in definitions.keys():
  269. if elem != 'MySchema':
  270. schema_name = elem
  271. break
  272. assert schema_name
  273. schema_def = definitions[schema_name]
  274. assert schema_def.get('properties', {}).get('name', {}).get('type') == 'string'
  275. assert doc.get('paths').get('/paper').get('post').get('parameters')[0]
  276. schema_ref = doc.get('paths').get('/paper').get('post').get('parameters')[0]
  277. assert schema_ref.get('in') == 'body'
  278. assert schema_ref['schema']['$ref'] == '#/definitions/{}'.format(schema_name)
  279. def test_func__schema_in_doc__ok__many_and_exclude_case(self):
  280. hapic = Hapic()
  281. app = bottle.Bottle()
  282. hapic.set_context(MyContext(app=app))
  283. class MySchema(marshmallow.Schema):
  284. name = marshmallow.fields.String(required=True)
  285. name2 = marshmallow.fields.String(required=True)
  286. @hapic.with_api_doc()
  287. @hapic.input_body(MySchema(exclude=('name2',), many=True))
  288. def my_controller():
  289. return {'name': 'test',}
  290. app.route('/paper', method='POST', callback=my_controller)
  291. doc = hapic.generate_doc()
  292. definitions = doc.get('definitions', {})
  293. # TODO - G-M - Find better way to find our new schema
  294. # Do Better test when we were able to set correctly schema name
  295. # according to content
  296. schema_name = None
  297. for elem in definitions.keys():
  298. if elem != 'MySchema':
  299. schema_name = elem
  300. break
  301. assert schema_name
  302. schema_def = definitions[schema_name]
  303. assert schema_def.get('properties', {}).get('name', {}).get('type') == 'string'
  304. assert doc.get('paths').get('/paper').get('post').get('parameters')[0]
  305. schema_ref = doc.get('paths').get('/paper').get('post').get('parameters')[0]
  306. assert schema_ref.get('in') == 'body'
  307. assert schema_ref['schema'] == {
  308. 'items': {'$ref': '#/definitions/{}'.format(schema_name)},
  309. 'type': 'array'
  310. }
  311. def test_func_schema_in_doc__ok__additionals_fields__query__string(self):
  312. hapic = Hapic()
  313. # TODO BS 20171113: Make this test non-bottle
  314. app = bottle.Bottle()
  315. hapic.set_context(MyContext(app=app))
  316. class MySchema(marshmallow.Schema):
  317. category = marshmallow.fields.String(
  318. required=True,
  319. description='a description',
  320. example='00010',
  321. format='binary',
  322. enum=['01000', '11111'],
  323. maxLength=5,
  324. minLength=5,
  325. # Theses none string specific parameters should disappear
  326. # in query/path
  327. maximum=400,
  328. # exclusiveMaximun=False,
  329. # minimum=0,
  330. # exclusiveMinimum=True,
  331. # multipleOf=1,
  332. )
  333. @hapic.with_api_doc()
  334. @hapic.input_query(MySchema())
  335. def my_controller():
  336. return
  337. app.route('/paper', method='POST', callback=my_controller)
  338. doc = hapic.generate_doc()
  339. assert doc.get('paths').get('/paper').get('post').get('parameters')[0]
  340. field = doc.get('paths').get('/paper').get('post').get('parameters')[0]
  341. assert field['description'] == 'a description\n\n*example value: 00010*'
  342. # INFO - G.M - 01-06-2018 - Field example not allowed here,
  343. # added in description instead
  344. assert 'example' not in field
  345. assert field['format'] == 'binary'
  346. assert field['in'] == 'query'
  347. assert field['type'] == 'string'
  348. assert field['maxLength'] == 5
  349. assert field['minLength'] == 5
  350. assert field['required'] == True
  351. assert field['enum'] == ['01000', '11111']
  352. assert 'maximum' not in field
  353. def test_func_schema_in_doc__ok__additionals_fields__path__string(self):
  354. hapic = Hapic()
  355. # TODO BS 20171113: Make this test non-bottle
  356. app = bottle.Bottle()
  357. hapic.set_context(MyContext(app=app))
  358. class MySchema(marshmallow.Schema):
  359. category = marshmallow.fields.String(
  360. required=True,
  361. description='a description',
  362. example='00010',
  363. format='binary',
  364. enum=['01000', '11111'],
  365. maxLength=5,
  366. minLength=5,
  367. # Theses none string specific parameters should disappear
  368. # in query/path
  369. maximum=400,
  370. # exclusiveMaximun=False,
  371. # minimum=0,
  372. # exclusiveMinimum=True,
  373. # multipleOf=1,
  374. )
  375. @hapic.with_api_doc()
  376. @hapic.input_path(MySchema())
  377. def my_controller():
  378. return
  379. app.route('/paper', method='POST', callback=my_controller)
  380. doc = hapic.generate_doc()
  381. assert doc.get('paths').get('/paper').get('post').get('parameters')[0]
  382. field = doc.get('paths').get('/paper').get('post').get('parameters')[0]
  383. assert field['description'] == 'a description\n\n*example value: 00010*'
  384. # INFO - G.M - 01-06-2018 - Field example not allowed here,
  385. # added in description instead
  386. assert 'example' not in field
  387. assert field['format'] == 'binary'
  388. assert field['in'] == 'path'
  389. assert field['type'] == 'string'
  390. assert field['maxLength'] == 5
  391. assert field['minLength'] == 5
  392. assert field['required'] == True
  393. assert field['enum'] == ['01000', '11111']
  394. assert 'maximum' not in field
  395. def test_func_schema_in_doc__ok__additionals_fields__path__number(self):
  396. hapic = Hapic()
  397. # TODO BS 20171113: Make this test non-bottle
  398. app = bottle.Bottle()
  399. hapic.set_context(MyContext(app=app))
  400. class MySchema(marshmallow.Schema):
  401. category = marshmallow.fields.Integer(
  402. required=True,
  403. description='a number',
  404. example='12',
  405. format='int64',
  406. enum=[4, 6],
  407. # Theses none string specific parameters should disappear
  408. # in query/path
  409. maximum=14,
  410. exclusiveMaximun=False,
  411. minimum=0,
  412. exclusiveMinimum=True,
  413. multipleOf=2,
  414. )
  415. @hapic.with_api_doc()
  416. @hapic.input_path(MySchema())
  417. def my_controller():
  418. return
  419. app.route('/paper', method='POST', callback=my_controller)
  420. doc = hapic.generate_doc()
  421. assert doc.get('paths').get('/paper').get('post').get('parameters')[0]
  422. field = doc.get('paths').get('/paper').get('post').get('parameters')[0]
  423. assert field['description'] == 'a number\n\n*example value: 12*'
  424. # INFO - G.M - 01-06-2018 - Field example not allowed here,
  425. # added in description instead
  426. assert 'example' not in field
  427. assert field['format'] == 'int64'
  428. assert field['in'] == 'path'
  429. assert field['type'] == 'integer'
  430. assert field['maximum'] == 14
  431. assert field['minimum'] == 0
  432. assert field['exclusiveMinimum'] == True
  433. assert field['required'] == True
  434. assert field['enum'] == [4, 6]
  435. assert field['multipleOf'] == 2
  436. def test_func_schema_in_doc__ok__additionals_fields__body__number(self):
  437. hapic = Hapic()
  438. # TODO BS 20171113: Make this test non-bottle
  439. app = bottle.Bottle()
  440. hapic.set_context(MyContext(app=app))
  441. class MySchema(marshmallow.Schema):
  442. category = marshmallow.fields.Integer(
  443. required=True,
  444. description='a number',
  445. example='12',
  446. format='int64',
  447. enum=[4, 6],
  448. # Theses none string specific parameters should disappear
  449. # in query/path
  450. maximum=14,
  451. exclusiveMaximun=False,
  452. minimum=0,
  453. exclusiveMinimum=True,
  454. multipleOf=2,
  455. )
  456. @hapic.with_api_doc()
  457. @hapic.input_body(MySchema())
  458. def my_controller():
  459. return
  460. app.route('/paper', method='POST', callback=my_controller)
  461. doc = hapic.generate_doc()
  462. schema_field = doc.get('definitions', {}).get('MySchema', {}).get('properties', {}).get('category', {}) # nopep8
  463. assert schema_field
  464. assert schema_field['description'] == 'a number'
  465. assert schema_field['example'] == '12'
  466. assert schema_field['format'] == 'int64'
  467. assert schema_field['type'] == 'integer'
  468. assert schema_field['maximum'] == 14
  469. assert schema_field['minimum'] == 0
  470. assert schema_field['exclusiveMinimum'] == True
  471. assert schema_field['enum'] == [4, 6]
  472. assert schema_field['multipleOf'] == 2
  473. def test_func_schema_in_doc__ok__additionals_fields__body__string(self):
  474. hapic = Hapic()
  475. # TODO BS 20171113: Make this test non-bottle
  476. app = bottle.Bottle()
  477. hapic.set_context(MyContext(app=app))
  478. class MySchema(marshmallow.Schema):
  479. category = marshmallow.fields.String(
  480. required=True,
  481. description='a description',
  482. example='00010',
  483. format='binary',
  484. enum=['01000', '11111'],
  485. maxLength=5,
  486. minLength=5,
  487. )
  488. @hapic.with_api_doc()
  489. @hapic.input_body(MySchema())
  490. def my_controller():
  491. return
  492. app.route('/paper', method='POST', callback=my_controller)
  493. doc = hapic.generate_doc()
  494. schema_field = doc.get('definitions', {}).get('MySchema', {}).get('properties', {}).get('category', {}) # nopep8
  495. assert schema_field
  496. assert schema_field['description'] == 'a description'
  497. assert schema_field['example'] == '00010'
  498. assert schema_field['format'] == 'binary'
  499. assert schema_field['type'] == 'string'
  500. assert schema_field['maxLength'] == 5
  501. assert schema_field['minLength'] == 5
  502. assert schema_field['enum'] == ['01000', '11111']