Kaynağa Gözat

chore(emqx_management): update test case

zhanghongtong 5 yıl önce
ebeveyn
işleme
24d954282d

+ 1 - 1
lib-ce/emqx_management/src/emqx_management.app.src

@@ -3,7 +3,7 @@
   {vsn, "4.3.0"}, % strict semver, bump manually!
   {modules, []},
   {registered, [emqx_management_sup]},
-  {applications, [kernel,stdlib,minirest,emqx_modules]},
+  {applications, [kernel,stdlib,minirest]},
   {mod, {emqx_mgmt_app,[]}},
   {env, []},
   {licenses, ["Apache-2.0"]},

+ 0 - 26
lib-ce/emqx_management/src/emqx_mgmt.erl

@@ -483,16 +483,6 @@ delete_banned(Who) ->
     emqx_banned:delete(Who).
 
 
-any_to_atom(L) when is_list(L) -> list_to_atom(L);
-any_to_atom(B) when is_binary(B) -> binary_to_atom(B, utf8);
-any_to_atom(A) when is_atom(A) -> A.
-
-to_version(Version) when is_integer(Version) ->
-    integer_to_list(Version);
-to_version(Version) when is_binary(Version) ->
-    binary_to_list(Version);
-to_version(Version) when is_list(Version) ->
-    Version.
 
 %%--------------------------------------------------------------------
 %% Telemtry API
@@ -561,20 +551,4 @@ max_row_limit() ->
 
 table_size(Tab) -> ets:info(Tab, size).
 
-map_to_actions(Maps) ->
-    [map_to_action(M) || M <- Maps].
-
-map_to_action(Map = #{<<"id">> := ActionInstId, <<"name">> := Name, <<"args">> := Args}) ->
-    #{id => ActionInstId,
-      name => any_to_atom(Name),
-      args => Args,
-      fallbacks => map_to_actions(maps:get(<<"fallbacks">>, Map, []))}.
-
-actions_to_prop_list(Actions) ->
-    [action_to_prop_list(Act) || Act <- Actions].
 
-action_to_prop_list({action_instance, ActionInstId, Name, FallbackActions, Args}) ->
-    [{id, ActionInstId},
-     {name, Name},
-     {fallbacks, actions_to_prop_list(FallbackActions)},
-     {args, Args}].

+ 5 - 1
lib-ce/emqx_management/src/emqx_mgmt_api_data.erl

@@ -75,7 +75,11 @@
         ]).
 
 export(_Bindings, _Params) ->
