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_backend_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: 
   10: -define(PORT, 5288).
   11: -define(HOST, "localhost").
   12: -define(IP,  {127,0,0,1}).
   13: 
   14: %% Error messages
   15: -define(ARGS_LEN_ERROR, <<"Bad parameters length.">>).
   16: -define(ARGS_SPEC_ERROR, <<"Bad name of the parameter.">>).
   17: -type method() :: string().
   18: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   19: %%%% suite configuration
   20: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   21: 
   22: client_module() ->
   23:     mongoose_api_client.
   24: 
   25: backend_module() ->
   26:     mongoose_api_admin.
   27: 
   28: all() ->
   29:     [
   30:      {group, simple_backend},
   31:      {group, get_advanced_backend},
   32:      {group, post_advanced_backend},
   33:      {group, delete_advanced_backend},
   34:       {group, simple_client}
   35:     ].
   36: 
   37: groups() ->
   38:     [
   39:      {simple_backend, [sequence],
   40:       [
   41:        get_simple,
   42:        post_simple,
   43:        delete_simple,
   44:        put_simple
   45:       ]
   46:      },
   47:      {get_advanced_backend, [sequence],
   48:       [
   49:        get_two_args,
   50:        get_wrong_path,
   51:        get_wrong_arg_number,
   52:        get_no_command,
   53:        get_wrong_arg_type
   54:       ]
   55:      },
   56:      {post_advanced_backend, [sequence],
   57:       [
   58:        post_simple_with_subcategory,
   59:        post_different_arg_order,
   60:        post_wrong_arg_number,
   61:        post_wrong_arg_name,
   62:        post_wrong_arg_type,
   63:        post_no_command
   64:       ]
   65:      },
   66:      {delete_advanced_backend, [sequence],
   67:       [
   68:        delete_wrong_arg_order,
   69:        delete_wrong_arg_types
   70:       ]
   71:      },
   72:      {put_advanced_backend, [sequence],
   73:       [
   74:        put_wrong_type,
   75:        put_wrong_param_type,
   76:        put_wrong_bind_type,
   77:        put_different_params_order,
   78:        put_wrong_binds_order,
   79:        put_too_less_params,
   80:        put_too_less_binds,
   81:        put_wrong_bind_name,
   82:        put_wrong_param_name
   83:       ]
   84:      },
   85:      {simple_client, [sequence],
   86:       [
   87:        get_simple_client,
   88:        get_two_args_client,
   89:        get_bad_auth,
   90:        post_simple_client,
   91:        put_simple_client,
   92:        delete_simple_client
   93:       ]
   94:      }
   95:     ].
   96: 
   97: setup(Module) ->
   98:     meck:unload(),
   99:     meck:new(supervisor, [unstick, passthrough, no_link]),
  100:     meck:new(gen_hook, []),
  101:     meck:new(ejabberd_auth, []),
  102:     %% you have to meck some stuff to get it working....
  103:     meck:expect(gen_hook, add_handler, fun(_, _, _, _, _) -> ok end),
  104:     meck:expect(gen_hook, run_fold, fun(_, _, _, _) -> {ok, ok} end),
  105:     spawn(fun mc_holder/0),
  106:     meck:expect(supervisor, start_child,
  107:         fun(ejabberd_listeners, {_, {_, start_link, [_]}, transient,
  108:             infinity, worker, [_]}) -> {ok, self()};
  109:             (A,B) -> meck:passthrough([A,B])
  110:         end),
  111:     %% HTTP API config
  112:     Opts = [{num_acceptors, 10},
  113:         {max_connections, 1024},
  114:         {modules, [{"localhost", "/api", Module, []}]}],
  115:     ejabberd_cowboy:start_listener({?PORT, ?IP, tcp}, Opts).
  116: 
  117: teardown() ->
  118:     cowboy:stop_listener(ejabberd_cowboy:ref({?PORT, ?IP, tcp})),
  119:     mongoose_commands:unregister(commands_new()),
  120:     meck:unload(ejabberd_auth),
  121:     meck:unload(gen_hook),
  122:     meck:unload(supervisor),
  123:     mc_holder_proc ! stop,
  124:     ok.
  125: 
  126: init_per_suite(C) ->
  127:     application:ensure_all_started(cowboy),
  128:     application:ensure_all_started(jid),
  129:     application:ensure_all_started(fusco),
  130:     ok = mnesia:start(),
  131:     C.
  132: 
  133: end_per_suite(C) ->
  134:     stopped = mnesia:stop(),
  135:     mnesia:delete_schema([node()]),
  136:     application:stop(fusco),
  137:     application:stop(cowboy),
  138:     C.
  139: 
  140: init_per_group(_, C) ->
  141:     C.
  142: 
  143: end_per_group(_, C) ->
  144:     C.
  145: 
  146: init_per_testcase(_, C) ->
  147:     C.
  148: 
  149: end_per_testcase(_, C) ->
  150:     C.
  151: 
  152: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  153: %%%% Backend side tests
  154: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  155: 
  156: get_simple(_Config) ->
  157:     Arg = {arg1, <<"bob@localhost">>},
  158:     Base = "/api/users",
  159:     ExpectedBody = get_simple_command(element(2, Arg)),
  160:     {ok, Response} = request(create_path_with_binds(Base, [Arg]), "GET", admin),
  161:     check_status_code(Response, 200),
  162:     check_response_body(Response, ExpectedBody).
  163: 
  164: delete_simple(_Config) ->
  165:     Arg1 = {arg1, <<"ala_ma_kota">>},
  166:     Arg2 = {arg2, 2},
  167:     Base = "/api/music",
  168:     {ok, Response} = request(create_path_with_binds(Base, [Arg1, Arg2]), "DELETE", admin),
  169:     check_status_code(Response, 204).
  170: 
  171: post_simple(_Config) ->
  172:     Arg1 = {arg1, 10},
  173:     Arg2 = {arg2, 2},
  174:     Args = [Arg1, Arg2],
  175:     Path = <<"/api/weather">>,
  176:     Result = binary_to_list(post_simple_command(element(2, Arg1), element(2, Arg2))),
  177:     {ok, Response} = request(Path, "POST", Args, admin),
  178:     check_status_code(Response, 201),
  179:     check_location_header(Response, list_to_binary(build_path_prefix()++"/api/weather/" ++ Result)).
  180: 
  181: post_simple_with_subcategory(_Config) ->
  182:     Arg1 = {arg1, 10},
  183:     Arg2 = {arg2, 2},
  184:     Args = [Arg2],
  185:     Path = <<"/api/weather/10/subcategory">>,
  186:     Result = binary_to_list(post_simple_command(element(2, Arg1), element(2, Arg2))),
  187:     {ok, Response} = request(Path, "POST", Args, admin),
  188:     check_status_code(Response, 201),
  189:     check_location_header(Response, list_to_binary(build_path_prefix()++"/api/weather/10/subcategory/" ++ Result)).
  190: 
  191: put_simple(_Config) ->
  192:     Binds = [{arg1, <<"username">>}, {arg2,<<"localhost">>}],
  193:     Args = [{arg3, <<"newusername">>}],
  194:     Base = "/api/users",
  195:     {ok, Response} = request(create_path_with_binds(Base, Binds), "PUT", Args, admin),
  196:     check_status_code(Response, 204).
  197: 
  198: get_two_args(_Config) ->
  199:     Arg1 = {arg1, 1},
  200:     Arg2 = {arg2, 2},
  201:     Base = "/api/animals",
  202:     ExpectedBody = get_two_args_command(element(2, Arg1), element(2, Arg2)),
  203:     {ok, Response} = request(create_path_with_binds(Base, [Arg1, Arg2]), "GET", admin),
  204:     check_status_code(Response, 200),
  205:     check_response_body(Response, ExpectedBody).
  206: 
  207: get_two_args_different_types(_Config) ->
  208:     Arg1 = {one, 1},
  209:     Arg2 = {two, <<"mybin">>},
  210:     Base = "/api/books",
  211:     ExpectedBody = get_two_args2_command(element(2, Arg1), element(2, Arg2)),
  212:     {ok, Response} = request(create_path_with_binds(Base, [Arg1, Arg2]), "GET", admin),
  213:     check_status_code(Response, 200),
  214:     check_response_body(Response, ExpectedBody).
  215: 
  216: get_wrong_path(_Config) ->
  217:     Path = <<"/api/animals2/1/2">>,
  218:     {ok, Response} = request(Path, "GET", admin),
  219:     check_status_code(Response, 404).
  220: 
  221: get_wrong_arg_number(_Config) ->
  222:     Path = <<"/api/animals/1/2/3">>,
  223:     {ok, Response} = request(Path, "GET", admin),
  224:     check_status_code(Response, 404).
  225: 
  226: get_no_command(_Config) ->
  227:     Path = <<"/api/unregistered_command/123123">>,
  228:     {ok, Response} = request(Path, "GET", admin),
  229:     check_status_code(Response, 404).
  230: 
  231: get_wrong_arg_type(_Config) ->
  232:     Path = <<"/api/animals/1/wrong">>,
  233:     {ok, Response} = request(Path, "GET", admin),
  234:     check_status_code(Response, 400).
  235: 
  236: post_wrong_arg_number(_Config) ->
  237:     Args = [{arg1, 10}, {arg2,2}, {arg3, 100}],
  238:     Path = <<"/api/weather">>,
  239:     {ok, Response} = request(Path, "POST", Args, admin),
  240:     check_status_code(Response, 404).
  241: 
  242: post_wrong_arg_name(_Config) ->
  243:     Args = [{arg11, 10}, {arg2,2}],
  244:     Path = <<"/api/weather">>,
  245:     {ok, Response} = request(Path, "POST", Args, admin),
  246:     check_status_code(Response, 400).
  247: 
  248: post_wrong_arg_type(_Config) ->
  249:     Args = [{arg1, 10}, {arg2,<<"weird binary">>}],
  250:     Path = <<"/api/weather">>,
  251:     {ok, Response} = request(Path, "POST", Args, admin),
  252:     check_status_code(Response, 400).
  253: 
  254: post_different_arg_order(_Config) ->
  255:     Arg1 = {arg1, 10},
  256:     Arg2 = {arg2, 2},
  257:     Args = [Arg2, Arg1],
  258:     Path = <<"/api/weather">>,
  259:     Result = binary_to_list(post_simple_command(element(2, Arg1), element(2, Arg2))),
  260:     {ok, Response} = request(Path, "POST", Args, admin),
  261:     check_status_code(Response, 201),
  262:     check_location_header(Response, list_to_binary(build_path_prefix() ++"/api/weather/" ++ Result)).
  263: 
  264: post_no_command(_Config) ->
  265:     Args = [{arg1, 10}, {arg2,2}],
  266:     Path = <<"/api/weather/10">>,
  267:     {ok, Response} = request(Path, "POST", Args, admin),
  268:     check_status_code(Response, 404).
  269: 
  270: 
  271: delete_wrong_arg_order(_Config) ->
  272:     Arg1 = {arg1, <<"ala_ma_kota">>},
  273:     Arg2 = {arg2, 2},
  274:     Base = "/api/music",
  275:     {ok, Response} = request(create_path_with_binds(Base, [Arg2, Arg1]), "DELETE", admin),
  276:     check_status_code(Response, 400).
  277: 
  278: delete_wrong_arg_types(_Config) ->
  279:     Arg1 = {arg1, 2},
  280:     Arg2 = {arg2, <<"ala_ma_kota">>},
  281:     Base = "/api/music",
  282:     {ok, Response} = request(create_path_with_binds(Base, [Arg1, Arg2]), "DELETE", admin),
  283:     check_status_code(Response, 400).
  284: 
  285: put_wrong_param_type(_Config) ->
  286:     Binds = [{username, <<"username">>}, {domain, <<"domain">>}],
  287:     Parameters = [{age, <<"23">>}, {kids, 10}],
  288:     Base = "/api/dragons",
  289:     {ok, Response} = request(create_path_with_binds(Base, Binds), "PUT", Parameters, admin),
  290:     check_status_code(Response, 400).
  291: 
  292: put_wrong_bind_type(_Config) ->
  293:     Binds = [{username, <<"username">>}, {domain, 123}],
  294:     Parameters = [{age, 23}, {kids, 10}],
  295:     Base = "/api/dragons",
  296:     {ok, Response} = request(create_path_with_binds(Base, Binds), "PUT", Parameters, admin),
  297:     check_status_code(Response, 400).
  298: 
  299: put_different_params_order(_Config) ->
  300:     Binds = [{username, <<"username">>}, {domain, <<"domain">>}],
  301:     Parameters = [{kids, 2}, {age, 45}],
  302:     Base = "/api/dragons",
  303:     {ok, Response} = request(create_path_with_binds(Base, Binds), "PUT", Parameters, admin),
  304:     check_status_code(Response, 200).
  305: 
  306: put_wrong_binds_order(_Config) ->
  307:     Binds = [{domain, <<"domain">>}, {username, <<"username">>}],
  308:     Parameters = [{kids, 2}, {age, 30}],
  309:     Base = "/api/dragons",
  310:     {ok, Response} = request(create_path_with_binds(Base, Binds), "PUT", Parameters, admin),
  311:     check_status_code(Response, 400).
  312: 
  313: put_too_less_params(_Config) ->
  314:     Binds = [{username, <<"username">>}, {domain, <<"domain">>}],
  315:     Parameters = [{kids, 3}],
  316:     Base = "/api/dragons",
  317:     {ok, Response} = request(create_path_with_binds(Base, Binds), "PUT", Parameters, admin),
  318:     check_status_code(Response, 400).
  319: 
  320: put_too_less_binds(_Config) ->
  321:     Binds = [{username, <<"username">>}],
  322:     Parameters = [{age, 20}, {kids, 3}],
  323:     Base = "/api/dragons",
  324:     {ok, Response} = request(create_path_with_binds(Base, Binds), "PUT", Parameters, admin),
  325:     check_status_code(Response, 404).
  326: 
  327: put_wrong_bind_name(_Config) ->
  328:     Binds = [{usersrejm, <<"username">>}, {domain, <<"localhost">>}],
  329:     Parameters = [{age, 20}, {kids, 3}],
  330:     Base = "/api/dragons",
  331:     {ok, Response} = request(create_path_with_binds(Base, Binds), "PUT", Parameters, admin),
  332:     check_status_code(Response, 404).
  333: 
  334: put_wrong_param_name(_Config) ->
  335:     Binds = [{username, <<"username">>}, {domain, <<"localhost">>}],
  336:     Parameters = [{age, 20}, {srids, 3}],
  337:     Base = "/api/dragons",
  338:     {ok, Response} = request(create_path_with_binds(Base, Binds), "PUT", Parameters, admin),
  339:     check_status_code(Response, 404).
  340: 
  341: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  342: %%%% Client side tests
  343: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  344: 
  345: get_simple_client(_Config) ->
  346:     Arg = {arg1, <<"bob@localhost">>},
  347:     Base = "/api/clients",
  348:     Username = <<"username@localhost">>,
  349:     Auth = {binary_to_list(Username), "secret"},
  350:     ExpectedBody = get_simple_client_command(Username, element(2, Arg)),
  351:     {ok, Response} = request(create_path_with_binds(Base, [Arg]), "GET", {Auth, true}),
  352:     check_status_code(Response, 200),
  353:     check_response_body(Response, ExpectedBody).
  354: 
  355: get_two_args_client(_Config) ->
  356:     Arg1 = {other, <<"bob@localhost">>},
  357:     Arg2 = {limit, 10},
  358:     Base = "/api/message",
  359:     Username = <<"alice@localhost">>,
  360:     Auth = {binary_to_list(Username), "secret"},
  361:     ExpectedBody = get_two_args_client_command(Username, element(2, Arg1), element(2, Arg2)),
  362:     {ok, Response} = request(create_path_with_binds(Base, [Arg1, Arg2]), "GET", {Auth, true}),
  363:     check_status_code(Response, 200),
  364:     check_response_body(Response, ExpectedBody).
  365: 
  366: get_bad_auth(_Config) ->
  367:     Arg = {arg1, <<"bob@localhost">>},
  368:     Base = "/api/clients",
  369:     Username = <<"username@localhost">>,
  370:     Auth = {binary_to_list(Username), "secret"},
  371:     get_simple_client_command(Username, element(2, Arg)),
  372:     {ok, Response} = request(create_path_with_binds(Base, [Arg]), "GET", {Auth, false}),
  373:     check_status_code(Response, 401).
  374: 
  375: post_simple_client(_Config) ->
  376:     Arg1 = {title, <<"Juliet's despair">>},
  377:     Arg2 = {content, <<"If they do see thee, they will murder thee!">>},
  378:     Base = <<"/api/ohmyromeo">>,
  379:     Username = <<"username@localhost">>,
  380:     Auth = {binary_to_list(Username), "secret"},
  381:     Result = binary_to_list(post_simple_client_command(Username, element(2, Arg1), element(2, Arg2))),
  382:     {ok, Response} = request(Base, "POST", [Arg1, Arg2], {Auth, true}),
  383:     check_status_code(Response, 201),
  384:     check_location_header(Response, list_to_binary(build_path_prefix() ++"/api/ohmyromeo/" ++ Result)).
  385: 
  386: put_simple_client(_Config) ->
  387:     Arg = {password, <<"ilovepancakes">>},
  388:     Base = <<"/api/superusers">>,
  389:     Username = <<"joe@localhost">>,
  390:     Auth = {binary_to_list(Username), "secretpassword"},
  391:     put_simple_client_command(Username, element(2, Arg)),
  392:     {ok, Response} = request(Base, "PUT", [Arg], {Auth, true}),
  393:     check_status_code(Response, 204).
  394: 
  395: delete_simple_client(_Config) ->
  396:     Arg = {name, <<"giant">>},
  397:     Base = "/api/bikes",
  398:     Username = <<"username@localhost">>,
  399:     Auth = {binary_to_list(Username), "secret"},
  400:     get_simple_client_command(Username, element(2, Arg)),
  401:     {ok, Response} = request(create_path_with_binds(Base, [Arg]), "DELETE", {Auth, true}),
  402:     check_status_code(Response, 204).
  403: 
  404: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  405: %%%% definitions
  406: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  407: 
  408: commands_client() ->
  409:     [
  410:      [
  411:       {name, get_simple_client},
  412:       {category, <<"clients">>},
  413:       {desc, <<"do nothing and return">>},
  414:       {module, ?MODULE},
  415:       {function, get_simple_client_command},
  416:       {action, read},
  417:       {identifiers, []},
  418:       {security_policy, [user]},
  419:       {args, [{caller, binary}, {arg1, binary}]},
  420:       {result, {result, binary}}
  421:      ],
  422:      [
  423:       {name, get_two_args_client},
  424:       {category, <<"message">>},
  425:       {desc, <<"do nothing and return">>},
  426:       {module, ?MODULE},
  427:       {function, get_two_args_client_command},
  428:       {action, read},
  429:       {identifiers, []},
  430:       {security_policy, [user]},
  431:       {args, [{caller, binary}, {other, binary}, {limit, integer}]},
  432:       {result, {result, binary}}
  433:      ],
  434:      [
  435:       {name, post_simple_client},
  436:       {category, <<"ohmyromeo">>},
  437:       {desc, <<"do nothing and return">>},
  438:       {module, ?MODULE},
  439:       {function, post_simple_client_command},
  440:       {action, create},
  441:       {identifiers, []},
  442:       {security_policy, [user]},
  443:       {args, [{caller, binary}, {title, binary}, {content, binary}]},
  444:       {result, {result, binary}}
  445:      ],
  446:      [
  447:       {name, put_simple_client},
  448:       {category, <<"superusers">>},
  449:       {desc, <<"do nothing and return">>},
  450:       {module, ?MODULE},
  451:       {function, put_simple_client_command},
  452:       {action, update},
  453:       {identifiers, [caller]},
  454:       {security_policy, [user]},
  455:       {args, [{caller, binary}, {password, binary}]},
  456:       {result, ok}
  457:      ],
  458:      [
  459:       {name, delete_simple_client},
  460:       {category, <<"bikes">>},
  461:       {desc, <<"do nothing and return">>},
  462:       {module, ?MODULE},
  463:       {function, delete_simple_client_command},
  464:       {action, delete},
  465:       {identifiers, []},
  466:       {security_policy, [user]},
  467:       {args, [{caller, binary}, {name, binary}]},
  468:       {result, ok}
  469:      ]
  470:     ].
  471: 
  472: commands_admin() ->
  473:     [
  474:      [
  475:       {name, get_simple},
  476:       {category, <<"users">>},
  477:       {desc, <<"do nothing and return">>},
  478:       {module, ?MODULE},
  479:       {function, get_simple_command},
  480:       {action, read},
  481:       {identifiers, []},
  482:       {args, [{arg1, binary}]},
  483:       {result, {result, binary}}
  484:      ],
  485:      [
  486:       {name, get_advanced},
  487:       {category, <<"animals">>},
  488:       {desc, <<"do nothing and return">>},
  489:       {module, ?MODULE},
  490:       {function, get_two_args_command},
  491:       {action, read},
  492:       {identifiers, []},
  493:       {args, [{arg1, integer}, {arg2, integer}]},
  494:       {result, {result, binary}}
  495:      ],
  496:      [
  497:       {name, get_advanced2},
  498:       {category, <<"books">>},
  499:       {desc, <<"do nothing and return">>},
  500:       {module, ?MODULE},
  501:       {function, get_two_args2_command},
  502:       {action, read},
  503:       {identifiers, []},
  504:       {args, [{one, integer}, {two, binary}]},
  505:       {result, {result, integer}}
  506:      ],
  507:      [
  508:       {name, post_simple},
  509:       {category, <<"weather">>},
  510:       {desc, <<"do nothing and return">>},
  511:       {module, ?MODULE},
  512:       {function, post_simple_command},
  513:       {action, create},
  514:       {identifiers, []},
  515:       {args, [{arg1, integer}, {arg2, integer}]},
  516:       {result, {result, binary}}
  517:      ],
  518:      [
  519:       {name, post_simple2},
  520:       {category, <<"weather">>},
  521:       {subcategory, <<"subcategory">>},
  522:       {desc, <<"do nothing and return">>},
  523:       {module, ?MODULE},
  524:       {function, post_simple_command},
  525:       {action, create},
  526:       {identifiers, [arg1]},
  527:       {args, [{arg1, integer}, {arg2, integer}]},
  528:       {result, {result, binary}}
  529:      ],
  530:      [
  531:       {name, delete_simple},
  532:       {category, <<"music">>},
  533:       {desc, <<"do nothing and return">>},
  534:       {module, ?MODULE},
  535:       {function, delete_simple_command},
  536:       {action, delete},
  537:       {identifiers, []},
  538:       {args, [{arg1, binary}, {arg2, integer}]},
  539:       {result, ok}
  540:      ],
  541:      [
  542:       {name, put_simple},
  543:       {category, <<"users">>},
  544:       {desc, <<"do nothing and return">>},
  545:       {module, ?MODULE},
  546:       {function, put_simple_command},
  547:       {action, update},
  548:       {args, [{arg1, binary}, {arg2, binary}, {arg3, binary}]},
  549:       {identifiers, [arg1, arg2]},
  550:       {result, ok}
  551:      ],
  552:      [
  553:       {name, put_advanced},
  554:       {category, <<"dragons">>},
  555:       {desc, <<"do nothing and return">>},
  556:       {module, ?MODULE},
  557:       {function, put_advanced_command},
  558:       {action, update},
  559:       {args, [{username, binary},
  560:               {domain, binary},
  561:               {age, integer},
  562:               {kids, integer}]},
  563:       {identifiers, [username, domain]},
  564:       {result, ok}
  565:      ]
  566:     ].
  567: 
  568: commands_new() ->
  569:     commands_admin() ++ commands_client().
  570: 
  571: 
  572: %% admin command funs
  573: get_simple_command(<<"bob@localhost">>) ->
  574:     <<"bob is OK">>.
  575: 
  576: get_two_args_command(1, 2) ->
  577:     <<"all is working">>.
  578: 
  579: get_two_args2_command(X, B) when is_integer(X) and is_binary(B) ->
  580:     100.
  581: 
  582: post_simple_command(_X, 2) ->
  583:     <<"new_resource">>.
  584: 
  585: delete_simple_command(Binary, 2) when is_binary(Binary) ->
  586:     10.
  587: 
  588: put_simple_command(_Arg1, _Arg2, _Arg3) ->
  589:     ok.
  590: 
  591: put_advanced_command(Arg1, Arg2, Arg3, Arg4) when is_binary(Arg1) and is_binary(Arg2)
  592:                                              and is_integer(Arg3) and is_integer(Arg4) ->
  593:     ok.
  594: 
  595: %% clients command funs
  596: get_simple_client_command(_Caller, _SomeBinary) ->
  597:     <<"client bob is OK">>.
  598: 
  599: get_two_args_client_command(_Caller, _SomeBinary, _SomeInteger) ->
  600:     <<"client2 bob is OK">>.
  601: 
  602: post_simple_client_command(_Caller, _Title, _Content) ->
  603:     <<"new_resource">>.
  604: 
  605: put_simple_client_command(_Username, _Password) ->
  606:     changed.
  607: 
  608: delete_simple_client_command(_Username, _BikeName) ->
  609:     changed.
  610: 
  611: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  612: %%%% utilities
  613: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  614: build_path_prefix() ->
  615:     "http://" ++ ?HOST ++ ":" ++ integer_to_list(?PORT).
  616: 
  617: maybe_add_body([]) ->
  618:     [];
  619: maybe_add_body(Args) ->
  620:     jiffy:encode(maps:from_list(Args)).
  621: 
  622: maybe_add_accepted_headers("POST") ->
  623:     accepted_headers();
  624: maybe_add_accepted_headers("PUT") ->
  625:     accepted_headers();
  626: maybe_add_accepted_headers(_) ->
  627:     [].
  628: 
  629: accepted_headers() ->
  630:     [{<<"Content-Type">>, <<"application/json">>}, {<<"Accept">>, <<"application/json">>}].
  631: 
  632: maybe_add_auth_header({User, Password}) ->
  633:     Basic = list_to_binary("Basic " ++ base64:encode_to_string(User ++ ":"++ Password)),
  634:     [{<<"authorization">>, Basic}];
  635: maybe_add_auth_header(admin) ->
  636:     [].
  637: 
  638: -spec create_path_with_binds(string(), list()) -> binary().
  639: create_path_with_binds(Base, ArgList) when is_list(ArgList) ->
  640:     list_to_binary(
  641:         lists:flatten(Base ++ ["/" ++ to_list(ArgValue)
  642:                                || {ArgName, ArgValue} <- ArgList])).
  643: 
  644: to_list(Int) when is_integer(Int) ->
  645:     integer_to_list(Int);
  646: to_list(Float) when is_float(Float) ->
  647:     float_to_list(Float);
  648: to_list(Bin) when is_binary(Bin) ->
  649:     binary_to_list(Bin);
  650: to_list(Atom) when is_atom(Atom) ->
  651:     atom_to_list(Atom);
  652: to_list(Other) ->
  653:     Other.
  654: 
  655: -spec request(binary(), method(), admin | {{binary(), binary()}, boolean()}) -> any.
  656: request(Path, "GET", Entity) ->
  657:     request(Path, "GET", [], Entity);
  658: request(Path, "DELETE", Entity) ->
  659:     request(Path, "DELETE", [], Entity).
  660: 
  661: -spec request(binary(), method(), list({atom(), any()}),
  662:               {headers, list()} | admin | {{binary(), binary()}, boolean()}) -> any.
  663: do_request(Path, Method, Body, {headers, Headers}) ->
  664:     {ok, Pid} = fusco:start_link("http://"++ ?HOST ++ ":" ++ integer_to_list(?PORT), []),
  665:     R = fusco:request(Pid, Path, Method, Headers, Body, 5000),
  666:     fusco:disconnect(Pid),
  667:     teardown(),
  668:     R.
  669: 
  670: request(Path, Method, BodyData, {{_User, _Pass} = Auth, Authorized}) ->
  671:     setup(client_module()),
  672:     meck:expect(ejabberd_auth, check_password, fun(_, _) -> Authorized end),
  673:     Body = maybe_add_body(BodyData),
  674:     AuthHeader = maybe_add_auth_header(Auth),
  675:     AcceptHeader = maybe_add_accepted_headers(Method),
  676:     do_request(Path, Method, Body, {headers, AuthHeader ++ AcceptHeader});
  677: request(Path, Method, BodyData, admin) ->
  678:     ct:pal("~p, ~p, ~p", [Path, Method, BodyData]),
  679:     setup(backend_module()),
  680:     Body = maybe_add_body(BodyData),
  681:     AcceptHeader = maybe_add_accepted_headers(Method),
  682:     do_request(Path, Method, Body, {headers, AcceptHeader}).
  683: 
  684: mc_holder() ->
  685:     erlang:register(mc_holder_proc, self()),
  686:     mongoose_commands:init(),
  687:     mongoose_commands:register(commands_new()),
  688:     receive
  689:         _ -> ok
  690:     end,
  691:     erlang:unregister(mc_holder_proc).
  692: 
  693: check_status_code(Response, Code) when is_integer(Code) ->
  694:     {{ResCode, _}, _, _, _, _} = Response,
  695:     ?assertEqual(Code, binary_to_integer(ResCode));
  696: check_status_code(_R, Code) ->
  697:     ?assertEqual(Code, not_a_number).
  698: 
  699: check_response_body(Response, ExpectedBody) ->
  700:     {_, _, Body, _ , _} = Response,
  701:     ?assertEqual(binary_to_list(Body), "\"" ++ binary_to_list(ExpectedBody) ++ "\"").
  702: 
  703: check_location_header(Response, Path) ->
  704:     {_, Headers, _, _ , _} = Response,
  705:     Location = proplists:get_value(<<"location">>, Headers),
  706:     ?assertEqual(Path, Location).