%%%------------------------------------------------------------------- %%% Author : Badlop %%% Created : 2 Jul 2024 by Badlop %%% %%% %%% ejabberd, Copyright (C) 2002-2025 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as %%% published by the Free Software Foundation; either version 2 of the %%% License, or (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU %%% General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License along %%% with this program; if not, write to the Free Software Foundation, Inc., %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% %%%------------------------------------------------------------------- %%%% definitions -module(commands_tests). -compile(export_all). -include("suite.hrl"). %%%================================== %%%% setup -ifdef(OTP_BELOW_24). single_cases() -> {commands_single, [sequence], []}. -else. single_cases() -> {commands_single, [sequence], [single_test(setup), single_test(ejabberdctl), single_test(http_integer), single_test(http_string), single_test(http_binary), single_test(http_atom), single_test(http_rescode), single_test(http_restuple), single_test(http_list), single_test(http_tuple), single_test(http_list_tuple), single_test(http_list_tuple_map), single_test(adhoc_list_commands), single_test(adhoc_apiversion), single_test(adhoc_apizero), single_test(adhoc_apione), single_test(adhoc_integer), single_test(adhoc_string), single_test(adhoc_binary), single_test(adhoc_tuple), single_test(adhoc_list), single_test(adhoc_list_tuple), single_test(adhoc_atom), single_test(adhoc_rescode), single_test(adhoc_restuple), %%single_test(adhoc_all), single_test(clean)]}. -endif. %% @format-begin single_test(T) -> list_to_atom("commands_" ++ atom_to_list(T)). setup(_Config) -> M = <<"mod_example">>, clean(_Config), case execute(module_install, [M]) of ok -> ok; {error, not_available} -> ?match(ok, execute(modules_update_specs, [])), ?match(ok, execute(module_install, [M])) end, Installed = execute(modules_installed, []), ?match(true, lists:keymember(mod_example, 1, Installed)). clean(_Config) -> M = <<"mod_example">>, execute(module_uninstall, [M]), Installed = execute(modules_installed, []), ?match(false, lists:keymember(mod_example, 1, Installed)). %%%================================== %%%% ejabberdctl %% TODO: find a way to read and check the output printed in the command line ejabberdctl(_Config) -> R = ejabberd_ctl:process(["modules_installed"]), ct:pal("ejabberdctl: R ~p", [R]), ct:pal("ejabberdctl: Q ~p", [os:cmd("ejabberdctl modules_installed")]), Installed = execute(modules_installed, []), ?match(true, lists:keymember(mod_example, 1, Installed)). execute(Name, Args) -> ejabberd_commands:execute_command2(Name, Args, #{caller_module => ejabberd_ctl}, 1000000). %%%================================== %%%% mod_http_api http_integer(Config) -> Integer = 123456789, ?match(Integer, query(Config, "command_test_integer", #{arg_integer => Integer})). http_string(Config) -> S = "This is a string.", B = iolist_to_binary(S), ?match(B, query(Config, "command_test_string", #{arg_string => S})), ?match(B, query(Config, "command_test_string", #{arg_string => B})). http_binary(Config) -> B = <<"This is a binary.">>, ?match(B, query(Config, "command_test_binary", #{arg_binary => B})). %% mod_http_api doesn't handle 'atom' result format %% and formats the result as a binary by default http_atom(Config) -> S = "Some string.", B = <<"Some string.">>, ?match(B, query(Config, "command_test_atom", #{arg_string => S})), ?match(B, query(Config, "command_test_atom", #{arg_string => B})). http_rescode(Config) -> ?match(0, query(Config, "command_test_rescode", #{code => "true"})), ?match(0, query(Config, "command_test_rescode", #{code => "ok"})), ?match(1, query(Config, "command_test_rescode", #{code => "problem"})), ?match(1, query(Config, "command_test_rescode", #{code => "error"})). http_restuple(Config) -> ?match(<<"Deleted 0 users: []">>, query(Config, "delete_old_users", #{days => 99})), ?match(<<"Good">>, query(Config, "command_test_restuple", #{code => "true", text => "Good"})), ?match(<<"OK!!">>, query(Config, "command_test_restuple", #{code => "ok", text => "OK!!"})). http_list(Config) -> ListS = ["one", "first", "primary"], ListB = lists:sort([<<"one">>, <<"first">>, <<"primary">>]), ?match(ListB, query(Config, "command_test_list", #{arg_list => ListS})), ?match(ListB, query(Config, "command_test_list", #{arg_list => ListB})). http_tuple(Config) -> MapA = #{element1 => "one", element2 => "first", element3 => "primary"}, MapB = #{<<"element1">> => <<"one">>, <<"element2">> => <<"first">>, <<"element3">> => <<"primary">>}, ?match(MapB, query(Config, "command_test_tuple", #{arg_tuple => MapA})), ?match(MapB, query(Config, "command_test_tuple", #{arg_tuple => MapB})). http_list_tuple(Config) -> LTA = [#{element1 => "one", element2 => "uno"}, #{element1 => "two", element2 => "dos"}, #{element1 => "three", element2 => "tres"}], LTB = lists:sort([#{<<"element1">> => <<"one">>, <<"element2">> => <<"uno">>}, #{<<"element1">> => <<"two">>, <<"element2">> => <<"dos">>}, #{<<"element1">> => <<"three">>, <<"element2">> => <<"tres">>}]), ?match(LTB, query(Config, "command_test_list_tuple", #{arg_list => LTA})), ?match(LTB, query(Config, "command_test_list_tuple", #{arg_list => LTB})). http_list_tuple_map(Config) -> LTA = #{<<"one">> => <<"uno">>, <<"two">> => <<"dos">>, <<"three">> => <<"tres">>}, LTB = lists:sort([#{<<"element1">> => <<"one">>, <<"element2">> => <<"uno">>}, #{<<"element1">> => <<"two">>, <<"element2">> => <<"dos">>}, #{<<"element1">> => <<"three">>, <<"element2">> => <<"tres">>}]), ?match(LTB, lists:sort(query(Config, "command_test_list_tuple", #{arg_list => LTA}))). %%% internal functions query(Config, Tail, Map) -> BodyQ = misc:json_encode(Map), Body = make_query(Config, Tail, BodyQ), misc:json_decode(Body). make_query(Config, Tail, BodyQ) -> ?match({ok, {{"HTTP/1.1", 200, _}, _, Body}}, httpc:request(post, {page(Config, Tail), [], "application/json", BodyQ}, [], [{body_format, binary}]), Body). page(Config, Tail) -> Server = ?config(server_host, Config), Port = ct:get_config(web_port, 5280), "http://" ++ Server ++ ":" ++ integer_to_list(Port) ++ "/api/" ++ Tail. %%%================================== %%%% ad-hoc %%% list commands adhoc_list_commands(Config) -> {ok, Result} = get_items(Config, <<"api-commands">>), {value, #disco_item{name = <<"command_test_binary">>}} = lists:keysearch(<<"command_test_binary">>, #disco_item.name, Result), suite:disconnect(Config). get_items(Config, Node) -> case suite:send_recv(Config, #iq{type = get, to = server_jid(Config), sub_els = [#disco_items{node = Node}]}) of #iq{type = result, sub_els = [#disco_items{node = Node, items = Items}]} -> {ok, Items}; #iq{type = result, sub_els = []} -> {empty, []}; #iq{type = error} = Err -> xmpp:get_error(Err) end. %%% apiversion adhoc_apiversion(Config) -> Node = <<"api-commands/command_test_apiversion">>, ArgFields = make_fields_args([]), ResFields = make_fields_res([{<<"apiversion">>, <<"2">>}]), {ok, Sid, _FormFields} = get_form(Config, Node), ?match({ok, ResFields}, set_form(Config, Node, Sid, ArgFields)), suite:disconnect(Config). %%% apizero adhoc_apizero(Config) -> Node = <<"api-commands/command_test_apizero">>, ArgFields = make_fields_args([]), ResFields = make_fields_res([{<<"apiversion">>, <<"0">>}]), {ok, Sid, _FormFields} = get_form(Config, Node), ?match({ok, ResFields}, set_form(Config, Node, Sid, ArgFields)), suite:disconnect(Config). %%% apione adhoc_apione(Config) -> Node = <<"api-commands/command_test_apione">>, ArgFields = make_fields_args([]), ResFields = make_fields_res([{<<"apiversion">>, <<"1">>}]), {ok, Sid, _FormFields} = get_form(Config, Node), ?match({ok, ResFields}, set_form(Config, Node, Sid, ArgFields)), suite:disconnect(Config). %%% integer adhoc_integer(Config) -> Node = <<"api-commands/command_test_integer">>, ArgFields = make_fields_args([{<<"arg_integer">>, <<"12345">>}]), ResFields = make_fields_res([{<<"res_integer">>, <<"12345">>}]), {ok, Sid, _FormFields} = get_form(Config, Node), ?match({ok, ResFields}, set_form(Config, Node, Sid, ArgFields)), suite:disconnect(Config). %%% string adhoc_string(Config) -> Node = <<"api-commands/command_test_string">>, ArgFields = make_fields_args([{<<"arg_string">>, <<"Some string.">>}]), ResFields = make_fields_res([{<<"res_string">>, <<"Some string.">>}]), {ok, Sid, _FormFields} = get_form(Config, Node), ?match({ok, ResFields}, set_form(Config, Node, Sid, ArgFields)), suite:disconnect(Config). %%% binary adhoc_binary(Config) -> Node = <<"api-commands/command_test_binary">>, ArgFields = make_fields_args([{<<"arg_binary">>, <<"Some binary.">>}]), ResFields = make_fields_res([{<<"res_string">>, <<"Some binary.">>}]), {ok, Sid, _FormFields} = get_form(Config, Node), ?match({ok, ResFields}, set_form(Config, Node, Sid, ArgFields)), suite:disconnect(Config). %%% tuple adhoc_tuple(Config) -> Node = <<"api-commands/command_test_tuple">>, ArgFields = make_fields_args([{<<"arg_tuple">>, <<"one:two:three">>}]), {ok, Sid, _FormFields} = get_form(Config, Node), ?match({ok, [{xdata_field, <<"res_tuple">>, 'text-single', <<"res_tuple">>, false, <<" {element1 : element2 : element3}">>, [<<"one : two : three">>], [], []}]}, set_form(Config, Node, Sid, ArgFields)), suite:disconnect(Config). %%% list adhoc_list(Config) -> Node = <<"api-commands/command_test_list">>, ArgFields = make_fields_args([{<<"arg_list">>, [<<"one">>, <<"first">>, <<"primary">>]}]), ResFields = make_fields_res([{<<"res_list">>, lists:sort([<<"one">>, <<"first">>, <<"primary">>])}]), {ok, Sid, _FormFields} = get_form(Config, Node), ?match({ok, ResFields}, set_form(Config, Node, Sid, ArgFields)), suite:disconnect(Config). %%% list_tuple adhoc_list_tuple(Config) -> Node = <<"api-commands/command_test_list_tuple">>, ArgFields = make_fields_args([{<<"arg_list">>, [<<"one:uno">>, <<"two:dos">>, <<"three:tres">>]}]), ResFields = make_fields_res([{<<"res_list">>, lists:sort([<<"one : uno">>, <<"two : dos">>, <<"three : tres">>])}]), {ok, Sid, _FormFields} = get_form(Config, Node), ?match({ok, ResFields}, set_form(Config, Node, Sid, ArgFields)), suite:disconnect(Config). %%% atom adhoc_atom(Config) -> Node = <<"api-commands/command_test_atom">>, ArgFields = make_fields_args([{<<"arg_string">>, <<"a_test_atom">>}]), ResFields = make_fields_res([{<<"res_atom">>, <<"a_test_atom">>}]), {ok, Sid, _FormFields} = get_form(Config, Node), ?match({ok, ResFields}, set_form(Config, Node, Sid, ArgFields)), suite:disconnect(Config). %%% rescode adhoc_rescode(Config) -> Node = <<"api-commands/command_test_rescode">>, ArgFields = make_fields_args([{<<"code">>, <<"ok">>}]), ResFields = make_fields_res([{<<"res_atom">>, <<"0">>}]), {ok, Sid, _FormFields} = get_form(Config, Node), ?match({ok, ResFields}, set_form(Config, Node, Sid, ArgFields)), suite:disconnect(Config). %%% restuple adhoc_restuple(Config) -> Node = <<"api-commands/command_test_restuple">>, ArgFields = make_fields_args([{<<"code">>, <<"ok">>}, {<<"text">>, <<"Just a result text">>}]), ResFields = make_fields_res([{<<"res_atom">>, <<"Just a result text">>}]), {ok, Sid, _FormFields} = get_form(Config, Node), ?match({ok, ResFields}, set_form(Config, Node, Sid, ArgFields)), suite:disconnect(Config). %%% internal functions server_jid(Config) -> jid:make(<<>>, ?config(server, Config), <<>>). make_fields_args(Fields) -> lists:map(fun ({Var, Values}) when is_list(Values) -> #xdata_field{label = Var, var = Var, required = true, type = 'text-multi', values = Values}; ({Var, Value}) -> #xdata_field{label = Var, var = Var, required = true, type = 'text-single', values = [Value]} end, Fields). make_fields_res(Fields) -> lists:map(fun ({Var, Values}) when is_list(Values) -> #xdata_field{label = Var, var = Var, type = 'text-multi', values = Values}; ({Var, Value}) -> #xdata_field{label = Var, var = Var, type = 'text-single', values = [Value]} end, Fields). get_form(Config, Node) -> case suite:send_recv(Config, #iq{type = set, to = server_jid(Config), sub_els = [#adhoc_command{node = Node}]}) of #iq{type = result, sub_els = [#adhoc_command{node = Node, action = execute, status = executing, sid = Sid, actions = #adhoc_actions{execute = complete, complete = true}, xdata = #xdata{fields = Fields}}]} -> {ok, Sid, [F || F <- Fields, F#xdata_field.type /= fixed]}; #iq{type = error} = Err -> xmpp:get_error(Err) end. set_form(Config, Node, Sid, ArgFields) -> Xdata = #xdata{type = submit, fields = ArgFields}, case suite:send_recv(Config, #iq{type = set, to = server_jid(Config), sub_els = [#adhoc_command{node = Node, action = complete, sid = Sid, xdata = Xdata}]}) of #iq{type = result, sub_els = [#adhoc_command{node = Node, action = execute, status = completed, sid = Sid, xdata = #xdata{fields = ResFields}}]} -> ResFields2 = [F || F <- ResFields, F#xdata_field.type /= fixed], {ok, ResFields2 -- ArgFields}; #iq{type = error} = Err -> xmpp:get_error(Err) end. %%%================================== %%% vim: set foldmethod=marker foldmarker=%%%%,%%%=: