emqx_swagger_requestBody_SUITE.erl 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2022-2024 EMQ Technologies Co., Ltd. All Rights Reserved.
  3. %%
  4. %% Licensed under the Apache License, Version 2.0 (the "License");
  5. %% you may not use this file except in compliance with the License.
  6. %% You may obtain a copy of the License at
  7. %%
  8. %% http://www.apache.org/licenses/LICENSE-2.0
  9. %%
  10. %% Unless required by applicable law or agreed to in writing, software
  11. %% distributed under the License is distributed on an "AS IS" BASIS,
  12. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. %% See the License for the specific language governing permissions and
  14. %% limitations under the License.
  15. %%--------------------------------------------------------------------
  16. -module(emqx_swagger_requestBody_SUITE).
  17. -behaviour(minirest_api).
  18. -behaviour(hocon_schema).
  19. -compile(nowarn_export_all).
  20. -compile(export_all).
  21. -include_lib("eunit/include/eunit.hrl").
  22. -include_lib("typerefl/include/types.hrl").
  23. -include_lib("hocon/include/hoconsc.hrl").
  24. -import(hoconsc, [mk/2]).
  25. all() -> emqx_common_test_helpers:all(?MODULE).
  26. init_per_suite(Config) ->
  27. _ = emqx_mgmt_api_test_util:init_suite([emqx_conf]),
  28. Config.
  29. end_per_suite(_Config) ->
  30. emqx_mgmt_api_test_util:end_suite([emqx_conf]),
  31. ok.
  32. t_object(_Config) ->
  33. Spec = #{
  34. post => #{
  35. parameters => [],
  36. requestBody => #{
  37. <<"content">> =>
  38. #{
  39. <<"application/json">> =>
  40. #{
  41. <<"schema">> =>
  42. #{
  43. required => [<<"per_page">>, <<"timeout">>],
  44. <<"properties">> => [
  45. {<<"per_page">>, #{
  46. description => <<"good per page desc">>,
  47. maximum => 100,
  48. minimum => 1,
  49. type => integer
  50. }},
  51. {<<"timeout">>, #{
  52. default => 5,
  53. <<"oneOf">> =>
  54. [
  55. #{example => <<"1h">>, type => string},
  56. #{enum => [infinity], type => string}
  57. ]
  58. }},
  59. {<<"inner_ref">>, #{
  60. <<"$ref">> =>
  61. <<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>
  62. }}
  63. ],
  64. <<"type">> => object
  65. }
  66. }
  67. }
  68. },
  69. responses => #{<<"200">> => #{description => <<"ok">>}}
  70. }
  71. },
  72. Refs = [{?MODULE, good_ref}],
  73. validate("/object", Spec, Refs),
  74. ok.
  75. t_deprecated(_Config) ->
  76. ?assertMatch(
  77. [
  78. #{
  79. <<"emqx_swagger_requestBody_SUITE.deprecated_ref">> :=
  80. #{
  81. <<"properties">> :=
  82. [
  83. {<<"tag1">>, #{
  84. deprecated := true
  85. }},
  86. {<<"tag2">>, #{
  87. deprecated := true
  88. }},
  89. {<<"tag3">>, #{
  90. deprecated := false
  91. }}
  92. ]
  93. }
  94. }
  95. ],
  96. emqx_dashboard_swagger:components([{?MODULE, deprecated_ref}], #{})
  97. ).
  98. t_nonempty_list(_Config) ->
  99. ?assertMatch(
  100. [
  101. #{
  102. <<"emqx_swagger_requestBody_SUITE.nonempty_list_ref">> :=
  103. #{
  104. <<"properties">> :=
  105. [{<<"list">>, #{items := #{type := string}, type := array}}]
  106. }
  107. }
  108. ],
  109. emqx_dashboard_swagger:components([{?MODULE, nonempty_list_ref}], #{})
  110. ).
  111. t_nest_object(_Config) ->
  112. GoodRef = <<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>,
  113. Spec = #{
  114. post => #{
  115. parameters => [],
  116. requestBody => #{
  117. <<"content">> => #{
  118. <<"application/json">> =>
  119. #{
  120. <<"schema">> =>
  121. #{
  122. required => [<<"timeout">>],
  123. <<"properties">> =>
  124. [
  125. {<<"per_page">>, #{
  126. description => <<"good per page desc">>,
  127. maximum => 100,
  128. minimum => 1,
  129. type => integer
  130. }},
  131. {<<"timeout">>, #{
  132. default => 5,
  133. <<"oneOf">> =>
  134. [
  135. #{example => <<"1h">>, type => string},
  136. #{enum => [infinity], type => string}
  137. ]
  138. }},
  139. {<<"nest_object">>, #{
  140. <<"properties">> =>
  141. [
  142. {<<"good_nest_1">>, #{type => integer}},
  143. {<<"good_nest_2">>, #{
  144. <<"$ref">> => GoodRef
  145. }}
  146. ],
  147. <<"type">> => object
  148. }},
  149. {<<"inner_ref">>, #{
  150. <<"$ref">> =>
  151. <<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>
  152. }}
  153. ],
  154. <<"type">> => object
  155. }
  156. }
  157. }
  158. },
  159. responses => #{<<"200">> => #{description => <<"ok">>}}
  160. }
  161. },
  162. Refs = [{?MODULE, good_ref}],
  163. validate("/nest/object", Spec, Refs),
  164. ok.
  165. t_local_ref(_Config) ->
  166. Spec = #{
  167. post => #{
  168. parameters => [],
  169. requestBody => #{
  170. <<"content">> => #{
  171. <<"application/json">> =>
  172. #{
  173. <<"schema">> => #{
  174. <<"$ref">> =>
  175. <<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>
  176. }
  177. }
  178. }
  179. },
  180. responses => #{<<"200">> => #{description => <<"ok">>}}
  181. }
  182. },
  183. Refs = [{?MODULE, good_ref}],
  184. validate("/ref/local", Spec, Refs),
  185. ok.
  186. t_remote_ref(_Config) ->
  187. Spec = #{
  188. post => #{
  189. parameters => [],
  190. requestBody => #{
  191. <<"content">> => #{
  192. <<"application/json">> =>
  193. #{
  194. <<"schema">> => #{
  195. <<"$ref">> =>
  196. <<"#/components/schemas/emqx_swagger_remote_schema.ref2">>
  197. }
  198. }
  199. }
  200. },
  201. responses => #{<<"200">> => #{description => <<"ok">>}}
  202. }
  203. },
  204. Refs = [{emqx_swagger_remote_schema, "ref2"}],
  205. {_, Components} = validate("/ref/remote", Spec, Refs),
  206. ExpectComponents = [
  207. #{
  208. <<"emqx_swagger_remote_schema.ref2">> => #{
  209. <<"properties">> => [
  210. {<<"page">>, #{
  211. description => <<"good page">>,
  212. maximum => 100,
  213. minimum => 1,
  214. type => integer
  215. }},
  216. {<<"another_ref">>, #{
  217. <<"$ref">> =>
  218. <<"#/components/schemas/emqx_swagger_remote_schema.ref3">>
  219. }}
  220. ],
  221. <<"type">> => object
  222. }
  223. },
  224. #{
  225. <<"emqx_swagger_remote_schema.ref3">> => #{
  226. <<"properties">> => [
  227. {<<"ip">>, #{
  228. description => <<"IP:Port">>,
  229. example => <<"127.0.0.1:80">>,
  230. type => string
  231. }},
  232. {<<"version">>, #{
  233. description => <<"a good version">>,
  234. example => <<"1.0.0">>,
  235. type => string
  236. }}
  237. ],
  238. <<"type">> => object
  239. }
  240. }
  241. ],
  242. ?assertEqual(ExpectComponents, Components),
  243. ok.
  244. t_nest_ref(_Config) ->
  245. Spec = #{
  246. post => #{
  247. parameters => [],
  248. requestBody => #{
  249. <<"content">> => #{
  250. <<"application/json">> =>
  251. #{
  252. <<"schema">> => #{
  253. <<"$ref">> =>
  254. <<"#/components/schemas/emqx_swagger_requestBody_SUITE.nest_ref">>
  255. }
  256. }
  257. }
  258. },
  259. responses => #{<<"200">> => #{description => <<"ok">>}}
  260. }
  261. },
  262. Refs = [{?MODULE, nest_ref}],
  263. ExpectComponents = lists:sort([
  264. #{
  265. <<"emqx_swagger_requestBody_SUITE.nest_ref">> => #{
  266. <<"properties">> => [
  267. {<<"env">>, #{enum => [test, dev, prod], type => string}},
  268. {<<"another_ref">>, #{
  269. description => <<"nest ref">>,
  270. <<"$ref">> =>
  271. <<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>
  272. }}
  273. ],
  274. <<"type">> => object
  275. }
  276. },
  277. #{
  278. <<"emqx_swagger_requestBody_SUITE.good_ref">> => #{
  279. <<"properties">> => [
  280. {<<"webhook-host">>, #{
  281. default => <<"127.0.0.1:80">>,
  282. example => <<"127.0.0.1:80">>,
  283. type => string
  284. }},
  285. {<<"log_dir">>, #{example => <<"var/log/emqx">>, type => string}},
  286. {<<"tag">>, #{description => <<"tag">>, type => string}}
  287. ],
  288. <<"type">> => object
  289. }
  290. }
  291. ]),
  292. {_, Components} = validate("/ref/nest/ref", Spec, Refs),
  293. ?assertEqual(ExpectComponents, Components),
  294. ok.
  295. t_none_ref(_Config) ->
  296. Path = "/ref/none",
  297. ?assertError(
  298. {failed_to_generate_swagger_spec, ?MODULE, Path, error, _FunctionClause, _Stacktrace},
  299. emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path, #{})
  300. ),
  301. ok.
  302. t_sub_fields(_Config) ->
  303. Spec = #{
  304. post => #{
  305. parameters => [],
  306. requestBody => #{
  307. <<"content">> => #{
  308. <<"application/json">> =>
  309. #{
  310. <<"schema">> => #{
  311. <<"$ref">> =>
  312. <<"#/components/schemas/emqx_swagger_requestBody_SUITE.sub_fields">>
  313. }
  314. }
  315. }
  316. },
  317. responses => #{<<"200">> => #{description => <<"ok">>}}
  318. }
  319. },
  320. Refs = [{?MODULE, sub_fields}],
  321. validate("/fields/sub", Spec, Refs),
  322. ok.
  323. t_bad_ref(_Config) ->
  324. Path = "/ref/bad",
  325. Spec = #{
  326. post => #{
  327. parameters => [],
  328. requestBody => #{
  329. <<"content">> => #{
  330. <<"application/json">> => #{
  331. <<"schema">> =>
  332. #{
  333. <<"$ref">> =>
  334. <<"#/components/schemas/emqx_swagger_requestBody_SUITE.bad_ref">>
  335. }
  336. }
  337. }
  338. },
  339. responses => #{<<"200">> => #{description => <<"ok">>}}
  340. }
  341. },
  342. Refs = [{?MODULE, bad_ref}],
  343. Fields = fields(bad_ref),
  344. ?assertThrow(
  345. {error, #{msg := <<"Object only supports non-empty fields list">>, args := Fields}},
  346. validate(Path, Spec, Refs)
  347. ),
  348. ok.
  349. t_ref_array_with_key(_Config) ->
  350. Spec = #{
  351. post => #{
  352. parameters => [],
  353. requestBody => #{
  354. <<"content">> => #{
  355. <<"application/json">> =>
  356. #{
  357. <<"schema">> => #{
  358. required => [<<"timeout">>],
  359. <<"type">> => object,
  360. <<"properties">> =>
  361. [
  362. {<<"per_page">>, #{
  363. description => <<"good per page desc">>,
  364. maximum => 100,
  365. minimum => 1,
  366. type => integer
  367. }},
  368. {<<"timeout">>, #{
  369. default => 5,
  370. <<"oneOf">> =>
  371. [
  372. #{example => <<"1h">>, type => string},
  373. #{enum => [infinity], type => string}
  374. ]
  375. }},
  376. {<<"array_refs">>, #{
  377. items => #{
  378. <<"$ref">> =>
  379. <<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>
  380. },
  381. type => array
  382. }}
  383. ]
  384. }
  385. }
  386. }
  387. },
  388. responses => #{<<"200">> => #{description => <<"ok">>}}
  389. }
  390. },
  391. Refs = [{?MODULE, good_ref}],
  392. validate("/ref/array/with/key", Spec, Refs),
  393. ok.
  394. t_ref_array_without_key(_Config) ->
  395. Spec = #{
  396. post => #{
  397. parameters => [],
  398. requestBody => #{
  399. <<"content">> => #{
  400. <<"application/json">> => #{
  401. <<"schema">> =>
  402. #{
  403. items => #{
  404. <<"$ref">> =>
  405. <<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>
  406. },
  407. type => array
  408. }
  409. }
  410. }
  411. },
  412. responses => #{<<"200">> => #{description => <<"ok">>}}
  413. }
  414. },
  415. Refs = [{?MODULE, good_ref}],
  416. validate("/ref/array/without/key", Spec, Refs),
  417. ok.
  418. t_api_spec(_Config) ->
  419. {Spec0, _} = emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}),
  420. Path = "/object",
  421. Body = #{
  422. <<"per_page">> => 1,
  423. <<"timeout">> => <<"infinity">>,
  424. <<"inner_ref">> => #{
  425. <<"webhook-host">> => <<"127.0.0.1:80">>,
  426. <<"log_dir">> => <<"var/log/test">>,
  427. <<"tag">> => <<"god_tag">>
  428. }
  429. },
  430. Filter0 = filter(Spec0, Path),
  431. ?assertMatch(
  432. {ok, #{body := #{<<"timeout">> := <<"infinity">>}}},
  433. trans_requestBody(Path, Body, Filter0)
  434. ),
  435. {Spec1, _} = emqx_dashboard_swagger:spec(
  436. ?MODULE,
  437. #{check_schema => true, translate_body => true}
  438. ),
  439. Filter1 = filter(Spec1, Path),
  440. ?assertMatch(
  441. {ok, #{body := #{<<"timeout">> := infinity}}},
  442. trans_requestBody(Path, Body, Filter1)
  443. ).
  444. t_object_trans(_Config) ->
  445. Path = "/object",
  446. Body = #{
  447. <<"per_page">> => 1,
  448. <<"timeout">> => <<"infinity">>,
  449. <<"inner_ref">> => #{
  450. <<"webhook-host">> => <<"127.0.0.1:80">>,
  451. <<"log_dir">> => <<"var/log/test">>,
  452. <<"tag">> => <<"god_tag">>
  453. }
  454. },
  455. Expect =
  456. #{
  457. bindings => #{},
  458. query_string => #{},
  459. body =>
  460. #{
  461. <<"per_page">> => 1,
  462. <<"timeout">> => infinity,
  463. <<"inner_ref">> => #{
  464. <<"log_dir">> => "var/log/test",
  465. <<"tag">> => <<"god_tag">>,
  466. <<"webhook-host">> => {{127, 0, 0, 1}, 80}
  467. }
  468. }
  469. },
  470. {ok, ActualBody} = trans_requestBody(Path, Body),
  471. ?assertEqual(Expect, ActualBody),
  472. ok.
  473. t_object_notrans(_Config) ->
  474. Path = "/object",
  475. Body = #{
  476. <<"per_page">> => 1,
  477. <<"timeout">> => <<"infinity">>,
  478. <<"inner_ref">> => #{
  479. <<"webhook-host">> => <<"127.0.0.1:80">>,
  480. <<"log_dir">> => <<"var/log/test">>,
  481. <<"tag">> => <<"god_tag">>
  482. }
  483. },
  484. {ok, #{body := ActualBody}} = trans_requestBody(
  485. Path,
  486. Body,
  487. fun emqx_dashboard_swagger:filter_check_request/2
  488. ),
  489. ?assertEqual(Body, ActualBody),
  490. ok.
  491. todo_t_nest_object_check(_Config) ->
  492. Path = "/nest/object",
  493. Body = #{
  494. <<"timeout">> => "10m",
  495. <<"per_page">> => 10,
  496. <<"inner_ref">> => #{
  497. <<"webhook-host">> => <<"127.0.0.1:80">>,
  498. <<"log_dir">> => <<"var/log/test">>,
  499. <<"tag">> => <<"god_tag">>
  500. },
  501. <<"nest_object">> => #{
  502. <<"good_nest_1">> => 1,
  503. <<"good_nest_2">> => #{
  504. <<"webhook-host">> => <<"127.0.0.1:80">>,
  505. <<"log_dir">> => <<"var/log/test">>,
  506. <<"tag">> => <<"god_tag">>
  507. }
  508. }
  509. },
  510. Expect = #{
  511. bindings => #{},
  512. query_string => #{},
  513. body => #{
  514. <<"per_page">> => 10,
  515. <<"timeout">> => 600
  516. }
  517. },
  518. {ok, NewRequest} = check_requestBody(Path, Body),
  519. ?assertEqual(Expect, NewRequest),
  520. ok.
  521. t_local_ref_trans(_Config) ->
  522. Path = "/ref/local",
  523. Body = #{
  524. <<"webhook-host">> => <<"127.0.0.1:80">>,
  525. <<"log_dir">> => <<"var/log/test">>,
  526. <<"tag">> => <<"A">>
  527. },
  528. Expect = #{
  529. bindings => #{},
  530. query_string => #{},
  531. body => #{
  532. <<"log_dir">> => "var/log/test",
  533. <<"tag">> => <<"A">>,
  534. <<"webhook-host">> => {{127, 0, 0, 1}, 80}
  535. }
  536. },
  537. {ok, NewRequest} = trans_requestBody(Path, Body),
  538. ?assertEqual(Expect, NewRequest),
  539. ok.
  540. t_remote_ref_trans(_Config) ->
  541. Path = "/ref/remote",
  542. Body = #{
  543. <<"page">> => 10,
  544. <<"another_ref">> => #{
  545. <<"version">> => "2.1.0",
  546. <<"ip">> => <<"198.12.2.1:89">>
  547. }
  548. },
  549. Expect = #{
  550. bindings => #{},
  551. query_string => #{},
  552. body => #{
  553. <<"page">> => 10,
  554. <<"another_ref">> => #{
  555. <<"version">> => "2.1.0",
  556. <<"ip">> => {{198, 12, 2, 1}, 89}
  557. }
  558. }
  559. },
  560. {ok, NewRequest} = trans_requestBody(Path, Body),
  561. ?assertEqual(Expect, NewRequest),
  562. ok.
  563. t_nest_ref_trans(_Config) ->
  564. Path = "/ref/nest/ref",
  565. Body = #{
  566. <<"env">> => <<"prod">>,
  567. <<"another_ref">> => #{
  568. <<"log_dir">> => "var/log/dev",
  569. <<"tag">> => <<"A">>,
  570. <<"webhook-host">> => "127.0.0.1:80"
  571. }
  572. },
  573. Expect = #{
  574. bindings => #{},
  575. query_string => #{},
  576. body => #{
  577. <<"another_ref">> => #{
  578. <<"log_dir">> => "var/log/dev",
  579. <<"tag">> => <<"A">>,
  580. <<"webhook-host">> => {{127, 0, 0, 1}, 80}
  581. },
  582. <<"env">> => prod
  583. }
  584. },
  585. {ok, NewRequest} = trans_requestBody(Path, Body),
  586. ?assertEqual(Expect, NewRequest),
  587. ok.
  588. t_ref_array_with_key_trans(_Config) ->
  589. Path = "/ref/array/with/key",
  590. Body = #{
  591. <<"per_page">> => 100,
  592. <<"timeout">> => "100m",
  593. <<"array_refs">> => [
  594. #{
  595. <<"log_dir">> => "var/log/dev",
  596. <<"tag">> => <<"A">>,
  597. <<"webhook-host">> => "127.0.0.1:80"
  598. },
  599. #{
  600. <<"log_dir">> => "var/log/test",
  601. <<"tag">> => <<"B">>,
  602. <<"webhook-host">> => "127.0.0.1:81"
  603. }
  604. ]
  605. },
  606. Expect = #{
  607. bindings => #{},
  608. query_string => #{},
  609. body => #{
  610. <<"per_page">> => 100,
  611. <<"timeout">> => 6000,
  612. <<"array_refs">> => [
  613. #{
  614. <<"log_dir">> => "var/log/dev",
  615. <<"tag">> => <<"A">>,
  616. <<"webhook-host">> => {{127, 0, 0, 1}, 80}
  617. },
  618. #{
  619. <<"log_dir">> => "var/log/test",
  620. <<"tag">> => <<"B">>,
  621. <<"webhook-host">> => {{127, 0, 0, 1}, 81}
  622. }
  623. ]
  624. }
  625. },
  626. {ok, NewRequest} = trans_requestBody(Path, Body),
  627. ?assertEqual(Expect, NewRequest),
  628. ok.
  629. t_ref_array_without_key_trans(_Config) ->
  630. Path = "/ref/array/without/key",
  631. Body = [
  632. #{
  633. <<"log_dir">> => "var/log/dev",
  634. <<"tag">> => <<"A">>,
  635. <<"webhook-host">> => "127.0.0.1:80"
  636. },
  637. #{
  638. <<"log_dir">> => "var/log/test",
  639. <<"tag">> => <<"B">>,
  640. <<"webhook-host">> => "127.0.0.1:81"
  641. }
  642. ],
  643. Expect = #{
  644. bindings => #{},
  645. query_string => #{},
  646. body => [
  647. #{
  648. <<"log_dir">> => "var/log/dev",
  649. <<"tag">> => <<"A">>,
  650. <<"webhook-host">> => {{127, 0, 0, 1}, 80}
  651. },
  652. #{
  653. <<"log_dir">> => "var/log/test",
  654. <<"tag">> => <<"B">>,
  655. <<"webhook-host">> => {{127, 0, 0, 1}, 81}
  656. }
  657. ]
  658. },
  659. {ok, NewRequest} = trans_requestBody(Path, Body),
  660. ?assertEqual(Expect, NewRequest),
  661. ok.
  662. t_ref_trans_error(_Config) ->
  663. Path = "/ref/nest/ref",
  664. Body = #{
  665. <<"env">> => <<"prod">>,
  666. <<"another_ref">> => #{
  667. <<"log_dir">> => "var/log/dev",
  668. <<"tag">> => <<"A">>,
  669. <<"webhook-host">> => "127.0..0.1:80"
  670. }
  671. },
  672. {400, 'BAD_REQUEST', _} = trans_requestBody(Path, Body),
  673. ok.
  674. t_object_trans_error(_Config) ->
  675. Path = "/object",
  676. Body = #{
  677. <<"per_page">> => 99,
  678. <<"timeout">> => <<"infinity">>,
  679. <<"inner_ref">> => #{
  680. <<"webhook-host">> => <<"127.0.0..1:80">>,
  681. <<"log_dir">> => <<"var/log/test">>,
  682. <<"tag">> => <<"god_tag">>
  683. }
  684. },
  685. {400, 'BAD_REQUEST', Reason} = trans_requestBody(Path, Body),
  686. ?assertNotEqual(nomatch, binary:match(Reason, [<<"webhook-host">>])),
  687. ok.
  688. validate(Path, ExpectSpec, ExpectRefs) ->
  689. {OperationId, Spec, Refs, #{}} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path, #{}),
  690. ?assertEqual(test, OperationId),
  691. ?assertEqual(ExpectSpec, Spec),
  692. ?assertEqual(ExpectRefs, Refs),
  693. {Spec, emqx_dashboard_swagger:components(Refs, #{})}.
  694. filter(ApiSpec, Path) ->
  695. [Filter] = [F || {P, _, _, #{filter := F}} <- ApiSpec, P =:= Path],
  696. Filter.
  697. trans_requestBody(Path, Body) ->
  698. trans_requestBody(
  699. Path,
  700. Body,
  701. fun emqx_dashboard_swagger:filter_check_request_and_translate_body/2
  702. ).
  703. check_requestBody(Path, Body) ->
  704. trans_requestBody(
  705. Path,
  706. Body,
  707. fun emqx_dashboard_swagger:filter_check_request/2
  708. ).
  709. trans_requestBody(Path, Body, Filter) ->
  710. Meta = #{module => ?MODULE, method => post, path => Path},
  711. Request = #{bindings => #{}, query_string => #{}, body => Body},
  712. Filter(Request, Meta).
  713. api_spec() -> emqx_dashboard_swagger:spec(?MODULE).
  714. paths() ->
  715. [
  716. "/object",
  717. "/nest/object",
  718. "/ref/local",
  719. "/ref/nest/ref",
  720. "/fields/sub",
  721. "/ref/array/with/key",
  722. "/ref/array/without/key"
  723. ].
  724. schema("/object") ->
  725. to_schema([
  726. {per_page, mk(range(1, 100), #{required => true, desc => <<"good per page desc">>})},
  727. {timeout,
  728. mk(
  729. hoconsc:union([infinity, emqx_schema:timeout_duration_s()]),
  730. #{default => 5, required => true}
  731. )},
  732. {inner_ref, mk(hoconsc:ref(?MODULE, good_ref), #{})}
  733. ]);
  734. schema("/nest/object") ->
  735. to_schema([
  736. {per_page, mk(range(1, 100), #{desc => <<"good per page desc">>})},
  737. {timeout,
  738. mk(
  739. hoconsc:union([infinity, emqx_schema:timeout_duration_s()]),
  740. #{default => 5, required => true}
  741. )},
  742. {nest_object, [
  743. {good_nest_1, mk(integer(), #{})},
  744. {good_nest_2, mk(hoconsc:ref(?MODULE, good_ref), #{})}
  745. ]},
  746. {inner_ref, mk(hoconsc:ref(?MODULE, good_ref), #{})}
  747. ]);
  748. schema("/ref/local") ->
  749. to_schema(mk(hoconsc:ref(good_ref), #{}));
  750. schema("/fields/sub") ->
  751. to_schema(mk(hoconsc:ref(sub_fields), #{}));
  752. schema("/ref/remote") ->
  753. to_schema(mk(hoconsc:ref(emqx_swagger_remote_schema, "ref2"), #{}));
  754. schema("/ref/bad") ->
  755. to_schema(mk(hoconsc:ref(?MODULE, bad_ref), #{}));
  756. schema("/ref/nest/ref") ->
  757. to_schema(mk(hoconsc:ref(?MODULE, nest_ref), #{}));
  758. schema("/ref/array/with/key") ->
  759. to_schema([
  760. {per_page, mk(range(1, 100), #{desc => <<"good per page desc">>})},
  761. {timeout,
  762. mk(
  763. hoconsc:union([infinity, emqx_schema:timeout_duration_s()]),
  764. #{default => 5, required => true}
  765. )},
  766. {array_refs, mk(hoconsc:array(hoconsc:ref(?MODULE, good_ref)), #{})}
  767. ]);
  768. schema("/ref/array/without/key") ->
  769. to_schema(mk(hoconsc:array(hoconsc:ref(?MODULE, good_ref)), #{})).
  770. to_schema(Body) ->
  771. #{
  772. operationId => test,
  773. post => #{requestBody => Body, responses => #{200 => <<"ok">>}}
  774. }.
  775. roots() -> [].
  776. namespace() -> atom_to_list(?MODULE).
  777. fields(good_ref) ->
  778. [
  779. {'webhook-host', mk(emqx_schema:ip_port(), #{default => <<"127.0.0.1:80">>})},
  780. {log_dir, mk(string(), #{example => "var/log/emqx"})},
  781. {tag, mk(binary(), #{desc => <<"tag">>})}
  782. ];
  783. fields(nest_ref) ->
  784. [
  785. {env, mk(hoconsc:enum([test, dev, prod]), #{})},
  786. {another_ref, mk(hoconsc:ref(good_ref), #{desc => "nest ref"})}
  787. ];
  788. %% don't support maps
  789. fields(bad_ref) ->
  790. #{
  791. username => mk(string(), #{}),
  792. is_admin => mk(boolean(), #{})
  793. };
  794. fields(sub_fields) ->
  795. #{
  796. fields => [
  797. {enable, fun enable/1},
  798. {init_file, fun init_file/1}
  799. ],
  800. desc => <<"test sub fields">>
  801. };
  802. fields(deprecated_ref) ->
  803. [
  804. {tag1, mk(binary(), #{desc => <<"tag1">>, deprecated => {since, "4.3.0"}})},
  805. {tag2, mk(binary(), #{desc => <<"tag2">>, deprecated => true})},
  806. {tag3, mk(binary(), #{desc => <<"tag3">>, deprecated => false})}
  807. ];
  808. fields(nonempty_list_ref) ->
  809. [
  810. {list, mk(nonempty_list(binary()), #{})}
  811. ].
  812. enable(type) -> boolean();
  813. enable(desc) -> <<"Whether to enable tls psk support">>;
  814. enable(default) -> false;
  815. enable(_) -> undefined.
  816. init_file(type) -> binary();
  817. init_file(desc) -> <<"test test desc">>;
  818. init_file(required) -> false;
  819. init_file(_) -> undefined.