-    return(emqx_mgmt_data_backup:export()).
+    case emqx_mgmt_data_backup:export() of
+        {ok, File = #{filename := Filename}} ->
+            return({ok, File#{filename => filename:basename(Filename)}});
+        Return -> return(Return)
+    end.
     
 list_exported(_Bindings, _Params) ->
     List = [ rpc:call(Node, ?MODULE, get_list_exported, []) || Node <- ekka_mnesia:running_nodes() ],

+ 7 - 11
lib-ce/emqx_management/src/emqx_mgmt_cli.erl

@@ -58,7 +58,7 @@
 -spec(load() -> ok).
 load() ->
     Cmds = [Fun || {Fun, _} <- ?MODULE:module_info(exports), is_cmd(Fun)],
-    lists:foreach(fun(Cmd) -> emqx_ctl:register_command(Cmd, {?MODULE, Cmd}, []) end, Cmds).
+    foreach(fun(Cmd) -> emqx_ctl:register_command(Cmd, {?MODULE, Cmd}, []) end, Cmds).
 
 is_cmd(Fun) ->
     not lists:member(Fun, [init, load, module_info]).
@@ -100,7 +100,7 @@ mgmt(["delete", AppId]) ->
     end;
 
 mgmt(["list"]) ->
-    lists:foreach(fun({AppId, AppSecret, Name, Desc, Status, Expired}) ->
+    foreach(fun({AppId, AppSecret, Name, Desc, Status, Expired}) ->
                       emqx_ctl:print("app_id: ~s, secret: ~s, name: ~s, desc: ~s, status: ~s, expired: ~p~n",
                                     [AppId, AppSecret, Name, Desc, Status, Expired])
                   end, emqx_mgmt_auth:list_apps());
@@ -229,7 +229,7 @@ routes(_) ->
                     {"routes show <Topic>", "Show a route"}]).
 
 subscriptions(["list"]) ->
-    lists:foreach(fun(Suboption) ->
+    foreach(fun(Suboption) ->
                         print({emqx_suboption, Suboption})
                   end, ets:tab2list(emqx_suboption));
 
@@ -544,8 +544,8 @@ stop_listener(#{listen_on := ListenOn} = Listener, _Input) ->
 
 data(["export"]) ->
     case emqx_mgmt_data_backup:export() of
-        {ok, _} ->
-            emqx_ctl:print("The emqx data has been successfully exported to ~s.~n", [NFilename]);
+        {ok, #{filename := Filename}} ->
+            emqx_ctl:print("The emqx data has been successfully exported to ~s.~n", [Filename]);
         {error, Reason} ->
             emqx_ctl:print("The emqx data export failed due to ~p.~n", [Reason])
     end;    
@@ -555,9 +555,9 @@ data(["import", Filename]) ->
         ok ->
             emqx_ctl:print("The emqx data has been imported successfully.~n");
         {error, import_failed} -> 
-            emqx_ctl:print("The emqx data import failed due: ~0p~n", [{Class,Reason,Stack}]);
+            emqx_ctl:print("The emqx data import failed. ~n");
         {error, unsupported_version} ->
-            emqx_ctl:print("Unsupported version: ~p~n", [Version]);
+            emqx_ctl:print("The emqx data import failed: Unsupported version. ~n");
         {error, Reason} ->
             emqx_ctl:print("The emqx data import failed: ~0p while reading ~s.~n", [Reason, Filename])
     end;
@@ -636,10 +636,6 @@ print(#plugin{name = Name, descr = Descr, active = Active}) ->
     emqx_ctl:print("Plugin(~s, description=~s, active=~s)~n",
                   [Name, Descr, Active]);
 
-print({module, {Name, Active}}) ->
-    emqx_ctl:print("Module(~s, description=~s, active=~s)~n",
-                  [Name, Name:description(), Active]);
-
 print({emqx_suboption, {{Pid, Topic}, Options}}) when is_pid(Pid) ->
     emqx_ctl:print("~s -> ~s~n", [maps:get(subid, Options), Topic]).
 

+ 41 - 12
lib-ce/emqx_management/src/emqx_mgmt_data_backup.erl

@@ -16,6 +16,10 @@
 
 -module(emqx_mgmt_data_backup).
 
+-include("emqx_mgmt.hrl").
+-include_lib("emqx/include/emqx.hrl").
+-include_lib("kernel/include/file.hrl").
+
 -ifdef(EMQX_ENTERPISE).
 -export([ export_modules/0
         , export_schemas/0
@@ -266,6 +270,14 @@ apply_new_config([Action = #{<<"name">> := <<"data_to_webserver">>,
             apply_new_config(More, Configs, [Action#{<<"args">> := Args} | Acc])
     end.
 
+actions_to_prop_list(Actions) ->
+    [action_to_prop_list(Act) || Act <- Actions].
+
+action_to_prop_list({action_instance, ActionInstId, Name, FallbackActions, Args}) ->
+    [{id, ActionInstId},
+     {name, Name},
+     {fallbacks, actions_to_prop_list(FallbackActions)},
+     {args, Args}].
 
 -endif.
 
@@ -415,6 +427,26 @@ import_confs(Configs, ListenersState) ->
 
 -endif.
 
+any_to_atom(L) when is_list(L) -> list_to_atom(L);
+any_to_atom(B) when is_binary(B) -> binary_to_atom(B, utf8);
+any_to_atom(A) when is_atom(A) -> A.
+
+map_to_actions(Maps) ->
+    [map_to_action(M) || M <- Maps].
+
+map_to_action(Map = #{<<"id">> := ActionInstId, <<"name">> := Name, <<"args">> := Args}) ->
+    #{id => ActionInstId,
+      name => any_to_atom(Name),
+      args => Args,
+      fallbacks => map_to_actions(maps:get(<<"fallbacks">>, Map, []))}.
+
+to_version(Version) when is_integer(Version) ->
+    integer_to_list(Version);
+to_version(Version) when is_binary(Version) ->
+    binary_to_list(Version);
+to_version(Version) when is_list(Version) ->
+    Version.
+
 -ifdef(EMQX_ENTERPRISE).
 export() ->
     Modules = export_modules(),
@@ -507,7 +539,7 @@ export() ->
     write_file(NFilename, Data). 
     
 import(Filename) ->
-    case file:read_file(FullFilename) of
+    case file:read_file(Filename) of
         {ok, Json} ->
             Data = emqx_json:decode(Json, [return_maps]),
             Version = to_version(maps:get(<<"version">>, Data)),
@@ -532,8 +564,7 @@ import(Filename) ->
                     logger:error("Unsupported version: ~p", [Version]),
                     {error, unsupported_version}
             end;
-        {error, Reason} ->
-            {error, Reason}
+        Error -> Error
     end.
 -endif.
 
@@ -544,14 +575,12 @@ write_file(Filename, Data) ->
             case file:read_file_info(Filename) of
                 {ok, #file_info{size = Size, ctime = {{Y, M, D}, {H, MM, S}}}} ->
                     CreatedAt = io_lib:format("~p-~p-~p ~p:~p:~p", [Y, M, D, H, MM, S]),
-                    {ok, [{filename, list_to_binary(Filename)},
-                          {size, Size},
-                          {created_at, list_to_binary(CreatedAt)},
-                          {node, node()}
-                         ]};
-                {error, Reason} ->
-                    {error, Reason}
+                    {ok, #{filename => list_to_binary(Filename),
+                           size => Size,
+                           created_at => list_to_binary(CreatedAt),
+                           node => node()
+                          }};
+                Error -> Error
             end;
-        {error, Reason} ->
-            {error, Reason}
+        Error -> Error
     end.

+ 2 - 16
lib-ce/emqx_management/test/emqx_mgmt_SUITE.erl

@@ -44,7 +44,6 @@ groups() ->
         t_clients_cmd,
         t_vm_cmd,
         t_plugins_cmd,
-        t_modules_cmd,
         t_trace_cmd,
         t_broker_cmd,
         t_router_cmd,
@@ -59,11 +58,11 @@ apps() ->
 init_per_suite(Config) ->
     ekka_mnesia:start(),
     emqx_mgmt_auth:mnesia(boot),
-    emqx_ct_helpers:start_apps([emqx_modules, emqx_management, emqx_auth_mnesia]),
+    emqx_ct_helpers:start_apps([emqx_management, emqx_auth_mnesia]),
     Config.
 
 end_per_suite(_Config) ->
-    emqx_ct_helpers:stop_apps([emqx_management, emqx_auth_mnesia, emqx_modules]).
+    emqx_ct_helpers:stop_apps([emqx_management, emqx_auth_mnesia]).
 
 t_app(_Config) ->
     {ok, AppSecret} = emqx_mgmt_auth:add_app(<<"app_id">>, <<"app_name">>),
@@ -329,19 +328,6 @@ t_plugins_cmd(_) ->
       ),
     unmock_print().
 
-t_modules_cmd(_) ->
-    mock_print(),
-    meck:new(emqx_modules, [non_strict, passthrough]),
-    meck:expect(emqx_modules, load, fun(_) -> ok end),
-    meck:expect(emqx_modules, unload, fun(_) -> ok end),
-    meck:expect(emqx_modules, reload, fun(_) -> ok end),
-    ?assertEqual(emqx_mgmt_cli:modules(["list"]), ok),
-    ?assertEqual(emqx_mgmt_cli:modules(["load", "emqx_mod_presence"]),
-                 "Module emqx_mod_presence loaded successfully.\n"),
-    ?assertEqual(emqx_mgmt_cli:modules(["unload", "emqx_mod_presence"]),
-                 "Module emqx_mod_presence unloaded successfully.\n"),
-    unmock_print().
-
 t_cli(_) ->
     mock_print(),
     ?assertMatch({match, _}, re:run(emqx_mgmt_cli:status([""]), "status")),

+ 2 - 61
lib-ce/emqx_management/test/emqx_mgmt_api_SUITE.erl

@@ -48,7 +48,6 @@ groups() ->
       , metrics
       , nodes
       , plugins
-      , modules
       , acl_cache
       , pubsub
       , routes_and_subscriptions
@@ -58,13 +57,13 @@ groups() ->
     }].
 
 init_per_suite(Config) ->
-    emqx_ct_helpers:start_apps([emqx_modules, emqx_management, emqx_auth_mnesia]),
+    emqx_ct_helpers:start_apps([emqx_management, emqx_auth_mnesia]),
     ekka_mnesia:start(),
     emqx_mgmt_auth:mnesia(boot),
     Config.
 
 end_per_suite(_Config) ->
-    emqx_ct_helpers:stop_apps([emqx_auth_mnesia, emqx_management, emqx_modules]),
+    emqx_ct_helpers:stop_apps([emqx_auth_mnesia, emqx_management]),
     ekka_mnesia:ensure_stopped().
 
 init_per_testcase(data, Config) ->
@@ -382,64 +381,6 @@ plugins(_) ->
                                auth_header_()),
     ?assertEqual(<<"not_started">>, get(<<"message">>, Error2)).
 
-modules(_) ->
-    emqx_modules:load_module(emqx_mod_presence, false),
-    timer:sleep(50),
-    {ok, Modules1} = request_api(get, api_path(["modules"]), auth_header_()),
-    [Modules11] = filter(get(<<"data">>, Modules1), <<"node">>, atom_to_binary(node(), utf8)),
-    [Module1] = filter(maps:get(<<"modules">>, Modules11), <<"name">>, <<"emqx_mod_presence">>),
-    ?assertEqual(<<"emqx_mod_presence">>, maps:get(<<"name">>, Module1)),
-    ?assertEqual(true, maps:get(<<"active">>, Module1)),
-
-    {ok, _} = request_api(put,
-                          api_path(["modules",
-                                    atom_to_list(emqx_mod_presence),
-                                    "unload"]),
-                          auth_header_()),
-    {ok, Error1} = request_api(put,
-                               api_path(["modules",
-                                         atom_to_list(emqx_mod_presence),
-                                         "unload"]),
-                               auth_header_()),
-    ?assertEqual(<<"not_started">>, get(<<"message">>, Error1)),
-    {ok, Modules2} = request_api(get,
-                                 api_path(["nodes", atom_to_list(node()), "modules"]),
-                                 auth_header_()),
-    [Module2] = filter(get(<<"data">>, Modules2), <<"name">>, <<"emqx_mod_presence">>),
-    ?assertEqual(<<"emqx_mod_presence">>, maps:get(<<"name">>, Module2)),
-    ?assertEqual(false, maps:get(<<"active">>, Module2)),
-
-    {ok, _} = request_api(put,
-                          api_path(["nodes",
-                                    atom_to_list(node()),
-                                    "modules",
-                                    atom_to_list(emqx_mod_presence),
-                                    "load"]),
-                          auth_header_()),
-    {ok, Modules3} = request_api(get,
-                                 api_path(["nodes", atom_to_list(node()), "modules"]),
-                                 auth_header_()),
-    [Module3] = filter(get(<<"data">>, Modules3), <<"name">>, <<"emqx_mod_presence">>),
-    ?assertEqual(<<"emqx_mod_presence">>, maps:get(<<"name">>, Module3)),
-    ?assertEqual(true, maps:get(<<"active">>, Module3)),
-
-    {ok, _} = request_api(put,
-                          api_path(["nodes",
-                                    atom_to_list(node()),
-                                    "modules",
-                                    atom_to_list(emqx_mod_presence),
-                                    "unload"]),
-                          auth_header_()),
-    {ok, Error2} = request_api(put,
-                               api_path(["nodes",
-                                         atom_to_list(node()),
-                                         "modules",
-                                         atom_to_list(emqx_mod_presence),
-                                         "unload"]),
-                               auth_header_()),
-    ?assertEqual(<<"not_started">>, get(<<"message">>, Error2)),
-    emqx_modules:unload(emqx_mod_presence).
-
 acl_cache(_) ->
     ClientId = <<"client1">>,
     Topic = <<"mytopic">>,

+ 4 - 1
lib-ce/emqx_modules/src/emqx_modules.erl

@@ -176,7 +176,10 @@ write_loaded(false) -> ok.
 %%--------------------------------------------------------------------
 %% @doc Modules Command
 cli(["list"]) ->
-    foreach(fun(Module) -> print({module, Module}) end, emqx_modules:list());
+    lists:foreach(fun({Name, Active}) -> 
+                    emqx_ctl:print("Module(~s, description=~s, active=~s)~n",
+                        [Name, Name:description(), Active])
+                  end, emqx_modules:list());
 
 cli(["load", Name]) ->
     case emqx_modules:load(list_to_atom(Name)) of

+ 15 - 11
lib-ce/emqx_modules/src/emqx_modules_api.erl

@@ -72,6 +72,10 @@
         , reload/2
         ]).
 
+-export([ do_load_module/2
+        , do_unload_module/2
+        ]).
+
 list(#{node := Node}, _Params) ->
     return({ok, [format(Module) || Module <- list_modules(Node)]});
 
@@ -79,10 +83,10 @@ list(_Bindings, _Params) ->
     return({ok, [format(Node, Modules) || {Node, Modules} <- list_modules()]}).
 
 load(#{node := Node, module := Module}, _Params) ->
-    return(load_module(Node, Module));
+    return(do_load_module(Node, Module));
 
 load(#{module := Module}, _Params) ->
-    Results = [load_module(Node, Module) || {Node, _Info} <- list_nodes()],
+    Results = [do_load_module(Node, Module) || {Node, _Info} <- emqx_mgmt:list_nodes()],
     case lists:filter(fun(Item) -> Item =/= ok end, Results) of
         [] ->
             return(ok);
@@ -91,10 +95,10 @@ load(#{module := Module}, _Params) ->
     end.
 
 unload(#{node := Node, module := Module}, _Params) ->
-    return(unload_module(Node, Module));
+    return(do_unload_module(Node, Module));
 
 unload(#{module := Module}, _Params) ->
-    Results = [unload_module(Node, Module) || {Node, _Info} <- list_nodes()],
+    Results = [do_unload_module(Node, Module) || {Node, _Info} <- emqx_mgmt:list_nodes()],
     case lists:filter(fun(Item) -> Item =/= ok end, Results) of
         [] ->
             return(ok);
@@ -109,7 +113,7 @@ reload(#{node := Node, module := Module}, _Params) ->
     end;
 
 reload(#{module := Module}, _Params) ->
-    Results = [reload_module(Node, Module) || {Node, _Info} <- list_nodes()],
+    Results = [reload_module(Node, Module) || {Node, _Info} <- emqx_mgmt:list_nodes()],
     case lists:filter(fun(Item) -> Item =/= ok end, Results) of
         [] ->
             return(ok);
@@ -137,15 +141,15 @@ list_modules(Node) when Node =:= node() ->
 list_modules(Node) ->
     rpc_call(Node, list_modules, [Node]).
 
-load_module(Node, Module) when Node =:= node() ->
+do_load_module(Node, Module) when Node =:= node() ->
     emqx_modules:load(Module);
-load_module(Node, Module) ->
-    rpc_call(Node, load_module, [Node, Module]).
+do_load_module(Node, Module) ->
+    rpc_call(Node, do_load_module, [Node, Module]).
 
-unload_module(Node, Module) when Node =:= node() ->
+do_unload_module(Node, Module) when Node =:= node() ->
     emqx_modules:unload(Module);
-unload_module(Node, Module) ->
-    rpc_call(Node, unload_module, [Node, Module]).
+do_unload_module(Node, Module) ->
+    rpc_call(Node, do_unload_module, [Node, Module]).
 
 reload_module(Node, Module) when Node =:= node() ->
     emqx_modules:reload(Module);

+ 145 - 2
lib-ce/emqx_modules/test/emqx_modules_SUITE.erl

@@ -21,10 +21,19 @@
 
 -include_lib("eunit/include/eunit.hrl").
 
+-define(CONTENT_TYPE, "application/x-www-form-urlencoded").
+
+-define(HOST, "http://127.0.0.1:8081/").
+
+-define(API_VERSION, "v4").
+
+-define(BASE_PATH, "api").
+
 all() -> emqx_ct:all(?MODULE).
 
 init_per_suite(Config) ->
-    emqx_ct_helpers:start_apps([emqx_modules], fun set_sepecial_cfg/1),
+    emqx_ct_helpers:start_apps([emqx_management, emqx_modules], fun set_sepecial_cfg/1),
+    emqx_ct_http:create_default_app(),
     Config.
 
 set_sepecial_cfg(_) ->
@@ -32,7 +41,8 @@ set_sepecial_cfg(_) ->
     ok.
 
 end_per_suite(_Config) ->
-    emqx_ct_helpers:stop_apps([emqx_modules]).
+    emqx_ct_http:delete_default_app(),
+    emqx_ct_helpers:stop_apps([emqx_modules, emqx_management]).
 
 t_load(_) ->
     ?assertEqual(ok, emqx_modules:unload()),
@@ -45,3 +55,136 @@ t_load(_) ->
 t_list(_) ->
     ?assertMatch([{_, _} | _ ], emqx_modules:list()).
 
+t_modules_api(_) ->
+    emqx_modules:load_module(emqx_mod_presence, false),
+    timer:sleep(50),
+    {ok, Modules1} = request_api(get, api_path(["modules"]), auth_header_()),
+    [Modules11] = filter(get(<<"data">>, Modules1), <<"node">>, atom_to_binary(node(), utf8)),
+    [Module1] = filter(maps:get(<<"modules">>, Modules11), <<"name">>, <<"emqx_mod_presence">>),
+    ?assertEqual(<<"emqx_mod_presence">>, maps:get(<<"name">>, Module1)),
+    ?assertEqual(true, maps:get(<<"active">>, Module1)),
+
+    {ok, _} = request_api(put,
+                          api_path(["modules",
+                                    atom_to_list(emqx_mod_presence),
+                                    "unload"]),
+                          auth_header_()),
+    {ok, Error1} = request_api(put,
+                               api_path(["modules",
+                                         atom_to_list(emqx_mod_presence),
+                                         "unload"]),
+                               auth_header_()),
+    ?assertEqual(<<"not_started">>, get(<<"message">>, Error1)),
+    {ok, Modules2} = request_api(get,
+                                 api_path(["nodes", atom_to_list(node()), "modules"]),
+                                 auth_header_()),
+    [Module2] = filter(get(<<"data">>, Modules2), <<"name">>, <<"emqx_mod_presence">>),
+    ?assertEqual(<<"emqx_mod_presence">>, maps:get(<<"name">>, Module2)),
+    ?assertEqual(false, maps:get(<<"active">>, Module2)),
+
+    {ok, _} = request_api(put,
+                          api_path(["nodes",
+                                    atom_to_list(node()),
+                                    "modules",
+                                    atom_to_list(emqx_mod_presence),
+                                    "load"]),
+                          auth_header_()),
+    {ok, Modules3} = request_api(get,
+                                 api_path(["nodes", atom_to_list(node()), "modules"]),
+                                 auth_header_()),
+    [Module3] = filter(get(<<"data">>, Modules3), <<"name">>, <<"emqx_mod_presence">>),
+    ?assertEqual(<<"emqx_mod_presence">>, maps:get(<<"name">>, Module3)),
+    ?assertEqual(true, maps:get(<<"active">>, Module3)),
+
+    {ok, _} = request_api(put,
+                          api_path(["nodes",
+                                    atom_to_list(node()),
+                                    "modules",
+                                    atom_to_list(emqx_mod_presence),
+                                    "unload"]),
+                          auth_header_()),
+    {ok, Error2} = request_api(put,
+                               api_path(["nodes",
+                                         atom_to_list(node()),
+                                         "modules",
+                                         atom_to_list(emqx_mod_presence),
+                                         "unload"]),
+                               auth_header_()),
+    ?assertEqual(<<"not_started">>, get(<<"message">>, Error2)),
+    emqx_modules:unload(emqx_mod_presence).
+
+
+t_modules_cmd(_) ->
+    mock_print(),
+    meck:new(emqx_modules, [non_strict, passthrough]),
+    meck:expect(emqx_modules, load, fun(_) -> ok end),
+    meck:expect(emqx_modules, unload, fun(_) -> ok end),
+    meck:expect(emqx_modules, reload, fun(_) -> ok end),
+    ?assertEqual(emqx_modules:cli(["list"]), ok),
+    ?assertEqual(emqx_modules:cli(["load", "emqx_mod_presence"]),
+                 "Module emqx_mod_presence loaded successfully.\n"),
+    ?assertEqual(emqx_modules:cli(["unload", "emqx_mod_presence"]),
+                 "Module emqx_mod_presence unloaded successfully.\n"),
+    unmock_print().
+
+mock_print() ->
+    catch meck:unload(emqx_ctl),
+    meck:new(emqx_ctl, [non_strict, passthrough]),
+    meck:expect(emqx_ctl, print, fun(Arg) -> emqx_ctl:format(Arg) end),
+    meck:expect(emqx_ctl, print, fun(Msg, Arg) -> emqx_ctl:format(Msg, Arg) end),
+    meck:expect(emqx_ctl, usage, fun(Usages) -> emqx_ctl:format_usage(Usages) end),
+    meck:expect(emqx_ctl, usage, fun(Cmd, Descr) -> emqx_ctl:format_usage(Cmd, Descr) end).
+
+unmock_print() ->
+    meck:unload(emqx_ctl).
+
+get(Key, ResponseBody) ->
+   maps:get(Key, jiffy:decode(list_to_binary(ResponseBody), [return_maps])).
+
+request_api(Method, Url, Auth) ->
+    request_api(Method, Url, [], Auth, []).
+
+request_api(Method, Url, QueryParams, Auth) ->
+    request_api(Method, Url, QueryParams, Auth, []).
+
+request_api(Method, Url, QueryParams, Auth, []) ->
+    NewUrl = case QueryParams of
+                 "" -> Url;
+                 _ -> Url ++ "?" ++ QueryParams
+             end,
+    do_request_api(Method, {NewUrl, [Auth]});
+request_api(Method, Url, QueryParams, Auth, Body) ->
+    NewUrl = case QueryParams of
+                 "" -> Url;
+                 _ -> Url ++ "?" ++ QueryParams
+             end,
+    do_request_api(Method, {NewUrl, [Auth], "application/json", emqx_json:encode(Body)}).
+
+do_request_api(Method, Request)->
+    ct:pal("Method: ~p, Request: ~p", [Method, Request]),
+    case httpc:request(Method, Request, [], []) of
+        {error, socket_closed_remotely} ->
+            {error, socket_closed_remotely};
+        {ok, {{"HTTP/1.1", Code, _}, _, Return} }
+            when Code =:= 200 orelse Code =:= 201 ->
+            {ok, Return};
+        {ok, {Reason, _, _}} ->
+            {error, Reason}
+    end.
+
+auth_header_() ->
+    AppId = <<"admin">>,
+    AppSecret = <<"public">>,
+    auth_header_(binary_to_list(AppId), binary_to_list(AppSecret)).
+
+auth_header_(User, Pass) ->
+    Encoded = base64:encode_to_string(lists:append([User,":",Pass])),
+    {"Authorization","Basic " ++ Encoded}.
+
+api_path(Parts)->
+    ?HOST ++ filename:join([?BASE_PATH, ?API_VERSION] ++ Parts).
+
+filter(List, Key, Value) ->
+    lists:filter(fun(Item) ->
+        maps:get(Key, Item) == Value
+    end, List).