emqx_variform_tests.erl 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 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_variform_tests).
  17. -compile(export_all).
  18. -compile(nowarn_export_all).
  19. -include_lib("eunit/include/eunit.hrl").
  20. -define(SYNTAX_ERROR, {error, "syntax error before:" ++ _}).
  21. redner_test_() ->
  22. [
  23. {"direct var reference", fun() -> ?assertEqual({ok, <<"1">>}, render("a", #{a => 1})) end},
  24. {"concat strings", fun() ->
  25. ?assertEqual({ok, <<"a,b">>}, render("concat(['a',',','b'])", #{}))
  26. end},
  27. {"concat empty string", fun() ->
  28. ?assertEqual({ok, <<"">>}, render("concat([''])", #{}))
  29. end},
  30. {"tokens 1st", fun() ->
  31. ?assertEqual({ok, <<"a">>}, render("nth(1,tokens(var, ','))", #{var => <<"a,b">>}))
  32. end},
  33. {"unknown var return error", fun() ->
  34. ?assertMatch({error, #{reason := var_unbound}}, render("var", #{}))
  35. end},
  36. {"out of range nth index", fun() ->
  37. ?assertEqual({ok, <<>>}, render("nth(2, tokens(var, ','))", #{var => <<"a">>}))
  38. end},
  39. {"string for nth index", fun() ->
  40. ?assertEqual({ok, <<"a">>}, render("nth('1', tokens(var, ','))", #{var => <<"a">>}))
  41. end},
  42. {"not a index number for nth", fun() ->
  43. ?assertMatch(
  44. {error, #{reason := invalid_argument, func := nth, index := <<"notnum">>}},
  45. render("nth('notnum', tokens(var, ','))", #{var => <<"a">>})
  46. )
  47. end},
  48. {"substr", fun() ->
  49. ?assertMatch(
  50. {ok, <<"b">>},
  51. render("substr(var,1)", #{var => <<"ab">>})
  52. )
  53. end},
  54. {"result in integer", fun() ->
  55. ?assertMatch(
  56. {ok, <<"2">>},
  57. render("strlen(var)", #{var => <<"ab">>})
  58. )
  59. end},
  60. {"result in float", fun() ->
  61. ?assertMatch(
  62. {ok, <<"2.2">>},
  63. render("var", #{var => 2.2})
  64. )
  65. end},
  66. {"concat a number", fun() ->
  67. ?assertMatch(
  68. {ok, <<"2.2">>},
  69. render("concat(strlen(var),'.2')", #{var => <<"xy">>})
  70. )
  71. end},
  72. {"var is an array", fun() ->
  73. ?assertMatch(
  74. {ok, <<"y">>},
  75. render("nth(2,var)", #{var => [<<"x">>, <<"y">>]})
  76. )
  77. end}
  78. ].
  79. unknown_func_test_() ->
  80. [
  81. {"unknown function", fun() ->
  82. ?assertMatch(
  83. {error, #{reason := unknown_variform_function}},
  84. render("nonexistingatom__(a)", #{})
  85. )
  86. end},
  87. {"unknown module", fun() ->
  88. ?assertMatch(
  89. {error, #{reason := unknown_variform_module}},
  90. render("nonexistingatom__.nonexistingatom__(a)", #{})
  91. )
  92. end},
  93. {"unknown function in a known module", fun() ->
  94. ?assertMatch(
  95. {error, #{reason := unknown_variform_function}},
  96. render("emqx_variform_bif.nonexistingatom__(a)", #{})
  97. )
  98. end},
  99. {"invalid func reference", fun() ->
  100. ?assertMatch(
  101. {error, #{reason := invalid_function_reference, function := "a.b.c"}},
  102. render("a.b.c(var)", #{})
  103. )
  104. end}
  105. ].
  106. concat(L) -> iolist_to_binary(L).
  107. inject_allowed_module_test() ->
  108. try
  109. emqx_variform:inject_allowed_module(?MODULE),
  110. ?assertEqual({ok, <<"ab">>}, render(atom_to_list(?MODULE) ++ ".concat(['a','b'])", #{})),
  111. ?assertMatch(
  112. {error, #{
  113. reason := unknown_variform_function,
  114. module := ?MODULE,
  115. function := concat,
  116. arity := 2
  117. }},
  118. render(atom_to_list(?MODULE) ++ ".concat('a','b')", #{})
  119. ),
  120. ?assertMatch(
  121. {error, #{reason := unallowed_veriform_module, module := emqx}},
  122. render("emqx.concat('a','b')", #{})
  123. )
  124. after
  125. emqx_variform:erase_allowed_module(?MODULE)
  126. end.
  127. coalesce_test_() ->
  128. [
  129. {"first", fun() ->
  130. ?assertEqual({ok, <<"a">>}, render("coalesce(['a','b'])", #{}))
  131. end},
  132. {"second", fun() ->
  133. ?assertEqual({ok, <<"b">>}, render("coalesce(['', 'b'])", #{}))
  134. end},
  135. {"first var", fun() ->
  136. ?assertEqual({ok, <<"a">>}, render("coalesce([a,b])", #{a => <<"a">>, b => <<"b">>}))
  137. end},
  138. {"second var", fun() ->
  139. ?assertEqual({ok, <<"b">>}, render("coalesce([a,b])", #{b => <<"b">>}))
  140. end},
  141. {"empty", fun() -> ?assertEqual({ok, <<>>}, render("coalesce([a,b])", #{})) end},
  142. {"arg from other func", fun() ->
  143. ?assertEqual({ok, <<"b">>}, render("coalesce(tokens(a,','))", #{a => <<",,b,c">>}))
  144. end},
  145. {"var unbound", fun() -> ?assertEqual({ok, <<>>}, render("coalesce(a)", #{})) end},
  146. {"var unbound in call", fun() ->
  147. ?assertEqual({ok, <<>>}, render("coalesce(concat(a))", #{}))
  148. end},
  149. {"var unbound in calls", fun() ->
  150. ?assertEqual({ok, <<"c">>}, render("coalesce([any_to_str(a),any_to_str(b),'c'])", #{}))
  151. end},
  152. {"badarg", fun() ->
  153. ?assertMatch(
  154. {error, #{reason := coalesce_badarg}}, render("coalesce(a,b)", #{a => 1, b => 2})
  155. )
  156. end},
  157. {"badarg from return", fun() ->
  158. ?assertMatch(
  159. {error, #{reason := coalesce_badarg}}, render("coalesce(any_to_str(a))", #{a => 1})
  160. )
  161. end}
  162. ].
  163. syntax_error_test_() ->
  164. [
  165. {"empty expression", fun() -> ?assertMatch(?SYNTAX_ERROR, render("", #{})) end},
  166. {"const string single quote", fun() -> ?assertMatch(?SYNTAX_ERROR, render("'a'", #{})) end},
  167. {"const string double quote", fun() ->
  168. ?assertMatch(?SYNTAX_ERROR, render(<<"\"a\"">>, #{}))
  169. end},
  170. {"no arity", fun() -> ?assertMatch(?SYNTAX_ERROR, render("concat()", #{})) end}
  171. ].
  172. render(Expression, Bindings) ->
  173. emqx_variform:render(Expression, Bindings).
  174. hash_pick_test() ->
  175. lists:foreach(
  176. fun(_) ->
  177. {ok, Res} = render("nth(hash_to_range(rand_str(10),1,5),[1,2,3,4,5])", #{}),
  178. ?assert(Res >= <<"1">> andalso Res =< <<"5">>)
  179. end,
  180. lists:seq(1, 100)
  181. ).
  182. map_to_range_pick_test() ->
  183. lists:foreach(
  184. fun(_) ->
  185. {ok, Res} = render("nth(map_to_range(rand_str(10),1,5),[1,2,3,4,5])", #{}),
  186. ?assert(Res >= <<"1">> andalso Res =< <<"5">>)
  187. end,
  188. lists:seq(1, 100)
  189. ).
  190. -define(ASSERT_BADARG(FUNC, ARGS),
  191. ?_assertEqual(
  192. {error, #{reason => badarg, function => FUNC}},
  193. render(atom_to_list(FUNC) ++ ARGS, #{})
  194. )
  195. ).
  196. to_range_badarg_test_() ->
  197. [
  198. ?ASSERT_BADARG(hash_to_range, "(1,1,2)"),
  199. ?ASSERT_BADARG(hash_to_range, "('',1,2)"),
  200. ?ASSERT_BADARG(hash_to_range, "('a','1',2)"),
  201. ?ASSERT_BADARG(hash_to_range, "('a',2,1)"),
  202. ?ASSERT_BADARG(map_to_range, "('',1,2)"),
  203. ?ASSERT_BADARG(map_to_range, "('a','1',2)"),
  204. ?ASSERT_BADARG(map_to_range, "('a',2,1)")
  205. ].