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