1: -module(graphql_muc_SUITE).
    2: 
    3: -compile([export_all, nowarn_export_all]).
    4: 
    5: -import(distributed_helper, [mim/0, require_rpc_nodes/1, rpc/4]).
    6: -import(graphql_helper, [execute_user/3, execute_auth/2, get_ok_value/2, get_err_msg/1,
    7:                          user_to_bin/1, user_to_full_bin/1]).
    8: 
    9: -include_lib("common_test/include/ct.hrl").
   10: -include_lib("escalus/include/escalus_xmlns.hrl").
   11: -include_lib("eunit/include/eunit.hrl").
   12: -include_lib("exml/include/exml.hrl").
   13: 
   14: suite() ->
   15:     require_rpc_nodes([mim]) ++ escalus:suite().
   16: 
   17: all() ->
   18:     [{group, user_muc},
   19:      {group, admin_muc}].
   20: 
   21: groups() ->
   22:     [{user_muc, [parallel], user_muc_handler()},
   23:      {admin_muc, [parallel], admin_muc_handler()}].
   24: 
   25: user_muc_handler() ->
   26:     [user_create_and_delete_room,
   27:      user_try_delete_nonexistent_room,
   28:      user_try_delete_room_by_not_owner,
   29:      user_try_create_instant_room_with_nonexistent_domain,
   30:      user_list_rooms,
   31:      user_list_room_users,
   32:      user_list_room_users_without_anonymous_mode,
   33:      user_try_list_room_users_without_permission,
   34:      user_try_list_nonexistent_room_users,
   35:      user_change_room_config,
   36:      user_try_change_nonexistent_room_config,
   37:      user_get_room_config,
   38:      user_try_get_nonexistent_room_config,
   39:      user_invite_user,
   40:      user_kick_user,
   41:      user_send_message_to_room,
   42:      user_send_message_to_room_with_specified_res,
   43:      user_without_session_send_message_to_room,
   44:      user_get_room_messages,
   45:      user_try_get_nonexistent_room_messages,
   46:      user_try_get_room_messages_without_permission].
   47: 
   48: admin_muc_handler() ->
   49:     [admin_create_and_delete_room,
   50:      admin_try_create_instant_room_with_nonexistent_domain,
   51:      admin_try_create_instant_room_with_nonexistent_user,
   52:      admin_try_delete_nonexistent_room,
   53:      admin_try_delete_room_with_nonexistent_domain,
   54:      admin_list_rooms,
   55:      admin_list_room_users,
   56:      admin_try_list_users_from_nonexistent_room,
   57:      admin_change_room_config,
   58:      admin_try_change_nonexistent_room_config,
   59:      admin_get_room_config,
   60:      admin_try_get_nonexistent_room_config,
   61:      admin_invite_user,
   62:      admin_try_invite_user_to_nonexistent_room,
   63:      admin_kick_user,
   64:      admin_send_message_to_room,
   65:      admin_get_room_messages,
   66:      admin_try_get_nonexistent_room_messages].
   67: 
   68: init_per_suite(Config) ->
   69:     HostType = domain_helper:host_type(),
   70:     Config2 = escalus:init_per_suite(Config),
   71:     Config3 = dynamic_modules:save_modules(HostType, Config2),
   72:     Config4 = rest_helper:maybe_enable_mam(mam_helper:backend(), HostType, Config3),
   73:     dynamic_modules:restart(HostType, mod_disco,
   74:                             config_parser_helper:default_mod_config(mod_disco)),
   75:     muc_helper:load_muc(),
   76:     mongoose_helper:ensure_muc_clean(),
   77:     Config4.
   78: 
   79: end_per_suite(Config) ->
   80:     escalus_fresh:clean(),
   81:     mongoose_helper:ensure_muc_clean(),
   82:     muc_helper:unload_muc(),
   83:     dynamic_modules:restore_modules(Config),
   84:     escalus:end_per_suite(Config).
   85: 
   86: init_per_group(admin_muc, Config) ->
   87:     graphql_helper:init_admin_handler(Config);
   88: init_per_group(user_muc, Config) ->
   89:     [{schema_endpoint, user} | Config].
   90: 
   91: end_per_group(_GN, Config) ->
   92:     Config.
   93: 
   94: init_per_testcase(TC, Config) ->
   95:     rest_helper:maybe_skip_mam_test_cases(TC, [user_get_room_messages,
   96:                                                admin_get_room_messages], Config).
   97: 
   98: end_per_testcase(TC, Config) ->
   99:     escalus:end_per_testcase(TC, Config).
  100: 
  101: -define(CREATE_INSTANT_ROOM_PATH, [data, muc, createInstantRoom]).
  102: -define(LIST_ROOMS_PATH, [data, muc, listRooms]).
  103: -define(INVITE_USER_PATH, [data, muc, inviteUser]).
  104: -define(KICK_USER_PATH, [data, muc, kickUser]).
  105: -define(DELETE_ROOM_PATH, [data, muc, deleteRoom]).
  106: -define(SEND_MESSAGE_PATH, [data, muc, sendMessageToRoom]).
  107: -define(GET_MESSAGES_PATH, [data, muc, getRoomMessages]).
  108: -define(LIST_ROOM_USERS_PATH, [data, muc, listRoomUsers]).
  109: -define(CHANGE_ROOM_CONFIG_PATH, [data, muc, changeRoomConfiguration]).
  110: -define(GET_ROOM_CONFIG_PATH, [data, muc, getRoomConfig]).
  111: 
  112: -define(NONEXISTENT_ROOM, <<"room@room">>).
  113: -define(NONEXISTENT_ROOM2, <<"room@", (muc_helper:muc_host())/binary>>).
  114: 
  115: admin_list_rooms(Config) ->
  116:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], fun admin_list_rooms_story/3).
  117: 
  118: admin_list_rooms_story(Config, Alice, Bob) ->
  119:     AliceJID = jid:from_binary(escalus_client:short_jid(Alice)),
  120:     BobJID = jid:from_binary(escalus_client:short_jid(Bob)),
  121:     AliceRoom = rand_name(),
  122:     BobRoom = rand_name(),
  123:     muc_helper:create_instant_room(AliceRoom, AliceJID, <<"Ali">>, []),
  124:     muc_helper:create_instant_room(BobRoom, BobJID, <<"Bob">>, [{public_list, false}]),
  125:     Res = execute_auth(admin_list_rooms_body(muc_helper:muc_host(), Alice, null, null), Config),
  126:     #{<<"rooms">> := Rooms } = get_ok_value(?LIST_ROOMS_PATH, Res),
  127:     ?assert(contain_room(AliceRoom, Rooms)),
  128:     ?assert(contain_room(BobRoom, Rooms)).
  129: 
  130: admin_create_and_delete_room(Config) ->
  131:     escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_create_and_delete_room_story/2).
  132: 
  133: admin_create_and_delete_room_story(Config, Alice) ->
  134:     Name = <<"first-alice-room">>,
  135:     MUCServer = muc_helper:muc_host(),
  136:     RoomJID = jid:make_bare(Name, MUCServer),
  137:     % Create instant room
  138:     Res = execute_auth(admin_create_instant_room_body(MUCServer, Name, Alice, <<"Ali">>), Config),
  139:     ?assertMatch(#{<<"title">> := Name, <<"private">> := false, <<"usersNumber">> := 0},
  140:                  get_ok_value(?CREATE_INSTANT_ROOM_PATH, Res)),
  141:     Res2 = execute_auth(admin_list_rooms_body(MUCServer, Alice, null, null), Config),
  142:     ?assert(contain_room(Name, get_ok_value(?LIST_ROOMS_PATH, Res2))),
  143:     % Delete room
  144:     Res3 = execute_auth(delete_room_body(RoomJID, null), Config),
  145:     ?assertNotEqual(nomatch, binary:match(get_ok_value(?DELETE_ROOM_PATH, Res3),
  146:                                           <<"successfully">>)),
  147:     Res4 = execute_auth(admin_list_rooms_body(MUCServer, Alice, null, null), Config),
  148:     ?assertNot(contain_room(Name, get_ok_value(?LIST_ROOMS_PATH, Res4))).
  149: 
  150: admin_try_create_instant_room_with_nonexistent_domain(Config) ->
  151:     escalus:fresh_story_with_config(Config, [{alice, 1}],
  152:                                     fun admin_try_create_instant_room_with_nonexistent_domain_story/2).
  153: 
  154: admin_try_create_instant_room_with_nonexistent_domain_story(Config, Alice) ->
  155:     Res = execute_auth(admin_create_instant_room_body(<<"unknown">>, rand_name(), Alice, <<"Ali">>),
  156:                        Config),
  157:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)).
  158: 
  159: admin_try_create_instant_room_with_nonexistent_user(Config) ->
  160:     Name = rand_name(),
  161:     MUCServer = muc_helper:muc_host(),
  162:     JID = <<(rand_name())/binary, "@localhost">>,
  163:     Res = execute_auth(admin_create_instant_room_body(MUCServer, Name, JID, <<"Ali">>), Config),
  164:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)).
  165: 
  166: admin_try_delete_nonexistent_room(Config) ->
  167:     RoomJID = jid:make_bare(<<"unknown">>, muc_helper:muc_host()),
  168:     Res = execute_auth(delete_room_body(RoomJID, null), Config),
  169:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"non-existent">>)).
  170: 
  171: admin_try_delete_room_with_nonexistent_domain(Config) ->
  172:     RoomJID = jid:make_bare(<<"unknown">>, <<"unknown">>),
  173:     Res = execute_auth(delete_room_body(RoomJID, null), Config),
  174:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"non-existent">>)).
  175: 
  176: admin_invite_user(Config) ->
  177:     muc_helper:story_with_room(Config, [], [{alice, 1}, {bob, 1}], fun admin_invite_user_story/3).
  178: 
  179: admin_invite_user_story(Config, Alice, Bob) ->
  180:     RoomJIDBin = ?config(room_jid, Config),
  181:     RoomJID = jid:from_binary(RoomJIDBin),
  182:     Res = execute_auth(admin_invite_user_body(RoomJID, Alice, Bob, null), Config),
  183:     ?assertNotEqual(nomatch, binary:match(get_ok_value(?INVITE_USER_PATH, Res),
  184:                                           <<"successfully">>)),
  185:     Stanza = escalus:wait_for_stanza(Bob),
  186:     escalus:assert(is_message, Stanza),
  187:     ?assertEqual(RoomJIDBin,
  188:                  exml_query:path(Stanza, [{element, <<"x">>}, {attr, <<"jid">>}])).
  189: 
  190: admin_try_invite_user_to_nonexistent_room(Config) ->
  191:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  192:                                     fun admin_try_invite_user_to_nonexistent_room_story/3).
  193: 
  194: admin_try_invite_user_to_nonexistent_room_story(Config, Alice, Bob) ->
  195:     Res = execute_auth(admin_invite_user_body(?NONEXISTENT_ROOM, Alice, Bob, null), Config),
  196:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)).
  197: 
  198: admin_kick_user(Config) ->
  199:     muc_helper:story_with_room(Config, [], [{alice, 1}, {bob, 1}], fun admin_kick_user_story/3).
  200: 
  201: admin_kick_user_story(Config, Alice, Bob) ->
  202:     RoomJIDBin = ?config(room_jid, Config),
  203:     RoomJID = jid:from_binary(RoomJIDBin),
  204:     BobNick = <<"Bobek">>,
  205:     Reason = <<"You are too laud">>,
  206:     enter_room(RoomJID, Alice, <<"ali">>),
  207:     enter_room(RoomJID, Bob, BobNick),
  208:     Res = execute_auth(kick_user_body(RoomJID, BobNick, Reason), Config),
  209:     ?assertNotEqual(nomatch, binary:match(get_ok_value(?KICK_USER_PATH, Res),
  210:                                           <<"successfully">>)),
  211:     escalus:wait_for_stanzas(Bob, 2),
  212:     KickStanza = escalus:wait_for_stanza(Bob),
  213:     escalus:assert(is_presence_with_type, [<<"unavailable">>], KickStanza),
  214:     ?assertEqual(Reason,
  215:                  exml_query:path(KickStanza, [{element, <<"x">>}, {element, <<"item">>},
  216:                                               {element, <<"reason">>}, cdata])).
  217: 
  218: admin_send_message_to_room(Config) ->
  219:     muc_helper:story_with_room(Config, [], [{alice, 1}, {bob, 1}],
  220:                                fun admin_send_message_to_room_story/3).
  221: 
  222: admin_send_message_to_room_story(Config, _Alice, Bob) ->
  223:     RoomJID = jid:from_binary(?config(room_jid, Config)),
  224:     Message = <<"Hello All!">>,
  225:     BobNick = <<"Bobek">>,
  226:     enter_room(RoomJID, Bob, BobNick),
  227:     escalus:wait_for_stanza(Bob),
  228:     % Send message
  229:     Res = execute_auth(admin_send_message_to_room_body(RoomJID, Bob, Message), Config),
  230:     ?assertNotEqual(nomatch, binary:match(get_ok_value(?SEND_MESSAGE_PATH, Res),
  231:                                           <<"successfully">>)),
  232:     assert_is_message_correct(RoomJID, BobNick, <<"groupchat">>, Message,
  233:                               escalus:wait_for_stanza(Bob)).
  234: 
  235: admin_get_room_config(Config) ->
  236:     muc_helper:story_with_room(Config, [], [{alice, 1}], fun admin_get_room_config_story/2).
  237: 
  238: admin_get_room_config_story(Config, _Alice) ->
  239:     RoomJID = jid:from_binary(?config(room_jid, Config)),
  240:     Res = execute_auth(get_room_config_body(RoomJID), Config),
  241:     assert_default_room_config(Res).
  242: 
  243: admin_try_get_nonexistent_room_config(Config) ->
  244:     Res = execute_auth(get_room_config_body(?NONEXISTENT_ROOM), Config),
  245:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)).
  246: 
  247: admin_change_room_config(Config) ->
  248:     muc_helper:story_with_room(Config, [], [{alice, 1}], fun admin_change_room_config_story/2).
  249: 
  250: admin_change_room_config_story(Config, _Alice) ->
  251:     RoomJID = jid:from_binary(?config(room_jid, Config)),
  252:     Title = <<"aloes">>,
  253:     Description = <<"The chat about aloes">>,
  254:     Public = false,
  255:     RoomConfig = #{title => Title, description => Description, public => Public},
  256:     Res = execute_auth(change_room_config_body(RoomJID, RoomConfig), Config),
  257:     ?assertMatch(#{<<"title">> := Title,
  258:                    <<"description">> := Description,
  259:                    <<"public">> := Public}, get_ok_value(?CHANGE_ROOM_CONFIG_PATH, Res)).
  260: 
  261: admin_try_change_nonexistent_room_config(Config) ->
  262:     RoomConfig = #{title => <<"NewTitle">>},
  263:     Res = execute_auth(change_room_config_body(?NONEXISTENT_ROOM, RoomConfig), Config),
  264:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)).
  265: 
  266: admin_list_room_users(Config) ->
  267:     muc_helper:story_with_room(Config, [], [{alice, 1}, {bob, 1}],
  268:                                fun admin_list_room_users_story/3).
  269: 
  270: admin_list_room_users_story(Config, Alice, Bob) ->
  271:     RoomJID = jid:from_binary(?config(room_jid, Config)),
  272:     BobNick = <<"Bobek">>,
  273:     AliceNick = <<"Ali">>,
  274:     enter_room(RoomJID, Bob, BobNick),
  275:     enter_room(RoomJID, Alice, AliceNick),
  276:     Res = execute_auth(list_room_users_body(RoomJID), Config),
  277:     ExpectedUsers = [{escalus_client:full_jid(Bob), BobNick, <<"PARTICIPANT">>},
  278:                      {escalus_client:full_jid(Alice), AliceNick, <<"MODERATOR">>}],
  279:     assert_room_users(ExpectedUsers, get_ok_value(?LIST_ROOM_USERS_PATH, Res)).
  280: 
  281: admin_try_list_users_from_nonexistent_room(Config) ->
  282:     Res = execute_auth(list_room_users_body(?NONEXISTENT_ROOM), Config),
  283:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)).
  284: 
  285: admin_get_room_messages(Config) ->
  286:     muc_helper:story_with_room(Config, [], [{alice, 1}, {bob, 1}],
  287:                                fun admin_get_room_messages_story/3).
  288: 
  289: admin_get_room_messages_story(Config, Alice, Bob) ->
  290:     RoomJID = jid:from_binary(?config(room_jid, Config)),
  291:     enter_room(RoomJID, Bob, <<"Bobek">>),
  292:     enter_room(RoomJID, Alice, <<"Ali">>),
  293:     escalus:wait_for_stanzas(Bob, 2),
  294:     execute_auth(admin_send_message_to_room_body(RoomJID, Bob, <<"Hi!">>), Config),
  295:     escalus:wait_for_stanzas(Bob, 1),
  296:     mam_helper:maybe_wait_for_archive(Config),
  297:     Res = execute_auth(get_room_messages_body(RoomJID, 50, null), Config),
  298:     #{<<"stanzas">> := [#{<<"stanza">> := StanzaXML}], <<"limit">> := 50} =
  299:         get_ok_value(?GET_MESSAGES_PATH, Res),
  300:     ?assertMatch({ok, #xmlel{name = <<"message">>}}, exml:parse(StanzaXML)).
  301: 
  302: admin_try_get_nonexistent_room_messages(Config) ->
  303:     Res = execute_auth(get_room_messages_body(?NONEXISTENT_ROOM, null, null), Config),
  304:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)).
  305: 
  306: %% User test cases
  307: 
  308: user_list_rooms(Config) ->
  309:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], fun user_list_rooms_story/3).
  310: 
  311: user_list_rooms_story(Config, Alice, Bob) ->
  312:     AliceJID = jid:from_binary(escalus_client:short_jid(Alice)),
  313:     BobJID = jid:from_binary(escalus_client:short_jid(Bob)),
  314:     AliceRoom = rand_name(),
  315:     BobRoom = rand_name(),
  316:     muc_helper:create_instant_room(AliceRoom, AliceJID, <<"Ali">>, []),
  317:     muc_helper:create_instant_room(BobRoom, BobJID, <<"Bob">>, []),
  318: 
  319:     Res = execute_user(user_list_rooms_body(muc_helper:muc_host(), null, null), Alice, Config),
  320:     #{<<"rooms">> := Rooms } = get_ok_value(?LIST_ROOMS_PATH, Res),
  321:     ?assert(contain_room(AliceRoom, Rooms)),
  322:     ?assert(contain_room(BobRoom, Rooms)).
  323: 
  324: user_create_and_delete_room(Config) ->
  325:     escalus:fresh_story_with_config(Config, [{alice, 1}], fun user_create_and_delete_room_story/2).
  326: 
  327: user_create_and_delete_room_story(Config, Alice) ->
  328:     Name = rand_name(),
  329:     MUCServer = muc_helper:muc_host(),
  330:     RoomJID = jid:make_bare(Name, MUCServer),
  331:     % Create instant room
  332:     Res = execute_user(user_create_instant_room_body(MUCServer, Name, <<"Ali">>), Alice, Config),
  333:     ?assertMatch(#{<<"title">> := Name, <<"private">> := false, <<"usersNumber">> := 0},
  334:                  get_ok_value(?CREATE_INSTANT_ROOM_PATH, Res)),
  335:     Res2 = execute_user(user_list_rooms_body(MUCServer, null, null), Alice, Config),
  336:     ?assert(contain_room(Name, get_ok_value(?LIST_ROOMS_PATH, Res2))),
  337:     % Delete room
  338:     Res3 = execute_user(delete_room_body(RoomJID, null), Alice, Config),
  339:     ?assertNotEqual(nomatch, binary:match(get_ok_value(?DELETE_ROOM_PATH, Res3),
  340:                                           <<"successfully">>)),
  341:     Res4 = execute_user(user_list_rooms_body(MUCServer, null, null), Alice, Config),
  342:     ?assertNot(contain_room(Name, get_ok_value(?LIST_ROOMS_PATH, Res4))).
  343: 
  344: user_try_create_instant_room_with_nonexistent_domain(Config) ->
  345:     escalus:fresh_story_with_config(Config, [{alice, 1}],
  346:                                     fun user_try_create_instant_room_with_nonexistent_domain_story/2).
  347: 
  348: user_try_create_instant_room_with_nonexistent_domain_story(Config, Alice) ->
  349:     Res = execute_user(user_create_instant_room_body(<<"unknown">>, rand_name(), <<"Ali">>),
  350:                        Alice, Config),
  351:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)).
  352: 
  353: user_try_delete_nonexistent_room(Config) ->
  354:     escalus:fresh_story_with_config(Config, [{alice, 1}],
  355:                                     fun user_try_delete_nonexistent_room_story/2).
  356: 
  357: user_try_delete_nonexistent_room_story(Config, Alice) ->
  358:     RoomJID = jid:make_bare(<<"unknown">>, muc_helper:muc_host()),
  359:     Res = execute_user(delete_room_body(RoomJID, null), Alice, Config),
  360:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"non-existent">>)).
  361: 
  362: user_try_delete_room_by_not_owner(Config) ->
  363:     muc_helper:story_with_room(Config, [], [{alice, 1}, {bob, 1}],
  364:                                fun user_try_delete_room_by_not_owner_story/3).
  365: 
  366: user_try_delete_room_by_not_owner_story(Config, _Alice, Bob) ->
  367:     RoomJID = jid:from_binary(?config(room_jid, Config)),
  368:     Res = execute_user(delete_room_body(RoomJID, null), Bob, Config),
  369:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"does not have permission">>)).
  370: 
  371: user_invite_user(Config) ->
  372:     muc_helper:story_with_room(Config, [], [{alice, 1}, {bob, 1}], fun user_invite_user_story/3).
  373: 
  374: user_invite_user_story(Config, Alice, Bob) ->
  375:     RoomJIDBin = ?config(room_jid, Config),
  376:     RoomJID = jid:from_binary(RoomJIDBin),
  377:     Res = execute_user(user_invite_user_body(RoomJID, Bob, null), Alice, Config),
  378:     ?assertNotEqual(nomatch, binary:match(get_ok_value(?INVITE_USER_PATH, Res),
  379:                                           <<"successfully">>)),
  380:     Stanza = escalus:wait_for_stanza(Bob),
  381:     escalus:assert(is_message, Stanza),
  382:     ?assertEqual(RoomJIDBin,
  383:                  exml_query:path(Stanza, [{element, <<"x">>}, {attr, <<"jid">>}])).
  384: 
  385: user_kick_user(Config) ->
  386:     muc_helper:story_with_room(Config, [], [{alice, 1}, {bob, 1}], fun user_kick_user_story/3).
  387: 
  388: user_kick_user_story(Config, Alice, Bob) ->
  389:     RoomJIDBin = ?config(room_jid, Config),
  390:     RoomJID = jid:from_binary(RoomJIDBin),
  391:     BobNick = <<"Bobek">>,
  392:     Reason = <<"You are too loud">>,
  393:     enter_room(RoomJID, Alice, <<"ali">>),
  394:     enter_room(RoomJID, Bob, BobNick),
  395:     Res = execute_user(kick_user_body(RoomJID, BobNick, Reason), Alice, Config),
  396:     ?assertNotEqual(nomatch, binary:match(get_ok_value(?KICK_USER_PATH, Res),
  397:                                           <<"successfully">>)),
  398:     escalus:wait_for_stanzas(Bob, 2),
  399:     KickStanza = escalus:wait_for_stanza(Bob),
  400:     escalus:assert(is_presence_with_type, [<<"unavailable">>], KickStanza),
  401:     ?assertEqual(Reason,
  402:                  exml_query:path(KickStanza, [{element, <<"x">>}, {element, <<"item">>},
  403:                                               {element, <<"reason">>}, cdata])).
  404: 
  405: user_send_message_to_room(Config) ->
  406:     muc_helper:story_with_room(Config, [], [{alice, 1}, {bob, 1}],
  407:                                fun user_send_message_to_room_story/3).
  408: 
  409: user_send_message_to_room_story(Config, _Alice, Bob) ->
  410:     RoomJID = jid:from_binary(?config(room_jid, Config)),
  411:     Message = <<"Hello All!">>,
  412:     BobNick = <<"Bobek">>,
  413:     enter_room(RoomJID, Bob, BobNick),
  414:     escalus:wait_for_stanza(Bob),
  415:     % Send message
  416:     Res = execute_user(user_send_message_to_room_body(RoomJID, Message, null), Bob, Config),
  417:     ?assertNotEqual(nomatch, binary:match(get_ok_value(?SEND_MESSAGE_PATH, Res),
  418:                                           <<"successfully">>)),
  419:     assert_is_message_correct(RoomJID, BobNick, <<"groupchat">>, Message,
  420:                               escalus:wait_for_stanza(Bob)).
  421: 
  422: user_send_message_to_room_with_specified_res(Config) ->
  423:     muc_helper:story_with_room(Config, [], [{alice, 1}, {bob, 2}],
  424:                                fun user_send_message_to_room_with_specified_res_story/4).
  425: 
  426: user_send_message_to_room_with_specified_res_story(Config, _Alice, Bob, Bob2) ->
  427:     RoomJID = jid:from_binary(?config(room_jid, Config)),
  428:     Message = <<"Hello All!">>,
  429:     BobNick = <<"Bobek">>,
  430:     enter_room(RoomJID, Bob2, BobNick),
  431:     escalus:wait_for_stanza(Bob2),
  432:     % Send message
  433:     Res = execute_user(user_send_message_to_room_body(RoomJID, Message, <<"res2">>), Bob, Config),
  434:     ?assertNotEqual(nomatch, binary:match(get_ok_value(?SEND_MESSAGE_PATH, Res),
  435:                                           <<"successfully">>)),
  436:     assert_is_message_correct(RoomJID, BobNick, <<"groupchat">>, Message,
  437:                               escalus:wait_for_stanza(Bob2)).
  438: 
  439: user_without_session_send_message_to_room(Config) ->
  440:     muc_helper:story_with_room(Config, [], [{alice, 1}],
  441:                                fun user_without_session_send_message_to_room_story/2).
  442: 
  443: user_without_session_send_message_to_room_story(Config, Alice) ->
  444:     RoomJID = jid:from_binary(?config(room_jid, Config)),
  445:     JID = jid:from_binary(escalus_client:full_jid(Alice)),
  446:     {exit, _} = rpc(mim(), ejabberd_c2s, terminate_session, [JID, <<"Kicked">>]),
  447:     % Send message
  448:     Res = execute_user(user_send_message_to_room_body(RoomJID, <<"Hello!">>, null), Alice, Config),
  449:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"does not have any session">>)).
  450: 
  451: user_get_room_config(Config) ->
  452:     muc_helper:story_with_room(Config, [], [{alice, 1}, {bob, 1}],
  453:                                fun user_get_room_config_story/3).
  454: 
  455: user_get_room_config_story(Config, Alice, Bob) ->
  456:     RoomJID = jid:from_binary(?config(room_jid, Config)),
  457:     Res = execute_user(get_room_config_body(RoomJID), Alice, Config),
  458:     assert_default_room_config(Res),
  459:     % Not an owner tries to get room config
  460:     Res2 = execute_user(get_room_config_body(RoomJID), Bob, Config),
  461:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"does not have permission">>)).
  462: 
  463: user_try_get_nonexistent_room_config(Config) ->
  464:     escalus:fresh_story_with_config(Config, [{alice, 1}],
  465:                                     fun user_try_get_nonexistent_room_config_story/2).
  466: 
  467: user_try_get_nonexistent_room_config_story(Config, Alice) ->
  468:     Res = execute_user(get_room_config_body(?NONEXISTENT_ROOM), Alice, Config),
  469:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)).
  470: 
  471: user_change_room_config(Config) ->
  472:     muc_helper:story_with_room(Config, [], [{alice, 1}, {bob, 1}],
  473:                                fun user_change_room_config_story/3).
  474: 
  475: user_change_room_config_story(Config, Alice, Bob) ->
  476:     RoomJID = jid:from_binary(?config(room_jid, Config)),
  477:     Title = <<"aloes">>,
  478:     Description = <<"The chat about aloes">>,
  479:     Public = false,
  480:     RoomConfig = #{title => Title, description => Description, public => Public},
  481:     Res = execute_user(change_room_config_body(RoomJID, RoomConfig), Alice, Config),
  482:     ?assertMatch(#{<<"title">> := Title,
  483:                    <<"description">> := Description,
  484:                    <<"public">> := Public}, get_ok_value(?CHANGE_ROOM_CONFIG_PATH, Res)),
  485:     % Not an owner tries to change the room config
  486:     Res2 = execute_user(change_room_config_body(RoomJID, RoomConfig), Bob, Config),
  487:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"does not have permission">>)).
  488: 
  489: user_try_change_nonexistent_room_config(Config) ->
  490:     escalus:fresh_story_with_config(Config, [{alice, 1}],
  491:                                     fun user_try_change_nonexistent_room_config_story/2).
  492: 
  493: user_try_change_nonexistent_room_config_story(Config, Alice) ->
  494:     RoomConfig = #{title => <<"NewTitle">>},
  495:     Res = execute_user(change_room_config_body(?NONEXISTENT_ROOM, RoomConfig), Alice, Config),
  496:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)).
  497: 
  498: user_list_room_users(Config) ->
  499:     muc_helper:story_with_room(Config, [], [{alice, 1}, {bob, 1}],
  500:                                fun user_list_room_users_story/3).
  501: 
  502: user_list_room_users_story(Config, Alice, Bob) ->
  503:     RoomJID = jid:from_binary(?config(room_jid, Config)),
  504:     BobNick = <<"Bobek">>,
  505:     AliceNick = <<"Ali">>,
  506:     enter_room(RoomJID, Bob, BobNick),
  507:     enter_room(RoomJID, Alice, AliceNick),
  508:     Res = execute_user(list_room_users_body(RoomJID), Alice, Config),
  509:     ExpectedUsers = [{null, BobNick, <<"PARTICIPANT">>},
  510:                      {null, AliceNick, <<"MODERATOR">>}],
  511:     assert_room_users(ExpectedUsers, get_ok_value(?LIST_ROOM_USERS_PATH, Res)).
  512: 
  513: user_list_room_users_without_anonymous_mode(Config) ->
  514:     muc_helper:story_with_room(Config, [{anonymous, false}], [{alice, 1}, {bob, 1}],
  515:                                fun user_list_room_users_without_anonymous_mode_story/3).
  516: 
  517: user_list_room_users_without_anonymous_mode_story(Config, Alice, Bob) ->
  518:     RoomJID = jid:from_binary(?config(room_jid, Config)),
  519:     BobNick = <<"Bobek">>,
  520:     AliceNick = <<"Ali">>,
  521:     enter_room(RoomJID, Bob, BobNick),
  522:     enter_room(RoomJID, Alice, AliceNick),
  523:     Res = execute_user(list_room_users_body(RoomJID), Alice, Config),
  524:     ExpectedUsers = [{escalus_client:full_jid(Bob), BobNick, <<"PARTICIPANT">>},
  525:                      {escalus_client:full_jid(Alice), AliceNick, <<"MODERATOR">>}],
  526:     assert_room_users(ExpectedUsers, get_ok_value(?LIST_ROOM_USERS_PATH, Res)).
  527: 
  528: user_try_list_nonexistent_room_users(Config) ->
  529:     escalus:fresh_story_with_config(Config, [{alice, 1}],
  530:                                     fun user_try_list_nonexistent_room_users_story/2).
  531: 
  532: user_try_list_nonexistent_room_users_story(Config, Alice) ->
  533:     Res = execute_user(list_room_users_body(?NONEXISTENT_ROOM), Alice, Config),
  534:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)).
  535: 
  536: user_try_list_room_users_without_permission(Config) ->
  537:     muc_helper:story_with_room(Config, [{members_only, true}], [{alice, 1}, {bob, 1}],
  538:                                fun user_try_list_room_users_without_permission_story/3).
  539: 
  540: user_try_list_room_users_without_permission_story(Config, _Alice, Bob) ->
  541:     RoomJID = jid:from_binary(?config(room_jid, Config)),
  542:     Res = execute_user(list_room_users_body(RoomJID), Bob, Config),
  543:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"does not have permission">>)).
  544: 
  545: user_get_room_messages(Config) ->
  546:     muc_helper:story_with_room(Config, [], [{alice, 1}, {bob, 1}],
  547:                                fun user_get_room_messages_story/3).
  548: 
  549: user_get_room_messages_story(Config, Alice, Bob) ->
  550:     RoomJID = jid:from_binary(?config(room_jid, Config)),
  551:     enter_room(RoomJID, Bob, <<"Bobek">>),
  552:     enter_room(RoomJID, Alice, <<"Ali">>),
  553:     escalus:wait_for_stanzas(Bob, 2),
  554:     execute_user(user_send_message_to_room_body(RoomJID, <<"Hi!">>, null), Bob, Config),
  555:     escalus:wait_for_stanzas(Bob, 1),
  556:     mam_helper:maybe_wait_for_archive(Config),
  557:     Res = execute_user(get_room_messages_body(RoomJID, 50, null), Alice, Config),
  558:     #{<<"stanzas">> := [#{<<"stanza">> := StanzaXML}], <<"limit">> := 50} =
  559:         get_ok_value(?GET_MESSAGES_PATH, Res),
  560:     ?assertMatch({ok, #xmlel{name = <<"message">>}}, exml:parse(StanzaXML)).
  561: 
  562: user_try_get_nonexistent_room_messages(Config) ->
  563:     escalus:fresh_story_with_config(Config, [{alice, 1}],
  564:                                     fun user_try_get_nonexistent_room_messages_story/2).
  565: 
  566: user_try_get_nonexistent_room_messages_story(Config, Alice) ->
  567:     % Non-existent room with non-existent domain
  568:     Res = execute_user(get_room_messages_body(?NONEXISTENT_ROOM, null, null), Alice, Config),
  569:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)),
  570:     % Non-existent room with existent domain
  571:     Res2 = execute_user(get_room_messages_body(?NONEXISTENT_ROOM2, null, null), Alice, Config),
  572:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"not found">>)).
  573: 
  574: user_try_get_room_messages_without_permission(Config) ->
  575:     muc_helper:story_with_room(Config, [{members_only, true}], [{alice, 1}, {bob, 1}],
  576:                                fun user_try_get_room_messages_without_permission/3).
  577: 
  578: user_try_get_room_messages_without_permission(Config, _Alice, Bob) ->
  579:     RoomJID = jid:from_binary(?config(room_jid, Config)),
  580:     Res = execute_user(get_room_messages_body(RoomJID, null, null), Bob, Config),
  581:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"does not have permission">>)).
  582: 
  583: %% Helpers
  584: 
  585: rand_name() ->
  586:     rpc(mim(), mongoose_bin, gen_from_crypto, []).
  587: 
  588: -spec assert_room_users([{jid:jid(), binary(), binary()}], [map()]) -> ok.
  589: assert_room_users(Expected, Actual) ->
  590:     ActualTuples = [{JID, Nick, Role} || #{<<"jid">> := JID, <<"role">> := Role, <<"nick">> := Nick} <- Actual],
  591:     ?assertEqual(lists:sort(Expected), lists:sort(ActualTuples)).
  592: 
  593: assert_is_message_correct(RoomJID, SenderNick, Type, Text, ReceivedMessage) ->
  594:     escalus_pred:is_message(ReceivedMessage),
  595:     From = jid:to_binary(jid:replace_resource(RoomJID, SenderNick)),
  596:     From = exml_query:attr(ReceivedMessage, <<"from">>),
  597:     Type = exml_query:attr(ReceivedMessage, <<"type">>),
  598:     Body = #xmlel{name = <<"body">>, children = [#xmlcdata{content=Text}]},
  599:     Body = exml_query:subelement(ReceivedMessage, <<"body">>).
  600: 
  601: enter_room(RoomJID, User, Nick) ->
  602:     JID = jid:to_binary(jid:replace_resource(RoomJID, Nick)),
  603:     Pres = escalus_stanza:to(escalus_stanza:presence(<<"available">>, []), JID),
  604:     escalus:send(User, Pres),
  605:     escalus:wait_for_stanza(User).
  606: 
  607: contain_room(Name, #{<<"rooms">> := Rooms}) ->
  608:     contain_room(Name, Rooms);
  609: contain_room(Name, Rooms) when is_list(Rooms) -> 
  610:     lists:any(fun(#{<<"title">> := T}) -> T =:= Name end, Rooms).
  611: 
  612: assert_default_room_config(Response) ->
  613:     ?assertMatch(#{<<"title">> := <<>>,
  614:                    <<"description">> := <<>>,
  615:                    <<"allowChangeSubject">> := true,
  616:                    <<"allowQueryUsers">> := true,
  617:                    <<"allowPrivateMessages">> := true,
  618:                    <<"allowVisitorStatus">> := true,
  619:                    <<"allowVisitorNickchange">> := true,
  620:                    <<"public">> := true,
  621:                    <<"publicList">> := true,
  622:                    <<"persistent">> := false,
  623:                    <<"moderated">> := true,
  624:                    <<"membersByDefault">> := true,
  625:                    <<"membersOnly">> := false,
  626:                    <<"allowUserInvites">> := false,
  627:                    <<"allowMultipleSession">> := false,
  628:                    <<"passwordProtected">> := false,
  629:                    <<"password">> := <<>>,
  630:                    <<"anonymous">> := true,
  631:                    <<"mayGetMemberList">> := [],
  632:                    <<"maxUsers">> := 200,
  633:                    <<"logging">> := false}, get_ok_value(?GET_ROOM_CONFIG_PATH, Response)).
  634: 
  635: %% Request bodies
  636: 
  637: admin_create_instant_room_body(MUCDomain, Name, Owner, Nick) ->
  638:     Query = <<"mutation M1($mucDomain: String!, $name: String!, $owner: JID!, $nick: String!)
  639:               { muc { createInstantRoom(mucDomain: $mucDomain, name: $name, owner: $owner, nick: $nick)
  640:               { jid title private usersNumber } } }">>,
  641:     OpName = <<"M1">>,
  642:     Vars = #{mucDomain => MUCDomain, name => Name, owner => user_to_bin(Owner), nick => Nick},
  643:     #{query => Query, operationName => OpName, variables => Vars}.
  644: 
  645: admin_invite_user_body(Room, Sender, Recipient, Reason) ->
  646:     Query = <<"mutation M1($room: JID!, $sender: JID!, $recipient: JID!, $reason: String)
  647:               { muc { inviteUser(room: $room, sender: $sender, recipient: $recipient, reason: $reason) } }">>,
  648:     OpName = <<"M1">>,
  649:     Vars = #{room => jid:to_binary(Room), sender => user_to_bin(Sender),
  650:              recipient => user_to_bin(Recipient), reason => Reason},
  651:     #{query => Query, operationName => OpName, variables => Vars}.
  652: 
  653: kick_user_body(Room, Nick, Reason) ->
  654:     Query = <<"mutation M1($room: JID!, $nick: String!, $reason: String)
  655:               { muc { kickUser(room: $room, nick: $nick, reason: $reason) } }">>,
  656:     OpName = <<"M1">>,
  657:     Vars = #{room => jid:to_binary(Room), nick => Nick, reason => Reason},
  658:     #{query => Query, operationName => OpName, variables => Vars}.
  659: 
  660: admin_send_message_to_room_body(Room, From, Body) ->
  661:     Query = <<"mutation M1($room: JID!, $from: JID!, $body: String!)
  662:               { muc { sendMessageToRoom(room: $room, from: $from, body: $body) } }">>,
  663:     OpName = <<"M1">>,
  664:     Vars = #{room => jid:to_binary(Room), from => user_to_full_bin(From), body => Body},
  665:     #{query => Query, operationName => OpName, variables => Vars}.
  666: 
  667: delete_room_body(Room, Reason) ->
  668:     Query = <<"mutation M1($room: JID!, $reason: String)
  669:               { muc { deleteRoom(room: $room, reason: $reason) } }">>,
  670:     OpName = <<"M1">>,
  671:     Vars = #{room => jid:to_binary(Room), reason => Reason},
  672:     #{query => Query, operationName => OpName, variables => Vars}.
  673: 
  674: change_room_config_body(Room, Config) ->
  675:     Query = <<"mutation M1($room: JID!, $config: MUCRoomConfigInput!)
  676:               { muc { changeRoomConfiguration(room: $room, config: $config)
  677:               { title description allowChangeSubject allowQueryUsers allowPrivateMessages
  678:                 allowVisitorStatus allowVisitorNickchange public publicList persistent
  679:                 moderated membersByDefault membersOnly allowUserInvites allowMultipleSession
  680:                 passwordProtected password anonymous mayGetMemberList maxUsers logging } } }">>,
  681:     OpName = <<"M1">>,
  682:     Vars = #{room => jid:to_binary(Room), config => Config},
  683:     #{query => Query, operationName => OpName, variables => Vars}.
  684: 
  685: admin_list_rooms_body(MUCDomain, From, Limit, Index) ->
  686:     Query = <<"query Q1($mucDomain: String!, $from: JID, $limit: Int, $index: Int)
  687:               { muc { listRooms(mucDomain: $mucDomain, from: $from, limit: $limit, index: $index)
  688:               { rooms { jid title private usersNumber } count index first last} } }">>,
  689:     OpName = <<"Q1">>,
  690:     Vars = #{mucDomain => MUCDomain, from => user_to_bin(From), limit => Limit, index => Index},
  691:     #{query => Query, operationName => OpName, variables => Vars}.
  692: 
  693: get_room_config_body(Room) ->
  694:     Query = <<"query Q1($room: JID!)
  695:               { muc { getRoomConfig(room: $room)
  696:               { title description allowChangeSubject allowQueryUsers allowPrivateMessages
  697:                 allowVisitorStatus allowVisitorNickchange public publicList persistent
  698:                 moderated membersByDefault membersOnly allowUserInvites allowMultipleSession
  699:                 passwordProtected password anonymous mayGetMemberList maxUsers logging } } }">>,
  700:     OpName = <<"Q1">>,
  701:     Vars = #{room => jid:to_binary(Room)},
  702:     #{query => Query, operationName => OpName, variables => Vars}.
  703: 
  704: list_room_users_body(RoomJID) ->
  705:     Query = <<"query Q1($room: JID!)
  706:               { muc { listRoomUsers(room: $room)
  707:               { jid nick role } } }">>,
  708:     OpName = <<"Q1">>,
  709:     Vars = #{room => jid:to_binary(RoomJID)},
  710:     #{query => Query, operationName => OpName, variables => Vars}.
  711: 
  712: get_room_messages_body(RoomJID, PageSize, Before) ->
  713:     Query = <<"query Q1($room: JID!, $pageSize: Int, $before: DateTime)
  714:               { muc { getRoomMessages(room: $room, pageSize: $pageSize, before: $before)
  715:               { stanzas { stanza } limit } } }">>,
  716:     OpName = <<"Q1">>,
  717:     Vars = #{<<"room">> => jid:to_binary(RoomJID), <<"pageSize">> => PageSize,
  718:              <<"before">> => Before},
  719:     #{query => Query, operationName => OpName, variables => Vars}.
  720: 
  721: user_list_rooms_body(MUCDomain, Limit, Index) ->
  722:     Query = <<"query Q1($mucDomain: String!, $limit: Int, $index: Int)
  723:               { muc { listRooms(mucDomain: $mucDomain, limit: $limit, index: $index)
  724:               { rooms { jid title private usersNumber } count index first last} } }">>,
  725:     OpName = <<"Q1">>,
  726:     Vars = #{mucDomain => MUCDomain, limit => Limit, index => Index},
  727:     #{query => Query, operationName => OpName, variables => Vars}.
  728: 
  729: user_send_message_to_room_body(Room, Body, Resource) ->
  730:     Query = <<"mutation M1($room: JID!, $body: String!, $resource: String)
  731:               { muc { sendMessageToRoom(room: $room, body: $body, resource: $resource) } }">>,
  732:     OpName = <<"M1">>,
  733:     Vars = #{room => jid:to_binary(Room), body => Body, resource => Resource},
  734:     #{query => Query, operationName => OpName, variables => Vars}.
  735: 
  736: user_create_instant_room_body(MUCDomain, Name, Nick) ->
  737:     Query = <<"mutation M1($mucDomain: String!, $name: String!, $nick: String!)
  738:               { muc { createInstantRoom(mucDomain: $mucDomain, name: $name, nick: $nick)
  739:               { jid title private usersNumber } } }">>,
  740:     OpName = <<"M1">>,
  741:     Vars = #{mucDomain => MUCDomain, name => Name, nick => Nick},
  742:     #{query => Query, operationName => OpName, variables => Vars}.
  743: 
  744: user_invite_user_body(Room, Recipient, Reason) ->
  745:     Query = <<"mutation M1($room: JID!, $recipient: JID!, $reason: String)
  746:               { muc { inviteUser(room: $room, recipient: $recipient, reason: $reason) } }">>,
  747:     OpName = <<"M1">>,
  748:     Vars = #{room => jid:to_binary(Room), recipient => user_to_bin(Recipient), reason => Reason},
  749:     #{query => Query, operationName => OpName, variables => Vars}.