From 24d954282d1d9b53e1301ebcadd9c4f9688b76f8 Mon Sep 17 00:00:00 2001 From: zhanghongtong Date: Thu, 25 Feb 2021 14:10:30 +0800 Subject: [PATCH] chore(emqx_management): update test case --- .../src/emqx_management.app.src | 2 +- lib-ce/emqx_management/src/emqx_mgmt.erl | 26 ---- .../src/emqx_mgmt_api_data.erl | 6 +- lib-ce/emqx_management/src/emqx_mgmt_cli.erl | 18 +-- .../src/emqx_mgmt_data_backup.erl | 53 +++++-- .../emqx_management/test/emqx_mgmt_SUITE.erl | 18 +-- .../test/emqx_mgmt_api_SUITE.erl | 63 +------- lib-ce/emqx_modules/src/emqx_modules.erl | 5 +- lib-ce/emqx_modules/src/emqx_modules_api.erl | 26 ++-- .../emqx_modules/test/emqx_modules_SUITE.erl | 147 +++++++++++++++++- 10 files changed, 222 insertions(+), 142 deletions(-) diff --git a/lib-ce/emqx_management/src/emqx_management.app.src b/lib-ce/emqx_management/src/emqx_management.app.src index 6f29fb4ff..b1e04c439 100644 --- a/lib-ce/emqx_management/src/emqx_management.app.src +++ b/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"]}, diff --git a/lib-ce/emqx_management/src/emqx_mgmt.erl b/lib-ce/emqx_management/src/emqx_mgmt.erl index e7d896c6a..39bc2153a 100644 --- a/lib-ce/emqx_management/src/emqx_mgmt.erl +++ b/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}]. diff --git a/lib-ce/emqx_management/src/emqx_mgmt_api_data.erl b/lib-ce/emqx_management/src/emqx_mgmt_api_data.erl index d3d22e4d1..18ba27265 100644 --- a/lib-ce/emqx_management/src/emqx_mgmt_api_data.erl +++ b/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() ], diff --git a/lib-ce/emqx_management/src/emqx_mgmt_cli.erl b/lib-ce/emqx_management/src/emqx_mgmt_cli.erl index d2712b5e1..19cf83f99 100644 --- a/lib-ce/emqx_management/src/emqx_mgmt_cli.erl +++ b/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 ", "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]). diff --git a/lib-ce/emqx_management/src/emqx_mgmt_data_backup.erl b/lib-ce/emqx_management/src/emqx_mgmt_data_backup.erl index c2e4be94e..03703be91 100644 --- a/lib-ce/emqx_management/src/emqx_mgmt_data_backup.erl +++ b/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. diff --git a/lib-ce/emqx_management/test/emqx_mgmt_SUITE.erl b/lib-ce/emqx_management/test/emqx_mgmt_SUITE.erl index 56db2d118..a3e9a6fad 100644 --- a/lib-ce/emqx_management/test/emqx_mgmt_SUITE.erl +++ b/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")), diff --git a/lib-ce/emqx_management/test/emqx_mgmt_api_SUITE.erl b/lib-ce/emqx_management/test/emqx_mgmt_api_SUITE.erl index fa063d73a..401da3009 100644 --- a/lib-ce/emqx_management/test/emqx_mgmt_api_SUITE.erl +++ b/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">>, diff --git a/lib-ce/emqx_modules/src/emqx_modules.erl b/lib-ce/emqx_modules/src/emqx_modules.erl index 38cf2ac1b..74a6c1256 100644 --- a/lib-ce/emqx_modules/src/emqx_modules.erl +++ b/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 diff --git a/lib-ce/emqx_modules/src/emqx_modules_api.erl b/lib-ce/emqx_modules/src/emqx_modules_api.erl index fae6d0e5e..6b8a67406 100644 --- a/lib-ce/emqx_modules/src/emqx_modules_api.erl +++ b/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); diff --git a/lib-ce/emqx_modules/test/emqx_modules_SUITE.erl b/lib-ce/emqx_modules/test/emqx_modules_SUITE.erl index b0ff65758..cc4ea6662 100644 --- a/lib-ce/emqx_modules/test/emqx_modules_SUITE.erl +++ b/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).