1: %% @doc This suite tests both old ejabberd_commands module, which is slowly getting deprecated,
    2: %% and the new mongoose_commands implementation.
    3: -module(commands_SUITE).
    4: -compile([export_all, nowarn_export_all]).
    5: 
    6: -include_lib("exml/include/exml.hrl").
    7: -include_lib("eunit/include/eunit.hrl").
    8: -include("ejabberd_commands.hrl").
    9: -include("jlib.hrl").
   10: 
   11: -define(PRT(X, Y), ct:pal("~p: ~p", [X, Y])).
   12: 
   13: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   14: %%%% suite configuration
   15: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   16: 
   17: all() ->
   18:     [
   19:      {group, old_commands},
   20:      {group, new_commands}
   21:     ].
   22: 
   23: groups() ->
   24:     [
   25:      {old_commands, [sequence],
   26:       [old_list,
   27:        old_exec,
   28:        old_access_ctl
   29:       ]
   30:      },
   31:      {new_commands, [sequence],
   32:       [new_type_checker,
   33:        new_reg_unreg,
   34:        new_failedreg,
   35:        new_list,
   36:        new_execute,
   37:        different_types,
   38:        errors_are_readable
   39:       ]
   40:      }
   41:     ].
   42: 
   43: init_per_suite(C) ->
   44:     application:ensure_all_started(jid),
   45:     ok = mnesia:start(),
   46:     C.
   47: 
   48: end_per_suite(_) ->
   49:     mnesia:stop(),
   50:     mnesia:delete_schema([node()]),
   51:     ok.
   52: 
   53: init_per_group(old_commands, C) ->
   54:     Pid = spawn(fun ec_holder/0),
   55:     [{helper_proc, Pid} | C];
   56: init_per_group(new_commands, C) ->
   57:     Pid = spawn(fun mc_holder/0),
   58:     [{helper_proc, Pid} | C].
   59: 
   60: end_per_group(old_commands, C) ->
   61:     ejabberd_commands:unregister_commands(commands_old()),
   62:     stop_helper_proc(C),
   63:     C;
   64: end_per_group(new_commands, C) ->
   65:     mongoose_commands:unregister(commands_new()),
   66:     stop_helper_proc(C),
   67:     C.
   68: 
   69: stop_helper_proc(C) ->
   70:     Pid = proplists:get_value(helper_proc, C),
   71:     Pid ! stop.
   72: 
   73: init_per_testcase(_, C) ->
   74:     [mongoose_config:set_opt(Key, Value) || {Key, Value} <- opts()],
   75:     meck:new(ejabberd_auth_dummy, [non_strict]),
   76:     meck:expect(ejabberd_auth_dummy, get_password_s, fun(_, _) -> <<"">> end),
   77:     meck:new(mongoose_domain_api),
   78:     meck:expect(mongoose_domain_api, get_domain_host_type, fun(H) -> {ok, H} end),
   79:     C.
   80: 
   81: end_per_testcase(_, _C) ->
   82:     [mongoose_config:unset_opt(Key) || {Key, _Value} <- opts()],
   83:     meck:unload().
   84: 
   85: opts() ->
   86:     [{{auth, <<"localhost">>}, #{methods => [dummy]}},
   87:      {{access, <<"localhost">>}, #{experts_only => [#{acl => coder, value => allow},
   88:                                                     #{acl => manager, value => allow},
   89:                                                     #{acl => all, value => deny}]}},
   90:      {{acl, <<"localhost">>}, #{coder => [#{user => <<"zenek">>, match => current_domain}]}}].
   91: 
   92: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   93: %%%% test methods
   94: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   95: 
   96: 
   97: old_list(_C) ->
   98:     %% list
   99:     Rlist = ejabberd_commands:list_commands(),
  100:     {command_one, _, "do nothing and return"} = proplists:lookup(command_one, Rlist),
  101:     %% get definition
  102:     Rget = ejabberd_commands:get_command_definition(command_one),
  103:     % we should get back exactly the definition we provided
  104:     [Cone | _] = commands_old(),
  105:     Cone = Rget,
  106:     %% get interface
  107:     {Argspec, Retspec} = ejabberd_commands:get_command_format(command_one),
  108:     [{msg, binary}] = Argspec,
  109:     {res, restuple} = Retspec,
  110:     %% list by tags
  111:     Tagcomm = ejabberd_commands:get_tags_commands(),
  112:     ?assertEqual(length(proplists:get_value("one", Tagcomm)), 1),
  113:     ?assertEqual(length(proplists:get_value("two", Tagcomm)), 2),
  114:     ?assertEqual(length(proplists:get_value("three", Tagcomm)), 1),
  115:     ok.
  116: 
  117: old_exec(_C) ->
  118:     %% execute
  119:     <<"bzzzz">> = ejabberd_commands:execute_command(command_one, [<<"bzzzz">>]),
  120:     Res = ejabberd_commands:execute_command(command_one, [123]),
  121:     ?PRT("invalid type ignored", Res), %% there is no arg type check
  122:     Res2 = ejabberd_commands:execute_command(command_two, [123]),
  123:     ?PRT("invalid return type ignored", Res2), %% nor return
  124:     %% execute unknown command
  125:     {error, command_unknown} = ejabberd_commands:execute_command(command_seven, [123]),
  126:     ok.
  127: 
  128: old_access_ctl(_C) ->
  129:     %% with no auth method it is all fine
  130:     checkauth(true, [], noauth),
  131:     %% noauth fails if first item is not 'all' (users)
  132:     checkauth(account_unprivileged, [{none, none, []}], noauth),
  133:     %% if here we allow all commands to noauth
  134:     checkauth(true, [{all, all, []}], noauth),
  135:     %% and here only command_one
  136:     checkauth(true, [{all, [command_one], []}], noauth),
  137:     %% so this'd fail
  138:     checkauth(account_unprivileged, [{all, [command_two], []}], noauth),
  139:     % now we provide a role name, this requires a user and triggers password and acl check
  140:     % this fails because password is bad
  141:     checkauth(invalid_account_data, [{some_acl_role, [command_one], []}], {<<"zenek">>, <<"localhost">>, <<"bbb">>}),
  142:     % this, because of acl
  143:     checkauth(account_unprivileged, [{some_acl_role, [command_one], []}], {<<"zenek">>, <<"localhost">>, <<"">>}),
  144:     % and this should work, because we define command_one as available to experts only, while acls in config
  145:     % (see ggo/1) state that experts-only funcs are available to coders and managers, and zenek is a coder, gah.
  146:     checkauth(true, [{experts_only, [command_one], []}], {<<"zenek">>, <<"localhost">>, <<"">>}),
  147:     ok.
  148: 
  149: 
  150: new_type_checker(_C) ->
  151:     true = t_check_type({msg, binary}, <<"zzz">>),
  152:     true = t_check_type({msg, integer}, 127),
  153:     {false, _} = t_check_type({{a, binary}, {b, integer}}, 127),
  154:     true = t_check_type({{a, binary}, {b, integer}}, {<<"z">>, 127}),
  155:     true = t_check_type({ok, {msg, integer}}, {ok, 127}),
  156:     true = t_check_type({ok, {msg, integer}, {val, binary}}, {ok, 127, <<"z">>}),
  157:     {false, _} = t_check_type({k, {msg, integer}, {val, binary}}, {ok, 127, <<"z">>}),
  158:     {false, _} = t_check_type({ok, {msg, integer}, {val, binary}}, {ok, 127, "z"}),
  159:     {false, _} = t_check_type({ok, {msg, integer}, {val, binary}}, {ok, 127, <<"z">>, 333}),
  160:     true = t_check_type([integer], []),
  161:     true = t_check_type([integer], [1, 2, 3]),
  162:     {false, _} = t_check_type([integer], [1, <<"z">>, 3]),
  163:     true = t_check_type([], [1, 2, 3]),
  164:     true = t_check_type([], []),
  165:     true = t_check_type({msg, boolean}, true),
  166:     true = t_check_type({msg, boolean}, false),
  167:     {false, _} = t_check_type({msg, boolean}, <<"true">>),
  168:     ok.
  169: 
  170: t_check_type(Spec, Value) ->
  171:     R = try mongoose_commands:check_type(argument, Spec, Value) of
  172:             true -> true
  173:         catch
  174:             E ->
  175:                 {false, E}
  176:         end,
  177:     R.
  178: 
  179: new_reg_unreg(_C) ->
  180:     L1 = length(commands_new()),
  181:     L2 = L1 + length(commands_new_temp()),
  182:     ?assertEqual(length(mongoose_commands:list(admin)), L1),
  183:     mongoose_commands:register(commands_new_temp()),
  184:     ?assertEqual(length(mongoose_commands:list(admin)), L2),
  185:     mongoose_commands:unregister(commands_new_temp()),
  186:     ?assertEqual(length(mongoose_commands:list(admin)), L1),
  187:     ok.
  188: 
  189: failedreg([]) -> ok;
  190: failedreg([Cmd|Tail]) ->
  191:     ?assertThrow({invalid_command_definition, _}, mongoose_commands:register([Cmd])),
  192:     failedreg(Tail).
  193: 
  194: new_failedreg(_C) ->
  195:     failedreg(commands_new_lame()).
  196: 
  197: 
  198: new_list(_C) ->
  199:     %% for admin
  200:     Rlist = mongoose_commands:list(admin),
  201:     [Cmd] = [C || C <- Rlist, mongoose_commands:name(C) == command_one],
  202:     command_one = mongoose_commands:name(Cmd),
  203:     <<"do nothing and return">> = mongoose_commands:desc(Cmd),
  204:     %% list by category
  205:     [_] = mongoose_commands:list(admin, <<"user">>),
  206:     [] = mongoose_commands:list(admin, <<"nocategory">>),
  207:     %% list by category and action
  208:     [_] = mongoose_commands:list(admin, <<"user">>, read),
  209:     [] = mongoose_commands:list(admin, <<"user">>, update),
  210:     %% get definition
  211:     Rget = mongoose_commands:get_command(admin, command_one),
  212:     command_one = mongoose_commands:name(Rget),
  213:     read = mongoose_commands:action(Rget),
  214:     [] = mongoose_commands:identifiers(Rget),
  215:     {error, denied, _} = mongoose_commands:get_command(ujid(), command_one),
  216:     %% list for a user
  217:     Ulist = mongoose_commands:list(ujid()),
  218:     [UCmd] = [UC || UC <- Ulist, mongoose_commands:name(UC) == command_foruser],
  219: 
  220:     command_foruser = mongoose_commands:name(UCmd),
  221:     URget = mongoose_commands:get_command(ujid(), command_foruser),
  222:     command_foruser = mongoose_commands:name(URget),
  223:     ok.
  224: 
  225: 
  226: new_execute(_C) ->
  227:     {ok, <<"bzzzz">>} = mongoose_commands:execute(admin, command_one, [<<"bzzzz">>]),
  228:     Cmd = mongoose_commands:get_command(admin, command_one),
  229:     {ok, <<"bzzzz">>} = mongoose_commands:execute(admin, Cmd, [<<"bzzzz">>]),
  230:     %% call with a map
  231:     {ok, <<"bzzzz">>} = mongoose_commands:execute(admin, command_one, #{msg => <<"bzzzz">>}),
  232:     %% command which returns just ok
  233:     ok = mongoose_commands:execute(admin, command_noreturn, [<<"bzzzz">>]),
  234:     %% this user has no permissions
  235:     {error, denied, _} = mongoose_commands:execute(ujid(), command_one, [<<"bzzzz">>]),
  236:     %% command is not registered
  237:     {error, not_implemented, _} = mongoose_commands:execute(admin, command_seven, [<<"bzzzz">>]),
  238:     %% invalid arguments
  239:     {error, type_error, _} = mongoose_commands:execute(admin, command_one, [123]),
  240:     {error, type_error, _} = mongoose_commands:execute(admin, command_one, []),
  241:     {error, type_error, _} = mongoose_commands:execute(admin, command_one, #{}),
  242:     {error, type_error, _} = mongoose_commands:execute(admin, command_one, #{msg => 123}),
  243:     {error, type_error, _} = mongoose_commands:execute(admin, command_one, #{notthis => <<"bzzzz">>}),
  244:     {error, type_error, _} = mongoose_commands:execute(admin, command_one, #{msg => <<"bzzzz">>, redundant => 123}),
  245:     %% backend func throws exception
  246:     {error, internal, _} = mongoose_commands:execute(admin, command_one, [<<"throw">>]),
  247:     %% backend func returns error
  248:     {error, internal, <<"byleco">>} = mongoose_commands:execute(admin, command_one, [<<"error">>]),
  249:     % user executes his command
  250:     {ok, <<"bzzzz">>} = mongoose_commands:execute(ujid(), command_foruser, #{msg => <<"bzzzz">>}),
  251:     % a caller arg
  252:     % called by admin
  253:     {ok, <<"admin@localhost/zbzzzz">>} = mongoose_commands:execute(admin,
  254:                                                                    command_withcaller,
  255:                                                                    #{caller => <<"admin@localhost/z">>,
  256:                                                                      msg => <<"bzzzz">>}),
  257:     % called by user
  258:     {ok, <<"zenek@localhost/zbzzzz">>} = mongoose_commands:execute(<<"zenek@localhost">>,
  259:                                                                    command_withcaller,
  260:                                                                    #{caller => <<"zenek@localhost/z">>,
  261:                                                                      msg => <<"bzzzz">>}),
  262:     % call by user but jids do not match
  263:     {error, denied, _} = mongoose_commands:execute(<<"wacek@localhost">>,
  264:                                                    command_withcaller,
  265:                                                    #{caller => <<"zenek@localhost/z">>,
  266:                                                      msg => <<"bzzzz">>}),
  267:     {ok, 30} = mongoose_commands:execute(admin, command_withoptargs, #{msg => <<"a">>}),
  268:     {ok, 18} = mongoose_commands:execute(admin, command_withoptargs, #{msg => <<"a">>, value => 6}),
  269:     ok.
  270: 
  271: different_types(_C) ->
  272:     mongoose_commands:register(commands_new_temp2()),
  273:     {ok, <<"response1">>} = mongoose_commands:execute(admin, command_two, [10, 15]),
  274:     {ok, <<"response2">>} = mongoose_commands:execute(admin, command_three, [10, <<"binary">>]),
  275:     mongoose_commands:unregister(commands_new_temp2()),
  276:     ok.
  277: 
  278: errors_are_readable(_C) ->
  279:     {error, internal, TextBin} = mongoose_commands:execute(admin, make_error, [<<"oops">>]),
  280:     Map = parse_binary_term(TextBin),
  281:     [<<"oops">>] = maps:get(args, Map),
  282:     admin = maps:get(caller, Map),
  283:     error = maps:get(class, Map),
  284:     make_error = maps:get(command_name, Map),
  285:     <<"oops">> = maps:get(reason, Map),
  286:     [_|_] = maps:get(stacktrace, Map),
  287:     command_failed = maps:get(what, Map),
  288:     ok.
  289: 
  290: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  291: %%%% definitions
  292: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  293: 
  294: commands_new() ->
  295:     [
  296:         [
  297:             {name, command_one},
  298:             {category, <<"user">>},
  299:             {desc, <<"do nothing and return">>},
  300:             {module, ?MODULE},
  301:             {function, cmd_one},
  302:             {action, read},
  303:             {args, [{msg, binary}]},
  304:             {result, {msg, binary}}
  305:         ],
  306:         [
  307:             {name, command_noreturn},
  308:             {category, <<"message">>},
  309:             {desc, <<"do nothing and return nothing">>},
  310:             {module, ?MODULE},
  311:             {function, cmd_one},
  312:             {action, create},
  313:             {args, [{msg, binary}]},
  314:             {result, ok}
  315:         ],
  316:         [
  317:             {name, command_foruser},
  318:             {category, <<"another">>},
  319:             {desc, <<"this is available for a user">>},
  320:             {module, ?MODULE},
  321:             {function, cmd_one},
  322:             {action, read},
  323:             {security_policy, [user]},
  324:             {args, [{msg, binary}]},
  325:             {result, {msg, binary}}
  326:         ],
  327:         [
  328:             {name, command_withoptargs},
  329:             {category, <<"yetanother">>},
  330:             {desc, <<"this is available for a user">>},
  331:             {module, ?MODULE},
  332:             {function, cmd_one_withvalue},
  333:             {action, read},
  334:             {security_policy, [user]},
  335:             {args, [{msg, binary}]},
  336:             {optargs, [{value, integer, 10}]},
  337:             {result, {nvalue, integer}}
  338:         ],
  339:         [
  340:             {name, command_withcaller},
  341:             {category, <<"another">>},
  342:             {desc, <<"this has a 'caller' argument, returns caller ++ msg">>},
  343:             {module, ?MODULE},
  344:             {function, cmd_concat},
  345:             {action, create},
  346:             {security_policy, [user]},
  347:             {args, [{caller, binary}, {msg, binary}]},
  348:             {result, {msg, binary}}
  349:         ],
  350:         [
  351:             {name, make_error},
  352:             {category, <<"testing">>},
  353:             {desc, <<"Just to test an error">>},
  354:             {module, erlang},
  355:             {function, error},
  356:             {action, read},
  357:             {args, [{error, binary}]},
  358:             {result, []}
  359:         ]
  360:     ].
  361: 
  362: commands_new_temp() ->
  363:     %% this is to check registering/unregistering commands
  364:     [
  365:         [
  366:             {name, command_temp},
  367:             {category, <<"user">>},
  368:             {desc, <<"do nothing and return">>},
  369:             {module, ?MODULE},
  370:             {function, cmd_one},
  371:             {action, create}, % different action
  372:             {args, [{msg, binary}]},
  373:             {result, {msg, binary}}
  374:         ],
  375:         [
  376:             {name, command_one_arity},
  377:             {category, <<"user">>},
  378:             {desc, <<"do nothing and return">>},
  379:             {module, ?MODULE},
  380:             {function, cmd_one},
  381:             {action, read},
  382:             {args, [{msg, binary}, {whatever, integer}]}, % different arity
  383:             {result, {msg, binary}}
  384:         ],
  385:         [
  386:             {name, command_one_two},
  387:             {category, <<"user">>},
  388:             {subcategory, <<"rosters">>}, % has subcategory
  389:             {desc, <<"do nothing and return">>},
  390:             {module, ?MODULE},
  391:             {function, cmd_one},
  392:             {action, read},
  393:             {args, [{msg, binary}]},
  394:             {result, {msg, binary}}
  395:         ],
  396:         [
  397:             {name, command_temp2},
  398:             {category, <<"user">>},
  399:             {desc, <<"this one specifies identifiers">>},
  400:             {module, ?MODULE},
  401:             {function, cmd_one},
  402:             {action, update},
  403:             {identifiers, [ident]},
  404:             {args, [{ident, integer}, {msg, binary}]},
  405:             {result, {msg, binary}}
  406:         ]
  407:     ].
  408: 
  409: commands_new_temp2() ->
  410:     %% This is for extra test with different arg types
  411:     [
  412:         [
  413:             {name, command_two},
  414:             {category, <<"animals">>},
  415:             {desc, <<"some">>},
  416:             {module, ?MODULE},
  417:             {function, the_same_types},
  418:             {action, read},
  419:             {args, [{one, integer}, {two, integer}]},
  420:             {result, {msg, binary}}
  421:     ],
  422:         [
  423:             {name, command_three},
  424:             {category, <<"music">>},
  425:             {desc, <<"two args, different types">>},
  426:             {module, ?MODULE},
  427:             {function, different_types},
  428:             {action, read},
  429:             {args, [{one, integer}, {two, binary}]},
  430:             {result, {msg, binary}}
  431:         ]
  432:     ].
  433: 
  434: commands_new_lame() ->
  435:     [
  436:         [
  437:             {name, command_one} % missing values
  438:         ],
  439:         [
  440:             {name, command_one},
  441:             {category, []} %% should be binary
  442:         ],
  443:         [
  444:             {name, command_one},
  445:             {category, <<"user">>},
  446:             {desc, <<"do nothing and return">>},
  447:             {module, ?MODULE},
  448:             {function, cmd_one},
  449:             {action, andnowforsomethingcompletelydifferent} %% not one of allowed values
  450:         ],
  451:         [
  452:             {name, command_one},
  453:             {category, <<"user">>},
  454:             {desc, <<"do nothing and return">>},
  455:             {module, ?MODULE},
  456:             {function, cmd_one},
  457:             {action, delete},
  458:             {args, [{msg, binary}, integer]}, %% args have to be a flat list of named arguments
  459:             {result, {msg, binary}}
  460:         ],
  461: %%        We do not crash if command is already registered because some modules are loaded more then once
  462: %%        [
  463: %%            {name, command_one}, %% everything is fine, but it is already registered
  464: %%            {category, another},
  465: %%            {desc, "do nothing and return"},
  466: %%            {module, ?MODULE},
  467: %%            {function, cmd_one},
  468: %%            {action, read},
  469: %%            {args, [{msg, binary}]},
  470: %%            {result, {msg, binary}}
  471: %%        ],
  472:         [
  473:             {name, command_one},
  474:             {category, <<"another">>},
  475:             {desc, <<"do nothing and return">>},
  476:             {module, ?MODULE},
  477:             {function, cmd_one},
  478:             {action, update}, %% an 'update' command has to specify identifiers
  479:             {args, [{msg, binary}]},
  480:             {result, {msg, binary}}
  481:         ],
  482:         [
  483:             {name, command_one},
  484:             {category, <<"another">>},
  485:             {desc, <<"do nothing and return">>},
  486:             {module, ?MODULE},
  487:             {function, cmd_one},
  488:             {action, update},
  489:             {identifiers, [1]}, %% ...and they must be atoms...
  490:             {args, [{msg, binary}]},
  491:             {result, {msg, binary}}
  492:         ],
  493:         [
  494:             {name, command_one},
  495:             {category, <<"another">>},
  496:             {desc, <<"do nothing and return">>},
  497:             {module, ?MODULE},
  498:             {function, cmd_one},
  499:             {action, update},
  500:             {identifiers, [ident]}, %% ...which are present in args
  501:             {args, [{msg, binary}]},
  502:             {result, {msg, binary}}
  503:         ],
  504:         [
  505:             {name, command_seven}, %% name is different...
  506:             {category, <<"user">>},
  507:             {desc, <<"do nothing and return">>},
  508:             {module, ?MODULE},
  509:             {function, cmd_one},
  510:             {action, read}, %% ...but another command with the same category and action and arity is already registered
  511:             {args, [{msg, binary}]},
  512:             {result, {msg, binary}}
  513:         ],
  514:         [
  515:             {name, command_seven},
  516:             {category, <<"user">>},
  517:             {desc, <<"do nothing and return">>},
  518:             {module, ?MODULE},
  519:             {function, cmd_one},
  520:             {action, delete},
  521:             {security_policy, [wrong]}, % invalid security definition
  522:             {args, [{msg, binary}]},
  523:             {result, {msg, binary}}
  524: %%        ],
  525: %%        [
  526: %%            {name, command_seven},
  527: %%            {category, user},
  528: %%            {desc, "do nothing and return"},
  529: %%            {module, ?MODULE},
  530: %%            {function, cmd_one},
  531: %%            {action, delete},
  532: %%            {security_policy, []}, % invalid security definition
  533: %%            {args, [{msg, binary}]},
  534: %%            {result, {msg, binary}}
  535:         ]
  536:     ].
  537: 
  538: commands_old() ->
  539:     [
  540:         #ejabberd_commands{name = command_one, tags = [one],
  541:                            desc = "do nothing and return",
  542:                            module = ?MODULE, function = cmd_one,
  543:                            args = [{msg, binary}],
  544:                            result = {res, restuple}},
  545:         #ejabberd_commands{name = command_two, tags = [two],
  546:                            desc = "this returns wrong type",
  547:                            module = ?MODULE, function = cmd_two,
  548:                            args = [{msg, binary}],
  549:                            result = {res, restuple}},
  550:         #ejabberd_commands{name = command_three, tags = [two, three],
  551:                            desc = "do nothing and return",
  552:                            module = ?MODULE, function = cmd_three,
  553:                            args = [{msg, binary}],
  554:                            result = {res, restuple}}
  555:     ].
  556: 
  557: cmd_one(<<"throw">>) ->
  558:     C = 12,
  559:     <<"A", C/binary>>;
  560: cmd_one(<<"error">>) ->
  561:     {error, internal, <<"byleco">>};
  562: cmd_one(M) ->
  563:     M.
  564: 
  565: cmd_one_withvalue(_Msg, Value) ->
  566:     Value * 3.
  567: 
  568: cmd_two(M) ->
  569:     M.
  570: 
  571: the_same_types(10, 15) ->
  572:     <<"response1">>;
  573: the_same_types(_, _) ->
  574:     <<"wrong response">>.
  575: 
  576: different_types(10, <<"binary">>) ->
  577:     <<"response2">>;
  578: different_types(_, _) ->
  579:     <<"wrong content">>.
  580: 
  581: cmd_concat(A, B) ->
  582:     <<A/binary, B/binary>>.
  583: 
  584: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  585: %%%% utilities
  586: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  587: 
  588: %% this is a bit stupid, but we need a process which would hold ets table
  589: ec_holder() ->
  590:     ejabberd_commands:init(),
  591:     ejabberd_commands:register_commands(commands_old()),
  592:     receive
  593:         _ -> ok
  594:     end.
  595: 
  596: mc_holder() ->
  597:     % we have to do it here to avoid race condition and random failures
  598:     {ok, Pid} = gen_hook:start_link(),
  599:     mongoose_commands:init(),
  600:     mongoose_commands:register(commands_new()),
  601:     receive
  602:         _ -> ok
  603:     end,
  604:     erlang:exit(Pid, kill).
  605: 
  606: checkauth(true, AccessCommands, Auth) ->
  607:     B = <<"bzzzz">>,
  608:     B = ejabberd_commands:execute_command(AccessCommands, Auth, command_one, [B]);
  609: checkauth(ErrMess, AccessCommands, Auth) ->
  610:     B = <<"bzzzz">>,
  611:     {error, ErrMess} = ejabberd_commands:execute_command(AccessCommands, Auth, command_one, [B]).
  612: 
  613: ujid() ->
  614:     <<"zenek@localhost/k">>.
  615: %%    #jid{user = <<"zenek">>, server = <<"localhost">>, resource = "k",
  616: %%         luser = <<"zenek">>, lserver = <<"localhost">>, lresource = "k"}.
  617: 
  618: parse_binary_term(TextBin) ->
  619:     {ok, Tokens, _} = erl_scan:string(binary_to_list(TextBin) ++ "."),
  620:     {ok, Abstract} = erl_parse:parse_exprs(Tokens),
  621:     {value, Value, _} = erl_eval:exprs(Abstract, erl_eval:new_bindings()),
  622:     Value.