emqx_schema_tests.erl 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2017-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_schema_tests).
  17. -include_lib("eunit/include/eunit.hrl").
  18. ssl_opts_dtls_test() ->
  19. Sc = emqx_schema:server_ssl_opts_schema(
  20. #{
  21. versions => dtls_all_available
  22. },
  23. false
  24. ),
  25. Checked = validate(Sc, #{<<"versions">> => [<<"dtlsv1.2">>, <<"dtlsv1">>]}),
  26. ?assertMatch(
  27. #{
  28. versions := ['dtlsv1.2', 'dtlsv1'],
  29. ciphers := []
  30. },
  31. Checked
  32. ).
  33. ssl_opts_tls_1_3_test() ->
  34. Sc = emqx_schema:server_ssl_opts_schema(#{}, false),
  35. Checked = validate(Sc, #{<<"versions">> => [<<"tlsv1.3">>]}),
  36. ?assertMatch(
  37. #{
  38. versions := ['tlsv1.3'],
  39. ciphers := [],
  40. handshake_timeout := _
  41. },
  42. Checked
  43. ).
  44. ssl_opts_tls_for_ranch_test() ->
  45. Sc = emqx_schema:server_ssl_opts_schema(#{}, true),
  46. Checked = validate(Sc, #{<<"versions">> => [<<"tlsv1.3">>]}),
  47. ?assertMatch(
  48. #{
  49. versions := ['tlsv1.3'],
  50. ciphers := [],
  51. handshake_timeout := _
  52. },
  53. Checked
  54. ).
  55. ssl_opts_cipher_array_test() ->
  56. Sc = emqx_schema:server_ssl_opts_schema(#{}, false),
  57. Checked = validate(Sc, #{
  58. <<"versions">> => [<<"tlsv1.3">>],
  59. <<"ciphers">> => [
  60. <<"TLS_AES_256_GCM_SHA384">>,
  61. <<"ECDHE-ECDSA-AES256-GCM-SHA384">>
  62. ]
  63. }),
  64. ?assertMatch(
  65. #{
  66. versions := ['tlsv1.3'],
  67. ciphers := ["TLS_AES_256_GCM_SHA384", "ECDHE-ECDSA-AES256-GCM-SHA384"]
  68. },
  69. Checked
  70. ).
  71. ssl_opts_cipher_comma_separated_string_test() ->
  72. Sc = emqx_schema:server_ssl_opts_schema(#{}, false),
  73. Checked = validate(Sc, #{
  74. <<"versions">> => [<<"tlsv1.3">>],
  75. <<"ciphers">> => <<"TLS_AES_256_GCM_SHA384,ECDHE-ECDSA-AES256-GCM-SHA384">>
  76. }),
  77. ?assertMatch(
  78. #{
  79. versions := ['tlsv1.3'],
  80. ciphers := ["TLS_AES_256_GCM_SHA384", "ECDHE-ECDSA-AES256-GCM-SHA384"]
  81. },
  82. Checked
  83. ).
  84. ssl_opts_tls_psk_test() ->
  85. Sc = emqx_schema:server_ssl_opts_schema(#{}, false),
  86. Checked = validate(Sc, #{<<"versions">> => [<<"tlsv1.2">>]}),
  87. ?assertMatch(#{versions := ['tlsv1.2']}, Checked).
  88. ssl_opts_version_gap_test_() ->
  89. Sc = emqx_schema:server_ssl_opts_schema(#{}, false),
  90. RanchSc = emqx_schema:server_ssl_opts_schema(#{}, true),
  91. Reason = "Using multiple versions that include tlsv1.3 but exclude tlsv1.2 is not allowed",
  92. [
  93. ?_assertThrow(
  94. {_, [#{kind := validation_error, reason := Reason}]},
  95. validate(S, #{<<"versions">> => [<<"tlsv1.1">>, <<"tlsv1.3">>]})
  96. )
  97. || S <- [Sc, RanchSc]
  98. ].
  99. ssl_opts_cert_depth_test() ->
  100. Sc = emqx_schema:server_ssl_opts_schema(#{}, false),
  101. Reason = #{expected => "non_neg_integer()"},
  102. ?assertThrow(
  103. {_Sc, [#{kind := validation_error, reason := Reason}]},
  104. validate(Sc, #{<<"depth">> => -1})
  105. ).
  106. bad_cipher_test() ->
  107. Sc = emqx_schema:server_ssl_opts_schema(#{}, false),
  108. Reason = {bad_ciphers, ["foo"]},
  109. ?assertThrow(
  110. {_Sc, [#{kind := validation_error, reason := Reason}]},
  111. validate(Sc, #{
  112. <<"versions">> => [<<"tlsv1.2">>],
  113. <<"ciphers">> => [<<"foo">>]
  114. })
  115. ),
  116. ok.
  117. fail_if_no_peer_cert_test_() ->
  118. Sc = #{
  119. roots => [mqtt_ssl_listener],
  120. fields => #{mqtt_ssl_listener => emqx_schema:fields("mqtt_ssl_listener")}
  121. },
  122. Opts = #{atom_key => false, required => false},
  123. OptsAtomKey = #{atom_key => true, required => false},
  124. InvalidConf = #{
  125. <<"bind">> => <<"0.0.0.0:9883">>,
  126. <<"ssl_options">> => #{
  127. <<"fail_if_no_peer_cert">> => true,
  128. <<"verify">> => <<"verify_none">>
  129. }
  130. },
  131. InvalidListener = #{<<"mqtt_ssl_listener">> => InvalidConf},
  132. ValidListener = #{
  133. <<"mqtt_ssl_listener">> => InvalidConf#{
  134. <<"ssl_options">> =>
  135. #{
  136. <<"fail_if_no_peer_cert">> => true,
  137. <<"verify">> => <<"verify_peer">>
  138. }
  139. }
  140. },
  141. ValidListener1 = #{
  142. <<"mqtt_ssl_listener">> => InvalidConf#{
  143. <<"ssl_options">> =>
  144. #{
  145. <<"fail_if_no_peer_cert">> => false,
  146. <<"verify">> => <<"verify_none">>
  147. }
  148. }
  149. },
  150. Reason = "verify must be verify_peer when fail_if_no_peer_cert is true",
  151. [
  152. ?_assertThrow(
  153. {_Sc, [#{kind := validation_error, reason := Reason}]},
  154. hocon_tconf:check_plain(Sc, InvalidListener, Opts)
  155. ),
  156. ?_assertThrow(
  157. {_Sc, [#{kind := validation_error, reason := Reason}]},
  158. hocon_tconf:check_plain(Sc, InvalidListener, OptsAtomKey)
  159. ),
  160. ?_assertMatch(
  161. #{mqtt_ssl_listener := #{}},
  162. hocon_tconf:check_plain(Sc, ValidListener, OptsAtomKey)
  163. ),
  164. ?_assertMatch(
  165. #{mqtt_ssl_listener := #{}},
  166. hocon_tconf:check_plain(Sc, ValidListener1, OptsAtomKey)
  167. ),
  168. ?_assertMatch(
  169. #{<<"mqtt_ssl_listener">> := #{}},
  170. hocon_tconf:check_plain(Sc, ValidListener, Opts)
  171. ),
  172. ?_assertMatch(
  173. #{<<"mqtt_ssl_listener">> := #{}},
  174. hocon_tconf:check_plain(Sc, ValidListener1, Opts)
  175. )
  176. ].
  177. validate(Schema, Data0) ->
  178. Sc = #{
  179. roots => [ssl_opts],
  180. fields => #{ssl_opts => Schema}
  181. },
  182. Data = Data0#{
  183. cacertfile => <<"cacertfile">>,
  184. certfile => <<"certfile">>,
  185. keyfile => <<"keyfile">>
  186. },
  187. #{ssl_opts := Checked} =
  188. hocon_tconf:check_plain(
  189. Sc,
  190. #{<<"ssl_opts">> => Data},
  191. #{atom_key => true}
  192. ),
  193. Checked.
  194. ciphers_schema_test() ->
  195. Sc = emqx_schema:ciphers_schema(undefined),
  196. WSc = #{roots => [{ciphers, Sc}]},
  197. ?assertThrow(
  198. {_, [#{kind := validation_error}]},
  199. hocon_tconf:check_plain(WSc, #{<<"ciphers">> => <<"foo,bar">>})
  200. ).
  201. bad_tls_version_test() ->
  202. Sc = emqx_schema:server_ssl_opts_schema(#{}, false),
  203. Reason = {unsupported_tls_versions, [foo]},
  204. ?assertThrow(
  205. {_Sc, [#{kind := validation_error, reason := Reason}]},
  206. validate(Sc, #{<<"versions">> => [<<"foo">>]})
  207. ),
  208. ok.
  209. ssl_opts_gc_after_handshake_test_rancher_listener_test() ->
  210. Sc = emqx_schema:server_ssl_opts_schema(
  211. #{
  212. gc_after_handshake => false
  213. },
  214. _IsRanchListener = true
  215. ),
  216. ?assertThrow(
  217. {_Sc, [
  218. #{
  219. kind := validation_error,
  220. reason := unknown_fields,
  221. unknown := "gc_after_handshake"
  222. }
  223. ]},
  224. validate(Sc, #{<<"gc_after_handshake">> => true})
  225. ),
  226. ok.
  227. ssl_opts_gc_after_handshake_test_not_rancher_listener_test() ->
  228. Sc = emqx_schema:server_ssl_opts_schema(
  229. #{
  230. gc_after_handshake => false
  231. },
  232. _IsRanchListener = false
  233. ),
  234. Checked = validate(Sc, #{<<"gc_after_handshake">> => <<"true">>}),
  235. ?assertMatch(
  236. #{
  237. gc_after_handshake := true
  238. },
  239. Checked
  240. ),
  241. ok.
  242. to_ip_port_test_() ->
  243. Ip = fun emqx_schema:to_ip_port/1,
  244. [
  245. ?_assertEqual({ok, 80}, Ip("80")),
  246. ?_assertEqual({ok, 80}, Ip(":80")),
  247. ?_assertEqual({error, bad_ip_port}, Ip("localhost:80")),
  248. ?_assertEqual({ok, {{127, 0, 0, 1}, 80}}, Ip("127.0.0.1:80")),
  249. ?_assertEqual({error, bad_ip_port}, Ip("$:1900")),
  250. ?_assertMatch({ok, {_, 1883}}, Ip("[::1]:1883")),
  251. ?_assertMatch({ok, {_, 1883}}, Ip("::1:1883")),
  252. ?_assertMatch({ok, {_, 1883}}, Ip(":::1883"))
  253. ].
  254. -define(T(CASE, EXPR), {CASE, fun() -> EXPR end}).
  255. parse_server_test_() ->
  256. DefaultPort = ?LINE,
  257. DefaultOpts = #{default_port => DefaultPort},
  258. Parse2 = fun(Value0, Opts) ->
  259. Value = emqx_schema:convert_servers(Value0),
  260. Validator = emqx_schema:servers_validator(Opts, _Required = true),
  261. try
  262. Result = emqx_schema:parse_servers(Value, Opts),
  263. ?assertEqual(ok, Validator(Value)),
  264. Result
  265. catch
  266. throw:Throw ->
  267. %% assert validator throws the same exception
  268. ?assertThrow(Throw, Validator(Value)),
  269. %% and then let the test code validate the exception
  270. throw(Throw)
  271. end
  272. end,
  273. Parse = fun(Value) -> Parse2(Value, DefaultOpts) end,
  274. HoconParse = fun(Str0) ->
  275. {ok, Map} = hocon:binary(Str0),
  276. Str = emqx_schema:convert_servers(Map),
  277. Parse(Str)
  278. end,
  279. [
  280. ?T(
  281. "single server, binary, no port",
  282. ?assertEqual(
  283. [#{hostname => "localhost", port => DefaultPort}],
  284. Parse(<<"localhost">>)
  285. )
  286. ),
  287. ?T(
  288. "single server, string, no port",
  289. ?assertEqual(
  290. [#{hostname => "localhost", port => DefaultPort}],
  291. Parse("localhost")
  292. )
  293. ),
  294. ?T(
  295. "single server, list(string), no port",
  296. ?assertEqual(
  297. [#{hostname => "localhost", port => DefaultPort}],
  298. Parse(["localhost"])
  299. )
  300. ),
  301. ?T(
  302. "single server, list(binary), no port",
  303. ?assertEqual(
  304. [#{hostname => "localhost", port => DefaultPort}],
  305. Parse([<<"localhost">>])
  306. )
  307. ),
  308. ?T(
  309. "single server, binary, with port",
  310. ?assertEqual(
  311. [#{hostname => "localhost", port => 9999}],
  312. Parse(<<"localhost:9999">>)
  313. )
  314. ),
  315. ?T(
  316. "single server, list(string), with port",
  317. ?assertEqual(
  318. [#{hostname => "localhost", port => 9999}],
  319. Parse(["localhost:9999"])
  320. )
  321. ),
  322. ?T(
  323. "single server, string, with port",
  324. ?assertEqual(
  325. [#{hostname => "localhost", port => 9999}],
  326. Parse("localhost:9999")
  327. )
  328. ),
  329. ?T(
  330. "single server, list(binary), with port",
  331. ?assertEqual(
  332. [#{hostname => "localhost", port => 9999}],
  333. Parse([<<"localhost:9999">>])
  334. )
  335. ),
  336. ?T(
  337. "multiple servers, string, no port",
  338. ?assertEqual(
  339. [
  340. #{hostname => "host1", port => DefaultPort},
  341. #{hostname => "host2", port => DefaultPort}
  342. ],
  343. Parse("host1, host2")
  344. )
  345. ),
  346. ?T(
  347. "multiple servers, binary, no port",
  348. ?assertEqual(
  349. [
  350. #{hostname => "host1", port => DefaultPort},
  351. #{hostname => "host2", port => DefaultPort}
  352. ],
  353. Parse(<<"host1, host2,,,">>)
  354. )
  355. ),
  356. ?T(
  357. "multiple servers, list(string), no port",
  358. ?assertEqual(
  359. [
  360. #{hostname => "host1", port => DefaultPort},
  361. #{hostname => "host2", port => DefaultPort}
  362. ],
  363. Parse(["host1", "host2"])
  364. )
  365. ),
  366. ?T(
  367. "multiple servers, list(binary), no port",
  368. ?assertEqual(
  369. [
  370. #{hostname => "host1", port => DefaultPort},
  371. #{hostname => "host2", port => DefaultPort}
  372. ],
  373. Parse([<<"host1">>, <<"host2">>])
  374. )
  375. ),
  376. ?T(
  377. "multiple servers, string, with port",
  378. ?assertEqual(
  379. [#{hostname => "host1", port => 1234}, #{hostname => "host2", port => 2345}],
  380. Parse("host1:1234, host2:2345")
  381. )
  382. ),
  383. ?T(
  384. "multiple servers, binary, with port",
  385. ?assertEqual(
  386. [#{hostname => "host1", port => 1234}, #{hostname => "host2", port => 2345}],
  387. Parse(<<"host1:1234, host2:2345, ">>)
  388. )
  389. ),
  390. ?T(
  391. "multiple servers, list(string), with port",
  392. ?assertEqual(
  393. [#{hostname => "host1", port => 1234}, #{hostname => "host2", port => 2345}],
  394. Parse([" host1:1234 ", "host2:2345"])
  395. )
  396. ),
  397. ?T(
  398. "multiple servers, list(binary), with port",
  399. ?assertEqual(
  400. [#{hostname => "host1", port => 1234}, #{hostname => "host2", port => 2345}],
  401. Parse([<<"host1:1234">>, <<"host2:2345">>])
  402. )
  403. ),
  404. ?T(
  405. "unexpected multiple servers",
  406. ?assertThrow(
  407. "expecting_one_host_but_got: 2",
  408. emqx_schema:parse_server(<<"host1:1234, host2:1234">>, #{default_port => 1})
  409. )
  410. ),
  411. ?T(
  412. "multiple servers without ports invalid string list",
  413. ?assertThrow(
  414. "hostname_has_space",
  415. Parse2(["host1 host2"], #{no_port => true})
  416. )
  417. ),
  418. ?T(
  419. "multiple servers without ports invalid binary list",
  420. ?assertThrow(
  421. "hostname_has_space",
  422. Parse2([<<"host1 host2">>], #{no_port => true})
  423. )
  424. ),
  425. ?T(
  426. "multiple servers without port, mixed list(binary|string)",
  427. ?assertEqual(
  428. [#{hostname => "host1"}, #{hostname => "host2"}],
  429. Parse2([<<"host1">>, "host2"], #{no_port => true})
  430. )
  431. ),
  432. ?T(
  433. "no default port, missing port number in config",
  434. ?assertThrow(
  435. "missing_port_number",
  436. emqx_schema:parse_server(<<"a">>, #{})
  437. )
  438. ),
  439. ?T(
  440. "empty binary string",
  441. ?assertEqual(
  442. undefined,
  443. emqx_schema:parse_server(<<>>, #{no_port => true})
  444. )
  445. ),
  446. ?T(
  447. "empty array",
  448. ?assertEqual(
  449. undefined,
  450. emqx_schema:parse_servers([], #{no_port => true})
  451. )
  452. ),
  453. ?T(
  454. "empty binary array",
  455. ?assertThrow(
  456. "bad_host_port",
  457. emqx_schema:parse_servers([<<>>], #{no_port => true})
  458. )
  459. ),
  460. ?T(
  461. "HOCON value undefined",
  462. ?assertEqual(
  463. undefined,
  464. emqx_schema:parse_server(undefined, #{no_port => true})
  465. )
  466. ),
  467. ?T(
  468. "single server map",
  469. ?assertEqual(
  470. [#{hostname => "host1.domain", port => 1234}],
  471. HoconParse("host1.domain:1234")
  472. )
  473. ),
  474. ?T(
  475. "multiple servers map",
  476. ?assertEqual(
  477. [
  478. #{hostname => "host1.domain", port => 1234},
  479. #{hostname => "host2.domain", port => 2345},
  480. #{hostname => "host3.domain", port => 3456}
  481. ],
  482. HoconParse("host1.domain:1234,host2.domain:2345,host3.domain:3456")
  483. )
  484. ),
  485. ?T(
  486. "no port expected valid port",
  487. ?assertThrow(
  488. "not_expecting_port_number",
  489. emqx_schema:parse_server("localhost:80", #{no_port => true})
  490. )
  491. ),
  492. ?T(
  493. "no port expected invalid port",
  494. ?assertThrow(
  495. "not_expecting_port_number",
  496. emqx_schema:parse_server("localhost:notaport", #{no_port => true})
  497. )
  498. ),
  499. ?T(
  500. "bad hostname",
  501. ?assertThrow(
  502. "expecting_hostname_but_got_a_number",
  503. emqx_schema:parse_server(":80", #{default_port => 80})
  504. )
  505. ),
  506. ?T(
  507. "bad port",
  508. ?assertThrow(
  509. "bad_port_number",
  510. emqx_schema:parse_server("host:33x", #{default_port => 33})
  511. )
  512. ),
  513. ?T(
  514. "bad host with port",
  515. ?assertThrow(
  516. "bad_host_port",
  517. emqx_schema:parse_server("host:name:80", #{default_port => 80})
  518. )
  519. ),
  520. ?T(
  521. "bad schema",
  522. ?assertError(
  523. "bad_schema",
  524. emqx_schema:parse_server("whatever", #{default_port => 10, no_port => true})
  525. )
  526. ),
  527. ?T(
  528. "scheme, hostname and port",
  529. ?assertEqual(
  530. #{scheme => "pulsar+ssl", hostname => "host", port => 6651},
  531. emqx_schema:parse_server(
  532. "pulsar+ssl://host:6651",
  533. #{
  534. default_port => 6650,
  535. supported_schemes => ["pulsar", "pulsar+ssl"]
  536. }
  537. )
  538. )
  539. ),
  540. ?T(
  541. "scheme and hostname, default port",
  542. ?assertEqual(
  543. #{scheme => "pulsar", hostname => "host", port => 6650},
  544. emqx_schema:parse_server(
  545. "pulsar://host",
  546. #{
  547. default_port => 6650,
  548. supported_schemes => ["pulsar", "pulsar+ssl"]
  549. }
  550. )
  551. )
  552. ),
  553. ?T(
  554. "scheme and hostname, no port",
  555. ?assertEqual(
  556. #{scheme => "pulsar", hostname => "host"},
  557. emqx_schema:parse_server(
  558. "pulsar://host",
  559. #{
  560. no_port => true,
  561. supported_schemes => ["pulsar", "pulsar+ssl"]
  562. }
  563. )
  564. )
  565. ),
  566. ?T(
  567. "scheme and hostname, missing port",
  568. ?assertThrow(
  569. "missing_port_number",
  570. emqx_schema:parse_server(
  571. "pulsar://host",
  572. #{
  573. no_port => false,
  574. supported_schemes => ["pulsar", "pulsar+ssl"]
  575. }
  576. )
  577. )
  578. ),
  579. ?T(
  580. "hostname, default scheme, no default port",
  581. ?assertEqual(
  582. #{scheme => "pulsar", hostname => "host"},
  583. emqx_schema:parse_server(
  584. "host",
  585. #{
  586. default_scheme => "pulsar",
  587. no_port => true,
  588. supported_schemes => ["pulsar", "pulsar+ssl"]
  589. }
  590. )
  591. )
  592. ),
  593. ?T(
  594. "hostname, default scheme, default port",
  595. ?assertEqual(
  596. #{scheme => "pulsar", hostname => "host", port => 6650},
  597. emqx_schema:parse_server(
  598. "host",
  599. #{
  600. default_port => 6650,
  601. default_scheme => "pulsar",
  602. supported_schemes => ["pulsar", "pulsar+ssl"]
  603. }
  604. )
  605. )
  606. ),
  607. ?T(
  608. "just hostname, expecting missing scheme",
  609. ?assertThrow(
  610. "missing_scheme",
  611. emqx_schema:parse_server(
  612. "host",
  613. #{
  614. no_port => true,
  615. supported_schemes => ["pulsar", "pulsar+ssl"]
  616. }
  617. )
  618. )
  619. ),
  620. ?T(
  621. "hostname, default scheme, defined port",
  622. ?assertEqual(
  623. #{scheme => "pulsar", hostname => "host", port => 6651},
  624. emqx_schema:parse_server(
  625. "host:6651",
  626. #{
  627. default_port => 6650,
  628. default_scheme => "pulsar",
  629. supported_schemes => ["pulsar", "pulsar+ssl"]
  630. }
  631. )
  632. )
  633. ),
  634. ?T(
  635. "inconsistent scheme opts",
  636. ?assertError(
  637. "bad_schema",
  638. emqx_schema:parse_server(
  639. "pulsar+ssl://host:6651",
  640. #{
  641. default_port => 6650,
  642. default_scheme => "something",
  643. supported_schemes => ["not", "supported"]
  644. }
  645. )
  646. )
  647. ),
  648. ?T(
  649. "hostname, default scheme, defined port",
  650. ?assertEqual(
  651. #{scheme => "pulsar", hostname => "host", port => 6651},
  652. emqx_schema:parse_server(
  653. "host:6651",
  654. #{
  655. default_port => 6650,
  656. default_scheme => "pulsar",
  657. supported_schemes => ["pulsar", "pulsar+ssl"]
  658. }
  659. )
  660. )
  661. ),
  662. ?T(
  663. "unsupported scheme",
  664. ?assertThrow(
  665. "unsupported_scheme",
  666. emqx_schema:parse_server(
  667. "pulsar+quic://host:6651",
  668. #{
  669. default_port => 6650,
  670. supported_schemes => ["pulsar"]
  671. }
  672. )
  673. )
  674. ),
  675. ?T(
  676. "multiple hostnames with schemes (1)",
  677. ?assertEqual(
  678. [
  679. #{scheme => "pulsar", hostname => "host", port => 6649},
  680. #{scheme => "pulsar+ssl", hostname => "other.host", port => 6651},
  681. #{scheme => "pulsar", hostname => "yet.another", port => 6650}
  682. ],
  683. emqx_schema:parse_servers(
  684. "pulsar://host:6649, pulsar+ssl://other.host:6651,pulsar://yet.another",
  685. #{
  686. default_port => 6650,
  687. supported_schemes => ["pulsar", "pulsar+ssl"]
  688. }
  689. )
  690. )
  691. )
  692. ].
  693. servers_validator_test() ->
  694. Required = emqx_schema:servers_validator(#{}, true),
  695. NotRequired = emqx_schema:servers_validator(#{}, false),
  696. ?assertThrow("cannot_be_empty", Required("")),
  697. ?assertThrow("cannot_be_empty", Required(<<>>)),
  698. ?assertThrow("cannot_be_empty", NotRequired("")),
  699. ?assertThrow("cannot_be_empty", NotRequired(<<>>)),
  700. ?assertThrow("cannot_be_empty", Required(undefined)),
  701. ?assertEqual(ok, NotRequired(undefined)),
  702. ?assertEqual(ok, NotRequired("undefined")),
  703. ok.
  704. converter_invalid_input_test() ->
  705. ?assertEqual(undefined, emqx_schema:convert_servers(undefined)),
  706. %% 'foo: bar' is a valid HOCON value, but 'bar' is not a port number
  707. ?assertThrow("bad_host_port", emqx_schema:convert_servers(#{foo => bar})).
  708. password_converter_test() ->
  709. ?assertEqual(undefined, emqx_schema:password_converter(undefined, #{})),
  710. ?assertEqual(<<"123">>, emqx_schema:password_converter(123, #{})),
  711. ?assertEqual(<<"123">>, emqx_schema:password_converter(<<"123">>, #{})),
  712. ?assertThrow("must_quote", emqx_schema:password_converter(foobar, #{})),
  713. ok.
  714. -define(MQTT(B, M), #{<<"keepalive_backoff">> => B, <<"keepalive_multiplier">> => M}).
  715. keepalive_convert_test() ->
  716. ?assertEqual(undefined, emqx_schema:mqtt_converter(undefined, #{})),
  717. DefaultBackoff = 0.75,
  718. DefaultMultiplier = 1.5,
  719. Default = ?MQTT(DefaultBackoff, DefaultMultiplier),
  720. ?assertEqual(Default, emqx_schema:mqtt_converter(Default, #{})),
  721. ?assertEqual(?MQTT(1.5, 3), emqx_schema:mqtt_converter(?MQTT(1.5, 3), #{})),
  722. ?assertEqual(
  723. ?MQTT(DefaultBackoff, 3), emqx_schema:mqtt_converter(?MQTT(DefaultBackoff, 3), #{})
  724. ),
  725. ?assertEqual(?MQTT(1, 2), emqx_schema:mqtt_converter(?MQTT(1, DefaultMultiplier), #{})),
  726. ?assertEqual(?MQTT(1.5, 3), emqx_schema:mqtt_converter(?MQTT(1.5, 3), #{})),
  727. ?assertEqual(#{}, emqx_schema:mqtt_converter(#{}, #{})),
  728. ?assertEqual(
  729. #{<<"keepalive_backoff">> => 1.5, <<"keepalive_multiplier">> => 3.0},
  730. emqx_schema:mqtt_converter(#{<<"keepalive_backoff">> => 1.5}, #{})
  731. ),
  732. ?assertEqual(
  733. #{<<"keepalive_multiplier">> => 5.0},
  734. emqx_schema:mqtt_converter(#{<<"keepalive_multiplier">> => 5.0}, #{})
  735. ),
  736. ?assertEqual(
  737. #{
  738. <<"keepalive_backoff">> => DefaultBackoff,
  739. <<"keepalive_multiplier">> => DefaultMultiplier
  740. },
  741. emqx_schema:mqtt_converter(#{<<"keepalive_backoff">> => DefaultBackoff}, #{})
  742. ),
  743. ?assertEqual(
  744. #{<<"keepalive_multiplier">> => DefaultMultiplier},
  745. emqx_schema:mqtt_converter(#{<<"keepalive_multiplier">> => DefaultMultiplier}, #{})
  746. ),
  747. ok.
  748. url_type_test_() ->
  749. [
  750. ?_assertEqual(
  751. {ok, <<"http://some.server/">>},
  752. typerefl:from_string(emqx_schema:url(), <<"http://some.server/">>)
  753. ),
  754. ?_assertEqual(
  755. {ok, <<"http://192.168.0.1/">>},
  756. typerefl:from_string(emqx_schema:url(), <<"http://192.168.0.1">>)
  757. ),
  758. ?_assertEqual(
  759. {ok, <<"http://some.server/">>},
  760. typerefl:from_string(emqx_schema:url(), "http://some.server/")
  761. ),
  762. ?_assertEqual(
  763. {ok, <<"http://some.server/">>},
  764. typerefl:from_string(emqx_schema:url(), <<"http://some.server">>)
  765. ),
  766. ?_assertEqual(
  767. {ok, <<"http://some.server:9090/">>},
  768. typerefl:from_string(emqx_schema:url(), <<"http://some.server:9090">>)
  769. ),
  770. ?_assertEqual(
  771. {ok, <<"https://some.server:9090/">>},
  772. typerefl:from_string(emqx_schema:url(), <<"https://some.server:9090">>)
  773. ),
  774. ?_assertEqual(
  775. {ok, <<"https://some.server:9090/path?q=uery">>},
  776. typerefl:from_string(emqx_schema:url(), <<"https://some.server:9090/path?q=uery">>)
  777. ),
  778. ?_assertEqual(
  779. {error, {unsupported_scheme, <<"postgres">>}},
  780. typerefl:from_string(emqx_schema:url(), <<"postgres://some.server:9090">>)
  781. ),
  782. ?_assertEqual(
  783. {error, empty_host_not_allowed},
  784. typerefl:from_string(emqx_schema:url(), <<"">>)
  785. )
  786. ].
  787. env_test_() ->
  788. Do = fun emqx_schema:naive_env_interpolation/1,
  789. [
  790. {"undefined", fun() -> ?assertEqual(undefined, Do(undefined)) end},
  791. {"full env abs path",
  792. with_env_fn(
  793. "MY_FILE",
  794. "/path/to/my/file",
  795. fun() -> ?assertEqual("/path/to/my/file", Do("$MY_FILE")) end
  796. )},
  797. {"full env relative path",
  798. with_env_fn(
  799. "MY_FILE",
  800. "path/to/my/file",
  801. fun() -> ?assertEqual("path/to/my/file", Do("${MY_FILE}")) end
  802. )},
  803. %% we can not test windows style file join though
  804. {"windows style",
  805. with_env_fn(
  806. "MY_FILE",
  807. "path\\to\\my\\file",
  808. fun() -> ?assertEqual("path\\to\\my\\file", Do("$MY_FILE")) end
  809. )},
  810. {"dir no {}",
  811. with_env_fn(
  812. "MY_DIR",
  813. "/mydir",
  814. fun() -> ?assertEqual("/mydir/foobar", Do(<<"$MY_DIR/foobar">>)) end
  815. )},
  816. {"dir with {}",
  817. with_env_fn(
  818. "MY_DIR",
  819. "/mydir",
  820. fun() -> ?assertEqual("/mydir/foobar", Do(<<"${MY_DIR}/foobar">>)) end
  821. )},
  822. %% a trailing / should not cause the sub path to become absolute
  823. {"env dir with trailing /",
  824. with_env_fn(
  825. "MY_DIR",
  826. "/mydir//",
  827. fun() -> ?assertEqual("/mydir/foobar", Do(<<"${MY_DIR}/foobar">>)) end
  828. )},
  829. {"string dir with doulbe /",
  830. with_env_fn(
  831. "MY_DIR",
  832. "/mydir/",
  833. fun() -> ?assertEqual("/mydir/foobar", Do(<<"${MY_DIR}//foobar">>)) end
  834. )},
  835. {"env not found",
  836. with_env_fn(
  837. "MY_DIR",
  838. "/mydir/",
  839. fun() -> ?assertEqual("${MY_DIR2}//foobar", Do(<<"${MY_DIR2}//foobar">>)) end
  840. )}
  841. ].
  842. with_env_fn(Name, Value, F) ->
  843. fun() ->
  844. with_envs(F, [{Name, Value}])
  845. end.
  846. with_envs(Fun, Envs) ->
  847. with_envs(Fun, [], Envs).
  848. with_envs(Fun, Args, [{_Name, _Value} | _] = Envs) ->
  849. set_envs(Envs),
  850. try
  851. apply(Fun, Args)
  852. after
  853. unset_envs(Envs)
  854. end.
  855. set_envs([{_Name, _Value} | _] = Envs) ->
  856. lists:map(fun({Name, Value}) -> os:putenv(Name, Value) end, Envs).
  857. unset_envs([{_Name, _Value} | _] = Envs) ->
  858. lists:map(fun({Name, _}) -> os:unsetenv(Name) end, Envs).
  859. timeout_types_test_() ->
  860. [
  861. ?_assertEqual(
  862. {ok, 4294967295},
  863. typerefl:from_string(emqx_schema:timeout_duration(), <<"4294967295ms">>)
  864. ),
  865. ?_assertEqual(
  866. {ok, 4294967295},
  867. typerefl:from_string(emqx_schema:timeout_duration_ms(), <<"4294967295ms">>)
  868. ),
  869. ?_assertEqual(
  870. {ok, 4294967},
  871. typerefl:from_string(emqx_schema:timeout_duration_s(), <<"4294967000ms">>)
  872. ),
  873. ?_assertThrow(
  874. #{
  875. kind := validation_error,
  876. message := "timeout value too large (max: 4294967295 ms)",
  877. schema_module := emqx_schema
  878. },
  879. typerefl:from_string(emqx_schema:timeout_duration(), <<"4294967296ms">>)
  880. ),
  881. ?_assertThrow(
  882. #{
  883. kind := validation_error,
  884. message := "timeout value too large (max: 4294967295 ms)",
  885. schema_module := emqx_schema
  886. },
  887. typerefl:from_string(emqx_schema:timeout_duration_ms(), <<"4294967296ms">>)
  888. ),
  889. ?_assertThrow(
  890. #{
  891. kind := validation_error,
  892. message := "timeout value too large (max: 4294967 s)",
  893. schema_module := emqx_schema
  894. },
  895. typerefl:from_string(emqx_schema:timeout_duration_s(), <<"4294967001ms">>)
  896. )
  897. ].