1: -module(rest_client_SUITE).
    2: -compile([export_all, nowarn_export_all]).
    3: 
    4: -include_lib("escalus/include/escalus.hrl").
    5: -include_lib("eunit/include/eunit.hrl").
    6: -include_lib("eunit/include/eunit.hrl").
    7: -include_lib("common_test/include/ct.hrl").
    8: 
    9: -import(rest_helper,
   10:         [decode_maplist/1,
   11:          gett/3,
   12:          post/4,
   13:          putt/4,
   14:          delete/3,
   15:          delete/4]
   16:          ).
   17: 
   18: -import(domain_helper, [host_type/0]).
   19: -import(config_parser_helper, [mod_config/2]).
   20: 
   21: -define(OK, {<<"200">>, <<"OK">>}).
   22: -define(CREATED, {<<"201">>, <<"Created">>}).
   23: -define(NOCONTENT, {<<"204">>, <<"No Content">>}).
   24: -define(NOT_FOUND, {<<"404">>, <<"Not Found">>}).
   25: -define(BAD_REQUEST, {<<"400">>, <<"Bad Request">>}).
   26: -define(UNAUTHORIZED, {<<"401">>, <<"Unauthorized">>}).
   27: -define(FORBIDDEN, {<<"403">>, <<"Forbidden">>}).
   28: 
   29: %% --------------------------------------------------------------------
   30: %% Common Test stuff
   31: %% --------------------------------------------------------------------
   32: 
   33: all() ->
   34:     [{group, messages},
   35:      {group, muc},
   36:      {group, muc_config},
   37:      {group, muc_disabled},
   38:      {group, roster},
   39:      {group, messages_with_props},
   40:      {group, messages_with_thread},
   41:      {group, security}].
   42: 
   43: groups() ->
   44:     [{messages_with_props, [parallel], message_with_props_test_cases()},
   45:      {messages_with_thread, [parallel], message_with_thread_test_cases()},
   46:      {messages, [parallel], message_test_cases()},
   47:      {muc, [parallel], muc_test_cases()},
   48:      {muc_config, [], muc_config_cases()},
   49:      {muc_disabled, [parallel], muc_disabled_cases()},
   50:      {roster, [parallel], roster_test_cases()},
   51:      {security, [], security_test_cases()}].
   52: 
   53: message_test_cases() ->
   54:     [msg_is_sent_and_delivered_over_xmpp,
   55:      msg_is_sent_and_delivered_over_sse,
   56:      message_sending_errors,
   57:      all_messages_are_archived,
   58:      messages_with_user_are_archived,
   59:      messages_can_be_paginated,
   60:      message_query_errors].
   61: 
   62: muc_test_cases() ->
   63:      [room_is_created,
   64:       room_is_created_with_given_identifier,
   65:       room_creation_errors,
   66:       room_query_errors,
   67:       user_is_invited_to_a_room,
   68:       user_is_removed_from_a_room,
   69:       user_removal_errors,
   70:       rooms_can_be_listed,
   71:       owner_can_leave_a_room_and_auto_select_owner,
   72:       user_can_leave_a_room,
   73:       invitation_to_room_is_forbidden_for_non_member,
   74:       msg_is_sent_and_delivered_in_room,
   75:       room_message_sending_errors,
   76:       messages_are_archived_in_room,
   77:       chat_markers_are_archived_in_room,
   78:       room_message_query_errors,
   79:       markable_property_is_archived_in_room,
   80:       only_room_participant_can_read_messages,
   81:       messages_can_be_paginated_in_room,
   82:       room_msg_is_sent_and_delivered_over_sse,
   83:       aff_change_msg_is_delivered_over_sse,
   84:       room_is_created_with_given_jid,
   85:       room_is_not_created_with_jid_not_matching_hostname,
   86:       room_can_be_fetched_by_jid,
   87:       messages_can_be_sent_and_fetched_by_room_jid,
   88:       user_can_be_added_and_removed_by_room_jid
   89:      ].
   90: 
   91: muc_config_cases() ->
   92:     [
   93:       config_can_be_changed_by_owner,
   94:       config_cannot_be_changed_by_member,
   95:       config_cannot_be_changed_by_non_member,
   96:       config_change_errors,
   97:       config_can_be_changed_by_all
   98:     ].
   99: 
  100: muc_disabled_cases() ->
  101:     [muc_disabled_errors].
  102: 
  103: roster_test_cases() ->
  104:     [add_contact_and_invite,
  105:      add_contact_and_be_invited,
  106:      add_and_remove,
  107:      add_and_remove_some_contacts_properly,
  108:      add_and_remove_some_contacts_with_nonexisting,
  109:      roster_errors].
  110: 
  111: message_with_props_test_cases() ->
  112:     [
  113:      msg_with_props_is_sent_and_delivered_over_xmpp,
  114:      msg_with_props_can_be_parsed,
  115:      msg_with_malformed_props_can_be_parsed,
  116:      msg_with_malformed_props_is_sent_and_delivered_over_xmpp
  117:      ].
  118: 
  119: message_with_thread_test_cases() ->
  120:     [msg_with_thread_is_sent_and_delivered_over_xmpp,
  121:      msg_with_thread_can_be_parsed,
  122:      msg_with_thread_and_parent_is_sent_and_delivered_over_xmpp,
  123:      msg_with_thread_and_parent_can_be_parsed,
  124:      msg_without_thread_can_be_parsed,
  125:      msg_without_thread_is_sent_and_delivered_over_xmpp].
  126: 
  127: security_test_cases() ->
  128:     [
  129:      default_http_server_name_is_returned_if_not_changed,
  130:      non_default_http_server_name_is_returned_if_configured
  131:     ].
  132: 
  133: init_per_suite(Config) ->
  134:     Config1 = init_modules(Config),
  135:     [{muc_light_host, muc_light_helper:muc_host()}
  136:      | escalus:init_per_suite(Config1)].
  137: 
  138: end_per_suite(Config) ->
  139:     escalus_fresh:clean(),
  140:     dynamic_modules:restore_modules(Config),
  141:     escalus:end_per_suite(Config).
  142: 
  143: init_modules(Config) ->
  144:     HostType = host_type(),
  145:     Config1 = dynamic_modules:save_modules(HostType, Config),
  146:     Config2 = rest_helper:maybe_enable_mam(mam_helper:backend(), HostType, Config1),
  147:     dynamic_modules:ensure_modules(HostType, required_modules(suite)),
  148:     Config2.
  149: 
  150: init_per_group(muc_disabled = GN, Config) ->
  151:     HostType = host_type(),
  152:     Config1 = dynamic_modules:save_modules(HostType, Config),
  153:     dynamic_modules:ensure_modules(HostType, required_modules(GN)),
  154:     Config1;
  155: init_per_group(_GN, Config) ->
  156:     Config.
  157: 
  158: end_per_group(muc_disabled, Config) ->
  159:     dynamic_modules:restore_modules(Config);
  160: end_per_group(_GN, _Config) ->
  161:     ok.
  162: 
  163: init_per_testcase(config_can_be_changed_by_all = TC, Config) ->
  164:     HostType = host_type(),
  165:     DefaultConfig = dynamic_modules:save_modules(HostType, Config),
  166:     dynamic_modules:ensure_modules(HostType, required_modules(TC)),
  167:     escalus:init_per_testcase(config_can_be_changed_by_all, DefaultConfig);
  168: init_per_testcase(TC, Config) ->
  169:     MAMTestCases = [all_messages_are_archived,
  170:                     messages_with_user_are_archived,
  171:                     messages_can_be_paginated,
  172:                     messages_are_archived_in_room,
  173:                     chat_markers_are_archived_in_room,
  174:                     markable_property_is_archived_in_room,
  175:                     only_room_participant_can_read_messages,
  176:                     messages_can_be_paginated_in_room,
  177:                     messages_can_be_sent_and_fetched_by_room_jid,
  178:                     msg_with_props_is_sent_and_delivered_over_xmpp,
  179:                     msg_with_props_can_be_parsed,
  180:                     msg_with_malformed_props_can_be_parsed,
  181:                     msg_with_malformed_props_is_sent_and_delivered_over_xmpp,
  182:                     msg_with_thread_is_sent_and_delivered_over_xmpp,
  183:                     msg_with_thread_can_be_parsed,
  184:                     msg_with_thread_and_parent_is_sent_and_delivered_over_xmpp,
  185:                     msg_with_thread_and_parent_can_be_parsed,
  186:                     msg_without_thread_can_be_parsed,
  187:                     msg_without_thread_is_sent_and_delivered_over_xmpp
  188:                    ],
  189:     rest_helper:maybe_skip_mam_test_cases(TC, MAMTestCases, Config).
  190: 
  191: end_per_testcase(config_can_be_changed_by_all, Config) ->
  192:     dynamic_modules:restore_modules(Config),
  193:     escalus:end_per_testcase(config_can_be_changed_by_all, Config);
  194: end_per_testcase(TC, C) ->
  195:     escalus:end_per_testcase(TC, C).
  196: 
  197: %% Module configuration - set up per suite and for special test cases
  198: %% TODO: include MAM configuration here
  199: 
  200: required_modules(muc_disabled) ->
  201:     [{mod_muc_light, stopped}];
  202: required_modules(SuiteOrTC) ->
  203:     Opts = maps:merge(common_muc_light_opts(), muc_light_opts(SuiteOrTC)),
  204:     [{mod_muc_light, mod_config(mod_muc_light, Opts)}].
  205: 
  206: muc_light_opts(config_can_be_changed_by_all) ->
  207:     #{all_can_configure => true};
  208: muc_light_opts(suite) ->
  209:     #{}.
  210: 
  211: common_muc_light_opts() ->
  212:     #{rooms_in_rosters => true,
  213:       backend => mongoose_helper:mnesia_or_rdbms_backend()}.
  214: 
  215: %% --------------------------------------------------------------------
  216: %% Test cases
  217: %% --------------------------------------------------------------------
  218: 
  219: msg_is_sent_and_delivered_over_xmpp(Config) ->
  220:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  221:         M = send_message(alice, Alice, Bob),
  222:         Msg = escalus:wait_for_stanza(Bob),
  223:         escalus:assert(is_chat_message, [maps:get(body, M)], Msg)
  224:     end).
  225: 
  226: msg_is_sent_and_delivered_over_sse(ConfigIn) ->
  227:     Config = escalus_fresh:create_users(ConfigIn, [{alice, 1}, {bob, 1}]),
  228:     Bob = escalus_users:get_userspec(Config, bob),
  229:     Alice = escalus_users:get_userspec(Config, alice),
  230: 
  231:     {200, Conn} = connect_to_sse({alice, Alice}),
  232:     M = send_message(bob, Bob, Alice),
  233: 
  234:     Event = sse_helper:wait_for_event(Conn),
  235:     assert_json_message(M, Event),
  236:     sse_helper:stop_sse(Conn).
  237: 
  238: message_sending_errors(Config) ->
  239:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  240:         BobJID = user_jid(Bob),
  241:         M = #{to => BobJID, body => <<"hello, ", BobJID/binary, " it's me">>},
  242:         Cred = credentials({alice, Alice}),
  243:         {?BAD_REQUEST, <<"Missing message body">>} =
  244:             post(client, <<"/messages">>, maps:remove(body, M), Cred),
  245:         {?BAD_REQUEST, <<"Missing recipient JID">>} =
  246:             post(client, <<"/messages">>, maps:remove(to, M), Cred),
  247:         {?BAD_REQUEST, <<"Invalid recipient JID">>} =
  248:             post(client, <<"/messages">>, M#{to => <<"@invalid">>}, Cred)
  249:     end).
  250: 
  251: room_msg_is_sent_and_delivered_over_sse(ConfigIn) ->
  252:     Config = escalus_fresh:create_users(ConfigIn, [{alice, 1}, {bob, 1}]),
  253:     Bob = escalus_users:get_userspec(Config, bob),
  254:     Alice = escalus_users:get_userspec(Config, alice),
  255:     RoomID = given_new_room_with_users({alice, Alice}, [{bob, Bob}]),
  256:     RoomInfo = get_room_info({alice, Alice}, RoomID),
  257:     true = is_participant(Bob, <<"member">>, RoomInfo),
  258:     {200, Conn} = connect_to_sse({bob, Bob}),
  259:     Message = given_message_sent_to_room(RoomID, {alice, Alice}),
  260:     Event = sse_helper:wait_for_event(Conn),
  261:     assert_json_room_sse_message(Message#{room => RoomID, type => <<"message">>}, Event),
  262:     sse_helper:stop_sse(Conn).
  263: 
  264: aff_change_msg_is_delivered_over_sse(ConfigIn) ->
  265:     Config = escalus_fresh:create_users(ConfigIn, [{alice, 1}, {bob, 1}]),
  266:     Bob = escalus_users:get_userspec(Config, bob),
  267:     Alice = escalus_users:get_userspec(Config, alice),
  268:     RoomID = given_new_room({alice, Alice}),
  269:     {200, Conn} = connect_to_sse({bob, Bob}),
  270:     given_user_invited({alice, Alice}, RoomID, Bob),
  271:     Event = sse_helper:wait_for_event(Conn),
  272:     BobJID = user_jid(Bob),
  273:     RoomJID = room_jid(RoomID, Config),
  274:     assert_json_room_sse_message(#{room => RoomID,
  275:                                    from => RoomJID,
  276:                                    type => <<"affiliation">>,
  277:                                    user => BobJID}, Event),
  278:     sse_helper:stop_sse(Conn).
  279: 
  280: all_messages_are_archived(Config) ->
  281:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  282:         Sent = [M1 | _] = send_messages(Config, Alice, Bob, Kate),
  283:         AliceJID = maps:get(to, M1),
  284:         AliceCreds = {AliceJID, user_password(alice)},
  285:         GetPath = lists:flatten("/messages/"),
  286:         {?OK, Msgs} = rest_helper:gett(client, GetPath, AliceCreds),
  287:         Received = [_Msg1, _Msg2, _Msg3] = rest_helper:decode_maplist(Msgs),
  288:         assert_messages(Sent, Received)
  289: 
  290:     end).
  291: 
  292: messages_with_user_are_archived(Config) ->
  293:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  294:         [M1, _M2, M3] = send_messages(Config, Alice, Bob, Kate),
  295:         AliceJID = maps:get(to, M1),
  296:         KateJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Kate)),
  297:         AliceCreds = {AliceJID, user_password(alice)},
  298:         GetPath = lists:flatten(["/messages/", binary_to_list(KateJID)]),
  299:         {?OK, Msgs} = rest_helper:gett(client, GetPath, AliceCreds),
  300:         Recv = [_Msg2] = rest_helper:decode_maplist(Msgs),
  301:         assert_messages([M3], Recv)
  302: 
  303:     end).
  304: 
  305: messages_can_be_paginated(Config) ->
  306:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  307:         AliceJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Alice)),
  308:         BobJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)),
  309:         rest_helper:fill_archive(Alice, Bob),
  310:         mam_helper:maybe_wait_for_archive(Config),
  311:         AliceCreds = {AliceJID, user_password(alice)},
  312:         % recent msgs with a limit
  313:         M1 = get_messages(AliceCreds, BobJID, 10),
  314:         6 = length(M1),
  315:         M2 = get_messages(AliceCreds, BobJID, 3),
  316:         3 = length(M2),
  317:         % older messages - earlier then the previous midnight
  318:         PriorTo = rest_helper:make_timestamp(-1, {0, 0, 1}),
  319:         M3 = get_messages(AliceCreds, BobJID, PriorTo, 10),
  320:         4 = length(M3),
  321:         [Oldest|_] = M3,
  322:         <<"A">> = maps:get(body, Oldest),
  323:         % same with limit
  324:         M4 = get_messages(AliceCreds, BobJID, PriorTo, 2),
  325:         2 = length(M4),
  326:         [Oldest2|_] = M4,
  327:         <<"B">> = maps:get(body, Oldest2)
  328:     end).
  329: 
  330: message_query_errors(Config) ->
  331:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  332:         BobJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)),
  333:         Creds = credentials({alice, Alice}),
  334:         Path = <<"/messages/", BobJID/binary>>,
  335:         {?BAD_REQUEST, <<"Invalid interlocutor JID">>} =
  336:             rest_helper:gett(client, <<"/messages/@invalid">>, Creds),
  337:         {?BAD_REQUEST, <<"Invalid limit">>} =
  338:             rest_helper:gett(client, <<Path/binary, "?limit=x">>, Creds),
  339:         {?BAD_REQUEST, <<"Invalid value of 'before'">>} =
  340:             rest_helper:gett(client, <<Path/binary, "?before=x">>, Creds),
  341:         {?BAD_REQUEST, <<"Invalid query string">>} =
  342:             rest_helper:gett(client, <<Path/binary, "?kuropatwa">>, Creds)
  343:     end).
  344: 
  345: room_is_created(Config) ->
  346:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  347:         RoomID = given_new_room({alice, Alice}),
  348:         RoomInfo = get_room_info({alice, Alice}, RoomID),
  349:         assert_room_info(Alice, RoomInfo)
  350:     end).
  351: 
  352: room_query_errors(Config) ->
  353:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  354:         RoomID = given_new_room_with_users({alice, Alice}, []),
  355:         Creds = credentials({bob, Bob}),
  356:         {?NOT_FOUND, <<"Room not found">>} =
  357:             rest_helper:gett(client, <<"/rooms/badroom">>, Creds),
  358:         {?FORBIDDEN, _} =
  359:             rest_helper:gett(client, <<"/rooms/", RoomID/binary>>, Creds)
  360:     end).
  361: 
  362: muc_disabled_errors(Config) ->
  363:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  364:         Creds = credentials({alice, Alice}),
  365:         {?NOT_FOUND, <<"MUC Light server not found">>} =
  366:             rest_helper:gett(client, <<"/rooms/badroom">>, Creds)
  367:     end).
  368: 
  369: room_is_created_with_given_identifier(Config) ->
  370:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  371:         GivenRoomID = muc_helper:fresh_room_name(),
  372:         GivenRoomID = given_new_room({alice, Alice}, GivenRoomID),
  373:         RoomInfo = get_room_info({alice, Alice}, GivenRoomID),
  374:         assert_room_info(Alice, RoomInfo)
  375:     end).
  376: 
  377: room_creation_errors(Config) ->
  378:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  379:         RoomID = muc_helper:fresh_room_name(),
  380:         Room = #{name => <<"My Room">>, subject => <<"My Secrets">>},
  381:         Path = <<"/rooms/", RoomID/binary>>,
  382:         Creds = credentials({alice, Alice}),
  383:         {?BAD_REQUEST, <<"Missing room ID">>} =
  384:             putt(client, "/rooms", Room, Creds),
  385:         {?BAD_REQUEST, <<"Invalid room ID">>} =
  386:             putt(client, "/rooms/@invalid", Room, Creds),
  387:         {?BAD_REQUEST, <<"Missing room name">>} =
  388:             putt(client, Path, maps:remove(name, Room), Creds),
  389:         {?BAD_REQUEST, <<"Missing room subject">>} =
  390:             putt(client, Path, maps:remove(subject, Room), Creds),
  391:         {?CREATED, _} =
  392:             putt(client, Path, Room, Creds),
  393:         {?FORBIDDEN, _} =
  394:             putt(client, Path, Room, Creds)
  395:     end).
  396: 
  397: config_can_be_changed_by_owner(Config) ->
  398:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  399:         RoomID = muc_helper:fresh_room_name(),
  400:         RoomJID = room_jid(RoomID, Config),
  401:         RoomID = given_new_room({alice, Alice}, RoomJID, <<"old_name">>),
  402:         RoomInfo = get_room_info({alice, Alice}, RoomID),
  403:         assert_property_value(<<"name">>, <<"old_name">>, RoomInfo),
  404: 
  405:         {{<<"204">>,<<"No Content">>},<<>>} =
  406:             when_config_change({alice, Alice}, RoomJID, <<"new_name">>, <<"new_subject">>),
  407:         NewRoomInfo = get_room_info({alice, Alice}, RoomID),
  408:         assert_property_value(<<"name">>, <<"new_name">>, NewRoomInfo),
  409:         assert_property_value(<<"subject">>, <<"new_subject">>, NewRoomInfo)
  410:     end).
  411: 
  412: config_cannot_be_changed_by_member(Config) ->
  413:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  414:         RoomID = given_new_room_with_users({alice, Alice}, [{bob, Bob}]),
  415:         RoomJID = room_jid(RoomID, Config),
  416:         {?FORBIDDEN, _} =
  417:             when_config_change({bob, Bob}, RoomJID, <<"other_name">>, <<"other_subject">>),
  418:         NewRoomInfo = get_room_info({bob, Bob}, RoomID),
  419:         assert_property_value(<<"name">>, <<"new_room_name">>, NewRoomInfo)
  420:     end).
  421: 
  422: config_cannot_be_changed_by_non_member(Config) ->
  423:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  424:         RoomID = given_new_room_with_users({alice, Alice}, []),
  425:         RoomJID = room_jid(RoomID, Config),
  426:         {?FORBIDDEN, _} =
  427:             when_config_change({bob, Bob}, RoomJID, <<"other_name">>, <<"other_subject">>),
  428:         NewRoomInfo = get_room_info({alice, Alice}, RoomID),
  429:         assert_property_value(<<"name">>, <<"new_room_name">>, NewRoomInfo)
  430:     end).
  431: 
  432: config_change_errors(Config) ->
  433:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  434:         RoomID = given_new_room_with_users({alice, Alice}, []),
  435:         RoomJID = room_jid(RoomID, Config),
  436:         {?NOT_FOUND, _} =
  437:             when_config_change({alice, Alice}, <<"badroom">>, <<"other_name">>, <<"other_subject">>),
  438:         {?BAD_REQUEST, <<"Validation failed ", _/binary>>} =
  439:             when_config_change({alice, Alice}, RoomJID, <<"other_name">>, 123),
  440:         NewRoomInfo = get_room_info({alice, Alice}, RoomID),
  441:         assert_property_value(<<"name">>, <<"new_room_name">>, NewRoomInfo)
  442:     end).
  443: 
  444: config_can_be_changed_by_all(Config) ->
  445:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  446:         RoomID = given_new_room_with_users({alice, Alice}, [{bob, Bob}]),
  447:         RoomJID = room_jid(RoomID, Config),
  448:         RoomInfo = get_room_info({alice, Alice}, RoomID),
  449:         assert_property_value(<<"name">>,<<"new_room_name">>,RoomInfo),
  450:         {{<<"204">>,<<"No Content">>},<<>>} =
  451:             when_config_change({bob, Bob}, RoomJID, <<"other_name">>, <<"other_subject">>),
  452:         NewRoomInfo = get_room_info({alice, Alice}, RoomID),
  453:         assert_property_value(<<"name">>,<<"other_name">>,NewRoomInfo)
  454:     end).
  455: 
  456: rooms_can_be_listed(Config) ->
  457:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  458:         [] = get_my_rooms({alice, Alice}),
  459:         RoomID = given_new_room_with_users({alice, Alice}, [{bob, Bob}]),
  460:         [{Room}] = get_my_rooms({alice, Alice}),
  461:         RoomMap = maps:from_list(Room),
  462:         RoomID = maps:get(<<"id">>, RoomMap),
  463:         true = maps:is_key(<<"name">>, RoomMap),
  464:         true = maps:is_key(<<"subject">>, RoomMap),
  465:         [{Room}] = get_my_rooms({bob, Bob})
  466:     end).
  467: 
  468: user_is_invited_to_a_room(Config) ->
  469:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  470:         RoomID = given_new_room_with_users({alice, Alice}, [{bob, Bob}]),
  471:         RoomInfo = get_room_info({alice, Alice}, RoomID),
  472:         true = is_participant(Bob, <<"member">>, RoomInfo),
  473:         IQ = escalus_stanza:iq_get(<<"urn:xmpp:muclight:0#affiliations">>, []),
  474:         RoomJID = room_jid(RoomID, Config),
  475:         escalus:send(Alice, escalus_stanza:to(IQ, RoomJID)),
  476:         escalus:assert(is_iq_result, [IQ], escalus:wait_for_stanza(Alice))
  477: 
  478:     end).
  479: 
  480: user_is_removed_from_a_room(Config) ->
  481:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  482:         RoomID = given_new_room_with_users({alice, Alice}, [{bob, Bob}]),
  483:         {{<<"204">>, _}, _} = remove_user_from_a_room({alice, Alice}, RoomID, Bob),
  484:         Stanza = escalus:wait_for_stanza(Bob),
  485:         assert_aff_change_stanza(Stanza, Bob, <<"none">>)
  486:     end).
  487: 
  488: user_removal_errors(Config) ->
  489:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  490:         RoomID = given_new_room_with_users({alice, Alice}, [{bob, Bob}]),
  491:         Path = <<"/rooms/", RoomID/binary, "/users/">>,
  492:         BobJid = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)),
  493:         AliceJid = escalus_utils:jid_to_lower(escalus_client:short_jid(Alice)),
  494:         Creds = credentials({alice, Alice}),
  495:         {?BAD_REQUEST, <<"Invalid user JID: @invalid">>} =
  496:             rest_helper:delete(client, <<Path/binary, "@invalid">>, Creds),
  497:         {?BAD_REQUEST, <<"Missing JID">>} =
  498:             rest_helper:delete(client, Path, Creds),
  499:         {?FORBIDDEN, <<"Given user does not have permission", _/binary>>} =
  500:             rest_helper:delete(client, <<Path/binary, AliceJid/binary>>, credentials({bob, Bob})),
  501:         {?NOT_FOUND, <<"Room not found">>} =
  502:             rest_helper:delete(client, <<"/rooms/badroom/users/", BobJid/binary>>, Creds)
  503:     end).
  504: 
  505: owner_can_leave_a_room_and_auto_select_owner(Config) ->
  506:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  507:         RoomID = given_new_room_with_users({alice, Alice}, [{bob, Bob}]),
  508:         {{<<"204">>, _}, _} = remove_user_from_a_room({alice, Alice}, RoomID, Alice),
  509:         Stanza = escalus:wait_for_stanza(Bob),
  510:         assert_aff_change_stanza(Stanza, Alice, <<"none">>),
  511:         assert_aff_change_stanza(Stanza, Bob, <<"owner">>)
  512:     end).
  513: 
  514: user_can_leave_a_room(Config) ->
  515:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  516:         RoomID = given_new_room_with_users({alice, Alice}, [{bob, Bob}]),
  517:         {{<<"204">>, _}, _} = remove_user_from_a_room({bob, Bob}, RoomID, Bob),
  518:         Stanza = escalus:wait_for_stanza(Bob),
  519:         assert_aff_change_stanza(Stanza, Bob, <<"none">>)
  520:     end).
  521: 
  522: invitation_to_room_is_forbidden_for_non_member(Config) ->
  523:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  524:         RoomID = given_new_room({alice, Alice}),
  525:         {?FORBIDDEN, _ } = invite_to_room({bob, Bob}, RoomID, <<"auser@domain.com">>)
  526:     end).
  527: 
  528: msg_is_sent_and_delivered_in_room(Config) ->
  529:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  530:         given_new_room_with_users_and_msgs({alice, Alice}, [{bob, Bob}])
  531:     end).
  532: 
  533: room_message_sending_errors(Config) ->
  534:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  535:         Sender = {alice, Alice},
  536:         RoomID = given_new_room_with_users(Sender, []),
  537:         InvalidMarker = #{type => <<"bad">>, id => <<"some_id">>},
  538:         {?BAD_REQUEST, <<"Invalid message body">>} =
  539:             given_message_sent_to_room(RoomID, Sender, #{body => #{body => <<"Too nested">>}}),
  540:         {?BAD_REQUEST, <<"No valid message elements">>} =
  541:             given_message_sent_to_room(RoomID, Sender, #{no_body => <<"This should be in body">>}),
  542:         {?BAD_REQUEST, <<"No valid message elements">>} =
  543:             given_message_sent_to_room(RoomID, Sender, #{markable => true}),
  544:         {?BAD_REQUEST, <<"Invalid chat marker">>} =
  545:             given_message_sent_to_room(RoomID, Sender, #{chat_marker => InvalidMarker}),
  546:         {?BAD_REQUEST, <<"Invalid request body">>} =
  547:             given_message_sent_to_room(RoomID, Sender, <<"This is not JSON object">>),
  548:         {?FORBIDDEN, _} =
  549:             given_message_sent_to_room(RoomID, {bob, Bob}, #{body => <<"Hi">>}),
  550:         {?NOT_FOUND, <<"Room not found">>} =
  551:             given_message_sent_to_room(<<"badroom">>, Sender, #{body => <<"Hi">>})
  552:     end).
  553: 
  554: messages_are_archived_in_room(Config) ->
  555:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  556:         {RoomID, _Msgs} = given_new_room_with_users_and_msgs({alice, Alice}, [{bob, Bob}]),
  557:         mam_helper:maybe_wait_for_archive(Config),
  558:         {?OK, Result} = get_room_messages({alice, Alice}, RoomID),
  559:         [Aff, _Msg1, _Msg2] = rest_helper:decode_maplist(Result),
  560:         %% The oldest message is aff change
  561:         <<"affiliation">> = maps:get(type, Aff),
  562:         <<"member">> = maps:get(affiliation, Aff),
  563:         BobJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)),
  564:         BobJID = maps:get(user, Aff)
  565:     end).
  566: 
  567: chat_markers_are_archived_in_room(Config) ->
  568:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  569:         % GIVEN 3 different chat markers that are sent via HTTP and received via XMPP
  570:         MarkedID = <<"RagnarokIsComing">>,
  571:         MarkerTypes = [<<"received">>, <<"displayed">>, <<"acknowledged">>],
  572:         Markers = [#{ chat_marker => #{ type => Type, id => MarkedID } } || Type <- MarkerTypes ],
  573:         RoomID = given_new_room_with_users({alice, Alice}, [{bob, Bob}]),
  574:         lists:foreach(fun(Marker) ->
  575:                               {?OK, {_Result}} =
  576:                               given_message_sent_to_room(RoomID, {bob, Bob}, Marker),
  577:                               [ escalus:wait_for_stanza(Client) || Client <- [Alice, Bob] ]
  578:                       end, Markers),
  579:         mam_helper:maybe_wait_for_archive(Config),
  580: 
  581:         % WHEN an archive is queried via HTTP
  582:         {?OK, Result} = get_room_messages({alice, Alice}, RoomID),
  583: 
  584:         % THEN these markers are retrieved and in proper order and with valid payload
  585:         % (we discard remaining msg fields, they are tested by other cases)
  586:         [_Aff | ReceivedMarkers] = rest_helper:decode_maplist(Result),
  587:         Markers = [ maps:with([chat_marker], RecvMarker) || RecvMarker <- ReceivedMarkers ]
  588:     end).
  589: 
  590: % Combo test case which verifies both the translation of "markable" element
  591: % (JSON -> XML -> JSON) and if it's preserved properly in the archive
  592: markable_property_is_archived_in_room(Config) ->
  593:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  594:         % GIVEN a markable message is sent in the room
  595:         MarkableMsg = #{ markable => true, body => <<"Floor is lava!">> },
  596:         RoomID = given_new_room_with_users({alice, Alice}, [{bob, Bob}]),
  597:         {?OK, {_Result}}
  598:         = given_message_sent_to_room(RoomID, {bob, Bob}, MarkableMsg),
  599:         [ escalus:wait_for_stanza(Client) || Client <- [Alice, Bob] ],
  600:         mam_helper:maybe_wait_for_archive(Config),
  601: 
  602:         % WHEN an archive is queried via HTTP
  603:         {?OK, Result} = get_room_messages({alice, Alice}, RoomID),
  604: 
  605:         % THEN the retrieved message has markable property
  606:         [_Aff, Msg] = rest_helper:decode_maplist(Result),
  607:         true = maps:get(markable, Msg, undefined)
  608:     end).
  609: 
  610: only_room_participant_can_read_messages(Config) ->
  611:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  612:         RoomID = given_new_room({alice, Alice}),
  613:         {?FORBIDDEN, _} = get_room_messages({bob, Bob}, RoomID),
  614:         ok
  615:     end).
  616: 
  617: get_room_messages(Caller, RoomID) ->
  618:     Path = <<"/rooms/", RoomID/binary, "/messages">>,
  619:     Creds = credentials(Caller),
  620:     rest_helper:gett(client, Path, Creds).
  621: 
  622: messages_can_be_paginated_in_room(Config) ->
  623:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  624:         RoomID = given_new_room_with_users({alice, Alice}, [{bob, Bob}]),
  625:         %% GenMsgs1 is older than GenMsgs2
  626:         %% One message is already in the archive
  627:         [GenMsgs1, GenMsgs2 | _] = rest_helper:fill_room_archive(RoomID, [Alice, Bob], 1),
  628:         mam_helper:maybe_wait_for_archive(Config),
  629:         Msgs10 = get_room_messages({alice, Alice}, RoomID, 10),
  630:         Msgs10Len = length(Msgs10),
  631:         true = Msgs10Len > 0 andalso Msgs10Len =< 10,
  632:         Msgs3 = get_room_messages({alice, Alice}, RoomID, 3),
  633:         [_, _, _] = Msgs3,
  634:         {_, Time} = calendar:now_to_datetime(os:timestamp()),
  635:         PriorTo = rest_helper:make_timestamp(-1, Time) - timer:seconds(10),
  636:         [OldestMsg1 | _] = get_room_messages({alice, Alice}, RoomID, 4, PriorTo),
  637:         assert_room_messages(OldestMsg1, hd(lists:keysort(1, GenMsgs1))),
  638:         [OldestMsg2 | _] = get_room_messages({alice, Alice}, RoomID, 2, PriorTo),
  639:         assert_room_messages(OldestMsg2, hd(lists:keysort(1, GenMsgs2)))
  640:     end).
  641: 
  642: room_message_query_errors(Config) ->
  643:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  644:         RoomID = given_new_room_with_users({alice, Alice}, []),
  645:         Creds = credentials({alice, Alice}),
  646:         Path = <<"/rooms/", RoomID/binary, "/messages">>,
  647:         {?BAD_REQUEST, <<"Invalid limit">>} =
  648:             rest_helper:gett(client, <<Path/binary, "?limit=x">>, Creds),
  649:         {?BAD_REQUEST, <<"Invalid value of 'before'">>} =
  650:             rest_helper:gett(client, <<Path/binary, "?before=x">>, Creds),
  651:         {?BAD_REQUEST, <<"Invalid query string">>} =
  652:             rest_helper:gett(client, <<Path/binary, "?kuropatwa">>, Creds),
  653:         {?NOT_FOUND, <<"Room not found">>} =
  654:             rest_helper:gett(client, <<"/rooms/badroom/messages">>, Creds)
  655:     end).
  656: 
  657: room_is_created_with_given_jid(Config) ->
  658:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  659:         RoomID = muc_helper:fresh_room_name(),
  660:         RoomJID = room_jid(RoomID, Config),
  661:         RoomID = given_new_room({alice, Alice}, RoomJID),
  662:         RoomInfo = get_room_info({alice, Alice}, RoomID),
  663:         assert_room_info(Alice, RoomInfo)
  664:     end).
  665: 
  666: room_is_not_created_with_jid_not_matching_hostname(Config) ->
  667:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  668:         RoomID = muc_helper:fresh_room_name(),
  669:         RoomJID = <<RoomID/binary, "@muclight.wrongdomain">>,
  670:         Creds = credentials({alice, Alice}),
  671:         {{Status, _}, _} = create_room_with_id_request(Creds,
  672:                                                        <<"some_name">>,
  673:                                                        <<"some subject">>,
  674:                                                        RoomJID),
  675:         ?assertEqual(<<"400">>, Status)
  676:     end).
  677: 
  678: room_can_be_fetched_by_jid(Config) ->
  679:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  680:         RoomID = muc_helper:fresh_room_name(),
  681:         RoomJID = room_jid(RoomID, Config),
  682:         RoomID = given_new_room({alice, Alice}, RoomJID),
  683:         RoomInfo = get_room_info({alice, Alice}, RoomJID),
  684:         assert_room_info(Alice, RoomInfo)
  685:     end).
  686: 
  687: messages_can_be_sent_and_fetched_by_room_jid(Config) ->
  688:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, _Bob) ->
  689:         RoomID = given_new_room({alice, Alice}),
  690:         RoomJID = room_jid(RoomID, Config),
  691:         given_message_sent_to_room(RoomJID, {alice, Alice}),
  692:         mam_helper:maybe_wait_for_archive(Config),
  693:         [_] = get_room_messages({alice, Alice}, RoomJID, 10)
  694:     end).
  695: 
  696: user_can_be_added_and_removed_by_room_jid(Config) ->
  697:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  698:         RoomID = given_new_room({alice, Alice}),
  699:         RoomJID = room_jid(RoomID, Config),
  700:         given_user_invited({alice, Alice}, RoomJID, Bob),
  701:         {{Status, _}, _} = remove_user_from_a_room({alice, Alice}, RoomJID, Bob),
  702:         ?assertEqual(<<"204">>, Status)
  703:     end).
  704: 
  705: msg_with_props_is_sent_and_delivered_over_xmpp(Config) ->
  706:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  707:         BobJID = user_jid(Bob),
  708:         MsgID = base16:encode(crypto:strong_rand_bytes(5)),
  709:         M1 = rest_helper:make_msg_stanza_with_props(BobJID,MsgID),
  710: 
  711:         escalus:send(Alice, M1),
  712: 
  713:         M2 = escalus:wait_for_stanza(Bob),
  714:         escalus:assert(is_message, M2)
  715:     end).
  716: 
  717: msg_with_props_can_be_parsed(Config) ->
  718:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  719:         AliceJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Alice)),
  720:         BobJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)),
  721:         MsgID = base16:encode(crypto:strong_rand_bytes(5)),
  722:         M1 = rest_helper:make_msg_stanza_with_props(BobJID,MsgID),
  723: 
  724:         escalus:send(Alice, M1),
  725: 
  726:         escalus:wait_for_stanza(Bob),
  727:         mam_helper:wait_for_archive_size(Bob, 1),
  728:         mam_helper:wait_for_archive_size(Alice, 1),
  729: 
  730:         AliceCreds = {AliceJID, user_password(alice)},
  731: 
  732:         % recent msgs with a limit
  733:         M2 = get_messages_with_props(AliceCreds, BobJID, 1),
  734: 
  735:         [{MsgWithProps} | _] = M2,
  736: 
  737:         Data = maps:from_list(MsgWithProps),
  738: 
  739:         #{<<"properties">> := {Props},
  740:           <<"id">> := ReceivedMsgID} = Data,
  741: 
  742:         %we are expecting two properties:"some_string" and "some_number" for this test message
  743:         %test message defined in rest_helper:make_msg_stanza_with_props
  744:         2 = length(Props),
  745:         ReceivedMsgID = MsgID
  746: 
  747:     end).
  748: 
  749: msg_with_malformed_props_is_sent_and_delivered_over_xmpp(Config) ->
  750:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  751:         BobJID = user_jid(Bob),
  752:         MsgID = base16:encode(crypto:strong_rand_bytes(5)),
  753: 
  754:         M1 = rest_helper:make_malformed_msg_stanza_with_props(BobJID, MsgID),
  755: 
  756:         escalus:send(Alice, M1),
  757: 
  758:         M2 = escalus:wait_for_stanza(Bob),
  759:         escalus:assert(is_message, M2)
  760:     end).
  761: 
  762: msg_with_malformed_props_can_be_parsed(Config) ->
  763:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  764:         AliceJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Alice)),
  765:         AliceCreds = {AliceJID, user_password(alice)},
  766:         BobJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)),
  767:         MsgID = base16:encode(crypto:strong_rand_bytes(5)),
  768: 
  769:         M1 = rest_helper:make_malformed_msg_stanza_with_props(BobJID,MsgID),
  770:         escalus:send(Alice, M1),
  771: 
  772:         escalus:wait_for_stanza(Bob),
  773:         mam_helper:wait_for_archive_size(Bob, 1),
  774:         mam_helper:wait_for_archive_size(Alice, 1),
  775: 
  776:         % recent msgs with a limit
  777:         M2 = get_messages_with_props(AliceCreds, BobJID, 1),
  778:         [_Msg] = rest_helper:decode_maplist(M2),
  779: 
  780:         MsgID = maps:get(id, _Msg)
  781: 
  782:     end).
  783: 
  784: msg_with_thread_is_sent_and_delivered_over_xmpp(Config) ->
  785:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun (Alice, Bob) ->
  786: 				BobJID = user_jid(Bob),
  787: 				MsgID = base16:encode(crypto:strong_rand_bytes(5)),
  788: 				ThreadID = base16:encode(crypto:strong_rand_bytes(5)),
  789: 				M1 = rest_helper:make_msg_stanza_with_thread(BobJID, MsgID, ThreadID),
  790: 				escalus:send(Alice, M1),
  791: 				M2 = escalus:wait_for_stanza(Bob),
  792: 				escalus:assert(is_message, M2)
  793: 		end).
  794: 
  795: msg_with_thread_can_be_parsed(Config) ->
  796:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun (Alice, Bob) ->
  797: 				AliceJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Alice)),
  798: 				BobJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)),
  799: 				MsgID = base16:encode(crypto:strong_rand_bytes(5)),
  800: 				ThreadID = base16:encode(crypto:strong_rand_bytes(5)),
  801: 				M1 = rest_helper:make_msg_stanza_with_thread(BobJID, MsgID, ThreadID),
  802: 				escalus:send(Alice, M1),
  803: 				escalus:wait_for_stanza(Bob),
  804: 				mam_helper:wait_for_archive_size(Bob, 1),
  805: 				mam_helper:wait_for_archive_size(Alice, 1),
  806: 				AliceCreds = {AliceJID, user_password(alice)},
  807: 				% recent msgs with a limit
  808: 				M2 = get_messages_with_props(AliceCreds, BobJID, 1),
  809: 				[{MsgWithProps} | _] = M2,
  810: 				Data = maps:from_list(MsgWithProps),
  811: 				#{<<"thread">> := ReceivedThreadID,
  812: 				  <<"id">> := ReceivedMsgID} = Data,
  813: 				%we are expecting thread and parent thread for this test message
  814: 				%test message defined in rest_helper:make_msg_stanza_with_thread
  815: 				ReceivedThreadID = ThreadID,
  816: 				ReceivedMsgID = MsgID
  817: 		end).
  818: 
  819: msg_with_thread_and_parent_is_sent_and_delivered_over_xmpp(Config) ->
  820:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun (Alice, Bob) ->
  821: 				BobJID = user_jid(Bob),
  822: 				MsgID = base16:encode(crypto:strong_rand_bytes(5)),
  823: 				ThreadID = base16:encode(crypto:strong_rand_bytes(5)),
  824: 				ThreadParentID = base16:encode(crypto:strong_rand_bytes(5)),
  825: 				M1 = rest_helper:make_msg_stanza_with_thread_and_parent(BobJID, MsgID, ThreadID, ThreadParentID),
  826: 				escalus:send(Alice, M1),
  827: 				M2 = escalus:wait_for_stanza(Bob),
  828: 				escalus:assert(is_message, M2)
  829: 		end).
  830: 
  831: msg_with_thread_and_parent_can_be_parsed(Config) ->
  832:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun (Alice, Bob) ->
  833: 				AliceJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Alice)),
  834: 				BobJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)),
  835: 				MsgID = base16:encode(crypto:strong_rand_bytes(5)),
  836: 				ThreadID = base16:encode(crypto:strong_rand_bytes(5)),
  837: 				ThreadParentID = base16:encode(crypto:strong_rand_bytes(5)),
  838: 				M1 = rest_helper:make_msg_stanza_with_thread_and_parent(BobJID, MsgID, ThreadID, ThreadParentID),
  839: 				escalus:send(Alice, M1),
  840: 				escalus:wait_for_stanza(Bob),
  841: 				mam_helper:wait_for_archive_size(Bob, 1),
  842: 				mam_helper:wait_for_archive_size(Alice, 1),
  843: 				AliceCreds = {AliceJID, user_password(alice)},
  844: 				% recent msgs with a limit
  845: 				M2 = get_messages_with_props(AliceCreds, BobJID, 1),
  846: 				[{MsgWithProps} | _] = M2,
  847: 				Data = maps:from_list(MsgWithProps),
  848: 				#{<<"thread">> := ReceivedThreadID,
  849: 				  <<"parent">> := ReceivedThreadParentID,
  850: 				  <<"id">> := ReceivedMsgID} = Data,
  851: 				%we are expecting thread and parent thread for this test message
  852: 				%test message defined in rest_helper:make_msg_stanza_with_thread
  853: 				ReceivedThreadID = ThreadID,
  854: 				ReceivedThreadParentID = ThreadParentID,
  855: 				ReceivedMsgID = MsgID
  856: 		end).
  857: 
  858: msg_without_thread_is_sent_and_delivered_over_xmpp(Config) ->
  859:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun (Alice, Bob) ->
  860: 				BobJID = user_jid(Bob),
  861: 				MsgID = base16:encode(crypto:strong_rand_bytes(5)),
  862: 				M1 = rest_helper:make_msg_stanza_without_thread(BobJID, MsgID),
  863: 				escalus:send(Alice, M1),
  864: 				M2 = escalus:wait_for_stanza(Bob),
  865: 				escalus:assert(is_message, M2)
  866: 		end).
  867: 
  868: msg_without_thread_can_be_parsed(Config) ->
  869:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun (Alice, Bob) ->
  870: 				AliceJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Alice)),
  871: 				AliceCreds = {AliceJID, user_password(alice)},
  872: 				BobJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)),
  873: 				MsgID = base16:encode(crypto:strong_rand_bytes(5)),
  874: 				M1 = rest_helper:make_msg_stanza_without_thread(BobJID, MsgID),
  875: 				escalus:send(Alice, M1),
  876: 				escalus:wait_for_stanza(Bob),
  877: 				mam_helper:wait_for_archive_size(Bob, 1),
  878: 				mam_helper:wait_for_archive_size(Alice, 1),
  879: 				% recent msgs with a limit
  880: 				M2 = get_messages_with_props(AliceCreds, BobJID, 1),
  881: 				[_Msg] = rest_helper:decode_maplist(M2),
  882: 				MsgID = maps:get(id, _Msg)
  883: 		end).
  884: 
  885: assert_room_messages(RecvMsg, {_ID, _GenFrom, GenMsg}) ->
  886:     escalus:assert(is_chat_message, [maps:get(body, RecvMsg)], GenMsg),
  887:     ok.
  888: 
  889: get_room_info(User, RoomID) ->
  890:     Creds = credentials(User),
  891:     {?OK, {Result}} = rest_helper:gett(client, <<"/rooms/", RoomID/binary>>,
  892:                                                          Creds),
  893:     Result.
  894: 
  895: given_new_room_with_users_and_msgs(Owner, Users) ->
  896:     RoomID = given_new_room_with_users(Owner, Users),
  897:     Msgs = [given_message_sent_to_room(RoomID, Sender) || Sender <- [Owner | Users]],
  898:     wait_for_room_msgs(Msgs, [Owner | Users]),
  899:     {RoomID, Msgs}.
  900: 
  901: wait_for_room_msgs([], _) ->
  902:     ok;
  903: wait_for_room_msgs([Msg | Rest], Users) ->
  904:     [wait_for_room_msg(Msg, User) || {_, User} <- Users],
  905:     wait_for_room_msgs(Rest, Users).
  906: 
  907: wait_for_room_msg(Msg, User) ->
  908:     Stanza = escalus:wait_for_stanza(User),
  909:     escalus:assert(is_groupchat_message, [maps:get(body, Msg)], Stanza).
  910: 
  911: given_message_sent_to_room(RoomID, Sender) ->
  912:     Body = #{body => <<"Hi all!">>},
  913:     HTTPResult = given_message_sent_to_room(RoomID, Sender, Body),
  914:     {?OK, {Result}} = HTTPResult,
  915:     MsgId = proplists:get_value(<<"id">>, Result),
  916:     true = is_binary(MsgId),
  917:     {UserJID, _} = credentials(Sender),
  918: 
  919:     Body#{id => MsgId, from => UserJID}.
  920: 
  921: given_message_sent_to_room(RoomID, Sender, Body) ->
  922:     Creds = credentials(Sender),
  923:     Path = <<"/rooms/", RoomID/binary, "/messages">>,
  924:     rest_helper:post(client, Path, Body, Creds).
  925: 
  926: given_new_room_with_users(Owner, Users) ->
  927:     RoomID = given_new_room(Owner),
  928:     [given_user_invited(Owner, RoomID, User) || {_, User} <- Users],
  929:     RoomID.
  930: 
  931: given_new_room(Owner) ->
  932:     Creds = credentials(Owner),
  933:     RoomName = <<"new_room_name">>,
  934:     create_room(Creds, RoomName, <<"This room subject">>).
  935: 
  936: given_new_room(Owner, RoomID) ->
  937:     Creds = credentials(Owner),
  938:     RoomName = <<"new_room_name">>,
  939:     create_room_with_id(Creds, RoomName, <<"This room subject">>, RoomID).
  940: 
  941: given_new_room(Owner, RoomID, RoomName) ->
  942:     Creds = credentials(Owner),
  943:     create_room_with_id(Creds, RoomName, <<"This room subject">>, RoomID).
  944: 
  945: given_user_invited({_, Inviter} = Owner, RoomID, Invitee) ->
  946:     JID = user_jid(Invitee),
  947:     {?NOCONTENT, _} = invite_to_room(Owner, RoomID, JID),
  948:     maybe_wait_for_aff_stanza(Invitee, Invitee),
  949:     maybe_wait_for_aff_stanza(Inviter, Invitee).
  950: 
  951: when_config_change(User, RoomID, NewName, NewSubject) ->
  952:     Creds = credentials(User),
  953:     Config = #{name => NewName, subject => NewSubject},
  954:     Path = <<"/rooms/", RoomID/binary, "/config">>,
  955:     putt(client, Path, Config, Creds).
  956: 
  957: maybe_wait_for_aff_stanza(#client{} = Client, Invitee) ->
  958:     Stanza = escalus:wait_for_stanza(Client),
  959:     assert_aff_change_stanza(Stanza, Invitee, <<"member">>);
  960: maybe_wait_for_aff_stanza(_, _) ->
  961:     ok.
  962: 
  963: invite_to_room(Inviter, RoomID, Invitee) ->
  964:     Body = #{user => Invitee},
  965:     Creds = credentials(Inviter),
  966:     rest_helper:post(client, <<"/rooms/", RoomID/binary, "/users">>, Body, Creds).
  967: 
  968: remove_user_from_a_room(Inviter, RoomID, Invitee) ->
  969:     JID = escalus_utils:jid_to_lower(escalus_client:short_jid(Invitee)),
  970:     Creds = credentials(Inviter),
  971:     Path = <<"/rooms/", RoomID/binary, "/users/", JID/binary>>,
  972:     rest_helper:delete(client, Path, Creds).
  973: 
  974: credentials({User, ClientOrSpec}) ->
  975:     {user_jid(ClientOrSpec), user_password(User)}.
  976: 
  977: user_jid(#client{} = UserClient) ->
  978:     escalus_utils:jid_to_lower(escalus_client:short_jid(UserClient));
  979: user_jid(Spec) ->
  980:     U = proplists:get_value(username, Spec),
  981:     S = proplists:get_value(server, Spec),
  982:     escalus_utils:jid_to_lower(<<U/binary, $@, S/binary>>).
  983: 
  984: user_password(User) ->
  985:     [{User, Props}] = escalus:get_users([User]),
  986:     proplists:get_value(password, Props).
  987: 
  988: send_message(User, From, To) ->
  989:     AliceJID = user_jid(From),
  990:     BobJID = user_jid(To),
  991:     M = #{to => BobJID, body => <<"hello, ", BobJID/binary, " it's me">>},
  992:     Cred = credentials({User, From}),
  993:     {?OK, {Result}} = post(client, <<"/messages">>, M, Cred),
  994:     ID = proplists:get_value(<<"id">>, Result),
  995:     M#{id => ID, from => AliceJID}.
  996: 
  997: get_messages(MeCreds, Other, Count) ->
  998:     GetPath = lists:flatten(["/messages/",
  999:                              binary_to_list(Other),
 1000:                              "?limit=", integer_to_list(Count)]),
 1001:     get_messages(GetPath, MeCreds).
 1002: 
 1003: get_messages(Path, Creds) ->
 1004:     {?OK, Msgs} = rest_helper:gett(client, Path, Creds),
 1005:     rest_helper:decode_maplist(Msgs).
 1006: 
 1007: get_messages(MeCreds, Other, Before, Count) ->
 1008:     GetPath = lists:flatten(["/messages/",
 1009:                              binary_to_list(Other),
 1010:                              "?before=", integer_to_list(Before),
 1011:                              "&limit=", integer_to_list(Count)]),
 1012:     get_messages(GetPath, MeCreds).
 1013: 
 1014: get_messages_with_props(MeCreds, Other, Count) ->
 1015:     GetPath = lists:flatten(["/messages/",
 1016:                              binary_to_list(Other),
 1017:                              "?limit=", integer_to_list(Count)]),
 1018:     get_messages_with_props(GetPath, MeCreds).
 1019: 
 1020: get_messages_with_props(Path, Creds) ->
 1021:     {?OK, Msgs} = rest_helper:gett(client, Path, Creds),
 1022:     Msgs.
 1023: 
 1024: get_messages_with_props(MeCreds, Other, Before, Count) ->
 1025:     GetPath = lists:flatten(["/messages/",
 1026:                              binary_to_list(Other),
 1027:                              "?before=", integer_to_list(Before),
 1028:                              "&limit=", integer_to_list(Count)]),
 1029:     get_messages_with_props(GetPath, MeCreds).
 1030: 
 1031: get_room_messages(Client, RoomID, Count) ->
 1032:     get_room_messages(Client, RoomID, Count, undefined).
 1033: 
 1034: get_room_messages(Client, RoomID, Count, Before) ->
 1035:     Creds = credentials(Client),
 1036:     BasePathList = ["/rooms/", RoomID, "/messages?limit=", integer_to_binary(Count)],
 1037:     PathList = BasePathList ++ [["&before=", integer_to_binary(Before)] || Before /= undefined],
 1038:     Path = erlang:iolist_to_binary(PathList),
 1039:     get_messages(Path, Creds).
 1040: 
 1041: create_room({_AliceJID, _} = Creds, RoomName, Subject) ->
 1042:     Room = #{name => RoomName,
 1043:              subject => Subject},
 1044:     {?CREATED, {Result}} = rest_helper:post(client, <<"/rooms">>, Room, Creds),
 1045:     proplists:get_value(<<"id">>, Result).
 1046: 
 1047: create_room_with_id({_AliceJID, _} = Creds, RoomName, Subject, RoomID) ->
 1048:     Res = create_room_with_id_request(Creds, RoomName, Subject, RoomID),
 1049:     case Res of
 1050:         {?CREATED, {Result}} ->
 1051:             proplists:get_value(<<"id">>, Result);
 1052:         _ ->
 1053:             ct:fail(#{issue => create_room_with_id_failed,
 1054:                       result => Res,
 1055:                       creds => Creds,
 1056:                       room_name => RoomName,
 1057:                       subject => Subject,
 1058:                       room_id => RoomID})
 1059:     end.
 1060: 
 1061: create_room_with_id_request(Creds, RoomName, Subject, RoomID) ->
 1062:     Room = #{name => RoomName,
 1063:              subject => Subject},
 1064:     Path = <<"/rooms/", RoomID/binary>>,
 1065:     putt(client, Path, Room, Creds).
 1066: 
 1067: get_my_rooms(User) ->
 1068:     Creds = credentials(User),
 1069:     {?OK, Rooms} = rest_helper:gett(client, <<"/rooms">>, Creds),
 1070:     Rooms.
 1071: 
 1072: assert_messages([], []) ->
 1073:     ok;
 1074: assert_messages([SentMsg | SentRest], [RecvMsg | RecvRest]) ->
 1075:     FromJID = maps:get(from, SentMsg),
 1076:     FromJID = maps:get(from, RecvMsg),
 1077:     MsgId = maps:get(id, SentMsg),
 1078:     MsgId = maps:get(id, RecvMsg), %checks if there is an ID
 1079:     _ = maps:get(timestamp, RecvMsg), %checks if there ia timestamp
 1080:     MsgBody = maps:get(body, SentMsg),
 1081:     MsgBody = maps:get(body, RecvMsg),
 1082:     assert_messages(SentRest, RecvRest);
 1083: assert_messages(_Sent, _Recv) ->
 1084:     ct:fail("Send and Recv messages are not equal").
 1085: 
 1086: send_messages(Config, Alice, Bob, Kate) ->
 1087:     M1 = send_message(bob, Bob, Alice),
 1088:     M2 = send_message(alice, Alice, Bob),
 1089:     M3 = send_message(kate, Kate, Alice),
 1090:     mam_helper:maybe_wait_for_archive(Config),
 1091:     [M1, M2, M3].
 1092: 
 1093: assert_aff_change_stanza(Stanza, Target, Change) ->
 1094:     TargetJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Target)),
 1095:     ID = exml_query:attr(Stanza, <<"id">>),
 1096:     true = is_binary(ID) andalso ID /= <<>>,
 1097:     Users = exml_query:paths(Stanza, [{element, <<"x">>}, {element, <<"user">>}]),
 1098:     [User] = [User || User <- Users, TargetJID == exml_query:cdata(User)],
 1099:     Change = exml_query:attr(User, <<"affiliation">>),
 1100:     TargetJID = exml_query:cdata(User).
 1101: 
 1102: assert_room_info(Owner, RoomInfo) ->
 1103:     true = is_property_present(<<"subject">>, RoomInfo),
 1104:     true = is_property_present(<<"name">>, RoomInfo),
 1105:     true = is_property_present(<<"participants">>, RoomInfo),
 1106:     true = is_participant(Owner, <<"owner">>, RoomInfo).
 1107: 
 1108: is_property_present(Name, Proplist) ->
 1109:     Val = proplists:get_value(Name, Proplist),
 1110:     Val /= undefined.
 1111: 
 1112: assert_property_value(Name, Value, Proplist) ->
 1113:     Val = proplists:get_value(Name, Proplist),
 1114:     ?assertEqual(Value, Val).
 1115: 
 1116: is_participant(User, Role, RoomInfo) ->
 1117:     Participants = proplists:get_value(<<"participants">>, RoomInfo),
 1118:     JID = user_jid(User),
 1119:     Fun = fun({Props}) ->
 1120:                   UserJID = proplists:get_value(<<"user">>, Props),
 1121:                   UserRole = proplists:get_value(<<"role">>, Props),
 1122:                   UserJID == JID andalso UserRole == Role
 1123:           end,
 1124:     lists:any(Fun, Participants).
 1125: 
 1126: connect_to_sse(User) ->
 1127:     Port = ct:get_config({hosts, mim, http_api_client_endpoint_port}),
 1128:     sse_helper:connect_to_sse(Port, "/api/sse", credentials(User), #{transport => tls,
 1129:         tls_opts => [{verify, verify_none}]}).
 1130: 
 1131: assert_json_message(Sent, Received) ->
 1132:     #{<<"body">> := Body,
 1133:       <<"to">> := To,
 1134:       <<"from">> := From,
 1135:       <<"id">> := Id} = Received,
 1136: 
 1137:     Body = maps:get(body, Sent),
 1138:     To = maps:get(to, Sent),
 1139:     From = maps:get(from, Sent),
 1140:     Id = maps:get(id, Sent).
 1141: 
 1142: assert_json_room_sse_message(Expected, Received) ->
 1143:     #{<<"from">> := From,
 1144:       <<"room">> := Room,
 1145:       <<"id">> := _Id,
 1146:       <<"type">> := Type} = Received,
 1147: 
 1148:     Room = maps:get(room, Expected),
 1149:     Type = maps:get(type, Expected),
 1150:     From = maps:get(from, Expected),
 1151:     case Type of
 1152:         <<"message">> ->
 1153:             Body = maps:get(<<"body">>, Received),
 1154:             Body = maps:get(body, Expected);
 1155:         _ ->
 1156:             User = maps:get(<<"user">>, Received),
 1157:             User = maps:get(user, Expected)
 1158:     end.
 1159: 
 1160: 
 1161: add_contact_and_invite(Config) ->
 1162:     escalus:fresh_story(
 1163:         Config, [{alice, 1}, {bob, 1}],
 1164:         fun(Alice, Bob) ->
 1165:             AliceJID = escalus_utils:jid_to_lower(
 1166:                             escalus_client:short_jid(Alice)),
 1167:             BCred = credentials({bob, Bob}),
 1168:             % bob has empty roster
 1169:             {?OK, R} = gett(client, "/contacts", BCred),
 1170:             Res = decode_maplist(R),
 1171:             [] = Res,
 1172:             % adds Alice
 1173:             add_contact_check_roster_push(Alice, {bob, Bob}),
 1174:             % and she is in his roster, with empty status
 1175:             {?OK, R2} = gett(client, "/contacts", BCred),
 1176:             Result = decode_maplist(R2),
 1177:             [Res2] = Result,
 1178:             #{jid := AliceJID, subscription := <<"none">>,
 1179:               ask := <<"none">>} = Res2,
 1180:             % he invites her
 1181:             PutPath = lists:flatten(["/contacts/", binary_to_list(AliceJID)]),
 1182:             {?NOCONTENT, _} = putt(client, PutPath,
 1183:                                    #{action => <<"invite">>},
 1184:                                    BCred),
 1185:             % another roster push
 1186:             Push2 = escalus:wait_for_stanza(Bob),
 1187:             escalus:assert(is_roster_set, Push2),
 1188:             ct:log("Push2: ~p", [Push2]),
 1189:             % she receives  a subscription request
 1190:             Sub = escalus:wait_for_stanza(Alice),
 1191:             escalus:assert(is_presence_with_type, [<<"subscribe">>], Sub),
 1192:             % in his roster she has a changed 'ask' status
 1193:             {?OK, R3} = gett(client, "/contacts", BCred),
 1194:             Result3 = decode_maplist(R3),
 1195:             [Res3] = Result3,
 1196:             #{jid := AliceJID, subscription := <<"none">>,
 1197:               ask := <<"out">>} = Res3,
 1198:             % adds him to her contacts
 1199:             escalus:send(Alice, escalus_stanza:roster_add_contact(Bob,
 1200:                          [], <<"Bob">>)),
 1201:             PushReqB = escalus:wait_for_stanza(Alice),
 1202:             escalus:assert(is_roster_set, PushReqB),
 1203:             escalus:send(Alice, escalus_stanza:iq_result(PushReqB)),
 1204:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)),
 1205:             %% Alice sends subscribed presence
 1206:             escalus:send(Alice,
 1207:                          escalus_stanza:presence_direct(
 1208:                              escalus_client:short_jid(Bob),
 1209:                              <<"subscribed">>)),
 1210:             %% Wait for push before trying to query endpoint
 1211:             %% If we just call endpoint,
 1212:             %% the "subscribed" stanza can not yet be processed.
 1213:             Push3 = escalus:wait_for_stanza(Bob),
 1214:             ct:log("Push3 ~p", [Push3]),
 1215:             escalus:assert(is_roster_set, Push3),
 1216: 
 1217:             % now check Bob's roster
 1218:             {?OK, R4} = gett(client, "/contacts", BCred),
 1219:             Result4 = decode_maplist(R4),
 1220:             [Res4] = Result4,
 1221:             #{jid := AliceJID, subscription := <<"to">>,
 1222:                 ask := <<"none">>} = Res4,
 1223:             ok
 1224:         end
 1225:     ),
 1226:     ok.
 1227: 
 1228: add_contact_and_be_invited(Config) ->
 1229:     escalus:fresh_story(
 1230:         Config, [{alice, 1}, {bob, 1}],
 1231:         fun(Alice, Bob) ->
 1232:             AliceJID = escalus_utils:jid_to_lower(
 1233:                 escalus_client:short_jid(Alice)),
 1234:             BCred = credentials({bob, Bob}),
 1235:             % bob has empty roster
 1236:             {?OK, R} = gett(client, "/contacts", BCred),
 1237:             Res = decode_maplist(R),
 1238:             [] = Res,
 1239:             % adds Alice
 1240:             add_contact_check_roster_push(Alice, {bob, Bob}),
 1241:             % and she is in his roster, with empty status
 1242:             {?OK, R2} = gett(client, "/contacts", BCred),
 1243:             Result = decode_maplist(R2),
 1244:             [Res2] = Result,
 1245:             #{jid := AliceJID, subscription := <<"none">>,
 1246:               ask := <<"none">>} = Res2,
 1247:             %% she adds him and invites
 1248:             escalus:send(Alice, escalus_stanza:roster_add_contact(Bob,
 1249:                          [],
 1250:                          <<"Bobek">>)),
 1251:             escalus:assert_many([is_roster_set, is_iq_result],
 1252:                                 escalus:wait_for_stanzas(Alice, 2)),
 1253:             escalus:send(Alice,
 1254:                          escalus_stanza:presence_direct(
 1255:                              escalus_client:short_jid(Bob),
 1256:                              <<"subscribe">>)),
 1257:             escalus:assert(is_roster_set, escalus:wait_for_stanza(Alice)),
 1258:             escalus:assert(is_presence_with_type, [<<"subscribe">>],
 1259:                            escalus:wait_for_stanza(Bob)),
 1260:             % now check Bob's roster, and it is the same...
 1261:             {?OK, R4} = gett(client, "/contacts", BCred),
 1262:             [Res4] = decode_maplist(R4),
 1263:             #{jid := AliceJID, subscription := <<"none">>,
 1264:                 ask := <<"in">>} = Res4,
 1265:             % because although it is stated in RFC3921, 8.2.6 that {none, in}
 1266:             % should be hidden from user, we changed it in REST API
 1267:             % he accepts
 1268:             PutPath = lists:flatten(["/contacts/", binary_to_list(AliceJID)]),
 1269:             {?NOCONTENT, _} = putt(client, PutPath,
 1270:                                    #{action => <<"accept">>},
 1271:                                    BCred),
 1272:             escalus:assert(is_roster_set, escalus:wait_for_stanza(Bob)),
 1273:             IsSub = fun(S) ->
 1274:                         escalus_pred:is_presence_with_type(<<"subscribed">>, S)
 1275:                     end,
 1276:             escalus:assert_many([is_roster_set, IsSub,
 1277:                                  is_presence],
 1278:                                 escalus:wait_for_stanzas(Alice, 3)),
 1279:             ok
 1280:         end
 1281:     ),
 1282:     ok.
 1283: 
 1284: is_subscription_remove(User) ->
 1285:     IsSubscriptionRemove = fun(El) ->
 1286:                 Sub = exml_query:paths(El, [{element, <<"query">>},
 1287:                                             {element, <<"item">>},
 1288:                                             {attr, <<"subscription">>}]),
 1289:                 Sub == [<<"remove">>]
 1290:                 end,
 1291:     escalus:assert(IsSubscriptionRemove, escalus:wait_for_stanza(User)).
 1292: 
 1293: 
 1294: 
 1295: add_and_remove(Config) ->
 1296:     escalus:fresh_story(
 1297:         Config, [{alice, 1}, {bob, 1}],
 1298:         fun(Alice, Bob) ->
 1299:             AliceJID = escalus_utils:jid_to_lower(
 1300:                 escalus_client:short_jid(Alice)),
 1301:             BCred = credentials({bob, Bob}),
 1302:             % adds Alice
 1303:             add_contact_check_roster_push(Alice, {bob, Bob}),
 1304:             % Check if Contact is in Bob's roster
 1305:             {?OK, R2} = gett(client, "/contacts", BCred),
 1306:             Result = decode_maplist(R2),
 1307:             [Res2] = Result,
 1308:             #{jid := AliceJID, subscription := <<"none">>,
 1309:               ask := <<"none">>} = Res2,
 1310:             % delete user
 1311:             DelPath = lists:flatten(["/contacts/", binary_to_list(AliceJID)]),
 1312:             {?NOCONTENT, _} = delete(client, DelPath, BCred),
 1313:             % Bob's roster is empty again
 1314:             {?OK, R3} = gett(client, "/contacts", BCred),
 1315:             [] = decode_maplist(R3),
 1316:             is_subscription_remove(Bob),
 1317:             ok
 1318:         end
 1319:     ),
 1320:     ok.
 1321: 
 1322: 
 1323: add_and_remove_some_contacts_properly(Config) ->
 1324:     escalus:fresh_story(
 1325:         Config, [{alice, 1}, {bob, 1}, {kate, 1}, {mike, 1}],
 1326:         fun(Alice, Bob, Kate, Mike) ->
 1327:             BCred = credentials({bob, Bob}),
 1328:             % adds all the other users
 1329:             lists:foreach(fun(AddContact) ->
 1330:                                   add_contact_check_roster_push(AddContact, {bob, Bob}) end,
 1331:                          [Alice, Kate, Mike]),
 1332:             AliceJID = escalus_utils:jid_to_lower(
 1333:                 escalus_client:short_jid(Alice)),
 1334:             KateJID = escalus_utils:jid_to_lower(
 1335:                 escalus_client:short_jid(Kate)),
 1336:             MikeJID = escalus_utils:jid_to_lower(
 1337:                 escalus_client:short_jid(Mike)),
 1338:             _AliceContact = create_contact(AliceJID),
 1339:             _KateContact = create_contact(KateJID),
 1340:             MikeContact = create_contact(MikeJID),
 1341:             % delete Alice and Kate
 1342:             Body = jiffy:encode(#{<<"to_delete">> => [AliceJID, KateJID]}),
 1343:             {?OK, {[{<<"not_deleted">>,[]}]}} = delete(client, "/contacts", BCred, Body),
 1344:             % Bob's roster consists now of only Mike
 1345:             {?OK, R4} = gett(client, "/contacts", BCred),
 1346:             [MikeContact] = decode_maplist(R4),
 1347:             is_subscription_remove(Bob),
 1348:             ok
 1349:         end
 1350:     ),
 1351:     ok.
 1352: 
 1353: 
 1354: add_and_remove_some_contacts_with_nonexisting(Config) ->
 1355:     escalus:fresh_story(
 1356:         Config, [{alice, 1}, {bob, 1}, {kate, 1}, {mike, 1}],
 1357:         fun(Alice, Bob, Kate, Mike) ->
 1358:             BCred = credentials({bob, Bob}),
 1359:             % adds all the other users
 1360:             lists:foreach(fun(AddContact) ->
 1361:                                   add_contact_check_roster_push(AddContact, {bob, Bob}) end,
 1362:                          [Alice, Kate]),
 1363:             AliceJID = escalus_utils:jid_to_lower(
 1364:                 escalus_client:short_jid(Alice)),
 1365:             KateJID = escalus_utils:jid_to_lower(
 1366:                 escalus_client:short_jid(Kate)),
 1367:             MikeJID = escalus_utils:jid_to_lower(
 1368:                 escalus_client:short_jid(Mike)),
 1369:             _AliceContact = create_contact(AliceJID),
 1370:             _KateContact = create_contact(KateJID),
 1371:             _MikeContact = create_contact(MikeJID),
 1372:             % delete Alice, Kate and Mike (who is absent)
 1373:             Body = jiffy:encode(#{<<"to_delete">> => [AliceJID, KateJID, MikeJID]}),
 1374:             {?OK, {[{<<"not_deleted">>,[MikeJID]}]}} = delete(client, "/contacts", BCred, Body),
 1375:             % Bob's roster is empty now
 1376:             {?OK, R4} = gett(client, "/contacts", BCred),
 1377:             [] = decode_maplist(R4),
 1378:             is_subscription_remove(Bob),
 1379:             ok
 1380:         end
 1381:     ),
 1382:     ok.
 1383: 
 1384: create_contact(JID) ->
 1385:     #{jid => JID, subscription => <<"none">>,
 1386:                              ask => <<"none">>}.
 1387: 
 1388: add_contact_check_roster_push(Contact, {_, RosterOwnerSpec} = RosterOwner) ->
 1389:     ContactJID = escalus_utils:jid_to_lower(
 1390:                 escalus_client:short_jid(Contact)),
 1391:     RosterOwnerCreds = credentials(RosterOwner),
 1392:     {?NOCONTENT, _} = post(client, <<"/contacts">>, #{jid => ContactJID},
 1393:                             RosterOwnerCreds),
 1394:     Push = escalus:wait_for_stanza(RosterOwnerSpec),
 1395:     escalus:assert(is_roster_set, Push),
 1396:     ok.
 1397: 
 1398: roster_errors(Config) ->
 1399:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun roster_errors_story/2).
 1400: 
 1401: roster_errors_story(Alice, Bob) ->
 1402:     AliceJID = user_jid(Alice),
 1403:     BCred = credentials({bob, Bob}),
 1404:     {?BAD_REQUEST, <<"Missing JID">>} =
 1405:         post(client, <<"/contacts">>, #{}, BCred),
 1406:     {?BAD_REQUEST, <<"Invalid JID: @invalid">>} =
 1407:         post(client, <<"/contacts">>, #{jid => <<"@invalid">>}, BCred),
 1408:     {?BAD_REQUEST, <<"Invalid action">>} =
 1409:         putt(client, <<"/contacts/", AliceJID/binary>>, #{action => <<"nosuchaction">>}, BCred),
 1410:     {?BAD_REQUEST, <<"Missing action">>} =
 1411:         putt(client, <<"/contacts/", AliceJID/binary>>, #{}, BCred),
 1412:     {?NOT_FOUND, _} =
 1413:         post(client, <<"/contacts">>, #{jid => <<"zorro@localhost">>}, BCred),
 1414:     {?NOT_FOUND, _} =
 1415:         putt(client, <<"/contacts/zorro@localhost">>, #{action => <<"invite">>}, BCred),
 1416:     {?NOT_FOUND, _} =
 1417:         gett(client, <<"/contacts/zorro@localhost">>, BCred),
 1418:     {?NOT_FOUND, _} =
 1419:         delete(client, <<"/contacts/zorro@localhost">>, BCred).
 1420: 
 1421: -spec room_jid(RoomID :: binary(), Config :: list()) -> RoomJID :: binary().
 1422: room_jid(RoomID, Config) ->
 1423:     MUCLightHost = config_to_muc_host(Config),
 1424:     <<RoomID/binary, "@", MUCLightHost/binary>>.
 1425: 
 1426: default_http_server_name_is_returned_if_not_changed(_Config) ->
 1427:     %% GIVEN MIM1 uses default name
 1428:     verify_server_name_in_header(distributed_helper:mim(), <<"Cowboy">>).
 1429: 
 1430: non_default_http_server_name_is_returned_if_configured(_Config) ->
 1431:     %% GIVEN MIM2 uses name "Classified"
 1432:     verify_server_name_in_header(distributed_helper:mim2(), <<"Classified">>).
 1433: 
 1434: verify_server_name_in_header(Server, ExpectedName) ->
 1435:     % WHEN unathenticated user makes a request to nonexistent path
 1436:     ReqParams = #{
 1437:       role => client,
 1438:       method => <<"GET">>,
 1439:       path => "/contacts/zorro@localhost",
 1440:       body => <<>>,
 1441:       return_headers => true,
 1442:       server => Server
 1443:      },
 1444:     {?UNAUTHORIZED, Headers2, _} = rest_helper:make_request(ReqParams),
 1445:     % THEN expected server name is returned
 1446:     ExpectedName = proplists:get_value(<<"server">>, Headers2).
 1447: 
 1448: config_to_muc_host(Config) ->
 1449:     ?config(muc_light_host, Config).