1: -module(muc_light_SUITE).
    2: 
    3: -include_lib("escalus/include/escalus.hrl").
    4: -include_lib("escalus/include/escalus_xmlns.hrl").
    5: -include_lib("common_test/include/ct.hrl").
    6: -include_lib("exml/include/exml.hrl").
    7: -include_lib("eunit/include/eunit.hrl").
    8: -include("mam_helper.hrl").
    9: 
   10: -export([ % service
   11:          removing_users_from_server_triggers_room_destruction/1
   12:         ]).
   13: -export([ % entity
   14:          disco_service/1,
   15:          disco_features/1,
   16:          disco_features_with_mam/1,
   17:          disco_info/1,
   18:          disco_info_with_mam/1,
   19:          disco_rooms/1,
   20:          disco_rooms_empty_page_1/1,
   21:          disco_rooms_empty_page_infinity/1,
   22:          disco_rooms_created_page_1/1,
   23:          disco_rooms_created_page_infinity/1,
   24:          disco_rooms_rsm/1,
   25:          rooms_in_rosters/1,
   26:          rooms_in_rosters_doesnt_break_disco_info/1,
   27:          no_roomname_in_schema_doesnt_break_disco_and_roster/1,
   28:          unauthorized_stanza/1
   29:     ]).
   30: -export([ % occupant
   31:          send_message/1,
   32:          change_subject/1,
   33:          change_roomname/1,
   34:          all_can_configure/1,
   35:          set_config_deny/1,
   36:          get_room_config/1,
   37:          custom_default_config_works/1,
   38:          get_room_occupants/1,
   39:          get_room_info/1,
   40:          leave_room/1,
   41:          change_other_aff_deny/1
   42:         ]).
   43: -export([ % owner
   44:          create_room/1,
   45:          create_room_unique/1,
   46:          create_room_with_equal_occupants/1,
   47:          create_existing_room_deny/1,
   48:          destroy_room/1,
   49:          destroy_room_get_disco_items_empty/1,
   50:          destroy_room_get_disco_items_one_left/1,
   51:          set_config/1,
   52:          set_config_with_custom_schema/1,
   53:          deny_config_change_that_conflicts_with_schema/1,
   54:          assorted_config_doesnt_lead_to_duplication/1,
   55:          remove_and_add_users/1,
   56:          explicit_owner_change/1,
   57:          implicit_owner_change/1,
   58:          edge_case_owner_change/1,
   59:          adding_wrongly_named_user_triggers_infinite_loop/1
   60:         ]).
   61: -export([ % limits
   62:          rooms_per_user/1,
   63:          max_occupants/1
   64:         ]).
   65: -export([ % blocking
   66:          manage_blocklist/1,
   67:          block_room/1,
   68:          block_user/1,
   69:          blocking_disabled/1
   70:         ]).
   71: 
   72: -export([all/0, groups/0, suite/0,
   73:          init_per_suite/1, end_per_suite/1,
   74:          init_per_group/2, end_per_group/2,
   75:          init_per_testcase/2, end_per_testcase/2]).
   76: 
   77: -import(escalus_ejabberd, [rpc/3]).
   78: -import(muc_helper, [foreach_occupant/3, foreach_recipient/2]).
   79: -import(distributed_helper, [subhost_pattern/1]).
   80: -import(domain_helper, [host_type/0, domain/0]).
   81: -import(muc_light_helper, [
   82:                            bin_aff_users/1,
   83:                            gc_message_verify_fun/3,
   84:                            lbin/1,
   85:                            room_bin_jid/1,
   86:                            verify_aff_bcast/2,
   87:                            verify_aff_bcast/3,
   88:                            verify_aff_users/2,
   89:                            kv_el/2,
   90:                            stanza_create_room/3,
   91:                            create_room/6,
   92:                            stanza_aff_set/2,
   93:                            user_leave/3,
   94:                            stanza_blocking_set/1,
   95:                            default_config/1,
   96:                            default_schema/0
   97: ]).
   98: 
   99: -include("muc_light.hrl").
  100: 
  101: -define(ROOM, <<"testroom">>).
  102: -define(ROOM2, <<"testroom2">>).
  103: 
  104: -define(MUCHOST, (muc_light_helper:muc_host())).
  105: 
  106: -type ct_aff_user() :: {EscalusClient :: escalus:client(), Aff :: atom()}.
  107: -type ct_aff_users() :: [ct_aff_user()].
  108: -type ct_block_item() :: muc_light_helper:ct_block_item().
  109: -type verify_fun() :: muc_helper:verify_fun().
  110: -type xmlel() :: exml:element().
  111: 
  112: -define(DEFAULT_AFF_USERS, [{Alice, owner}, {Bob, member}, {Kate, member}]).
  113: 
  114: %%--------------------------------------------------------------------
  115: %% Suite configuration
  116: %%--------------------------------------------------------------------
  117: 
  118: all() ->
  119:     [
  120:      {group, service},
  121:      {group, entity},
  122:      {group, occupant},
  123:      {group, owner},
  124:      {group, blocking},
  125:      {group, limits}
  126:     ].
  127: 
  128: groups() ->
  129:     G = [
  130:          {service, [sequence], [
  131:                                 removing_users_from_server_triggers_room_destruction
  132:                                ]},
  133:          {entity, [sequence], [
  134:                                disco_service,
  135:                                disco_features,
  136:                                disco_features_with_mam,
  137:                                disco_info,
  138:                                disco_info_with_mam,
  139:                                disco_rooms,
  140:                                disco_rooms_rsm,
  141:                                disco_rooms_created_page_1,
  142:                                disco_rooms_created_page_infinity,
  143:                                disco_rooms_empty_page_infinity,
  144:                                disco_rooms_empty_page_1,
  145:                                unauthorized_stanza,
  146:                                rooms_in_rosters,
  147:                                rooms_in_rosters_doesnt_break_disco_info,
  148:                                no_roomname_in_schema_doesnt_break_disco_and_roster
  149:                               ]},
  150:          {occupant, [sequence], [
  151:                                  send_message,
  152:                                  change_subject,
  153:                                  change_roomname,
  154:                                  all_can_configure,
  155:                                  set_config_deny,
  156:                                  get_room_config,
  157:                                  custom_default_config_works,
  158:                                  get_room_occupants,
  159:                                  get_room_info,
  160:                                  leave_room,
  161:                                  change_other_aff_deny
  162:                                 ]},
  163:          {owner, [sequence], [
  164:                               create_room,
  165:                               create_room_unique,
  166:                               create_room_with_equal_occupants,
  167:                               create_existing_room_deny,
  168:                               destroy_room,
  169:                               destroy_room_get_disco_items_empty,
  170:                               destroy_room_get_disco_items_one_left,
  171:                               set_config,
  172:                               set_config_with_custom_schema,
  173:                               deny_config_change_that_conflicts_with_schema,
  174:                               assorted_config_doesnt_lead_to_duplication,
  175:                               remove_and_add_users,
  176:                               explicit_owner_change,
  177:                               implicit_owner_change,
  178:                               edge_case_owner_change,
  179:                               adding_wrongly_named_user_triggers_infinite_loop
  180:                              ]},
  181:          {limits, [sequence], [
  182:                                rooms_per_user,
  183:                                max_occupants
  184:                               ]},
  185:          {blocking, [sequence], [
  186:                                  manage_blocklist,
  187:                                  block_room,
  188:                                  block_user,
  189:                                  blocking_disabled
  190:                                 ]}
  191:         ],
  192:     ct_helper:repeat_all_until_all_ok(G).
  193: 
  194: suite() ->
  195:     escalus:suite().
  196: 
  197: %%--------------------------------------------------------------------
  198: %% Init & teardown
  199: %%--------------------------------------------------------------------
  200: 
  201: init_per_suite(Config) ->
  202:     Config1 = dynamic_modules:save_modules(host_type(), Config),
  203:     Config2 = escalus:init_per_suite(Config1),
  204:     escalus:create_users(Config2, escalus:get_users([alice, bob, kate, mike])).
  205: 
  206: end_per_suite(Config) ->
  207:     HostType = host_type(),
  208:     muc_light_helper:clear_db(HostType),
  209:     Config1 = escalus:delete_users(Config, escalus:get_users([alice, bob, kate, mike])),
  210:     dynamic_modules:restore_modules(Config),
  211:     escalus:end_per_suite(Config1).
  212: 
  213: init_per_group(_GroupName, Config) ->
  214:     Config.
  215: 
  216: end_per_group(_GroupName, Config) ->
  217:     Config.
  218: 
  219: -define(ROOM_LESS_CASES, [disco_rooms_empty_page_infinity,
  220:                           disco_rooms_empty_page_1]).
  221: 
  222: -define(CUSTOM_CONFIG_CASES, [set_config_with_custom_schema,
  223:                               deny_config_change_that_conflicts_with_schema,
  224:                               no_roomname_in_schema_doesnt_break_disco_and_roster,
  225:                               custom_default_config_works]).
  226: 
  227: init_per_testcase(removing_users_from_server_triggers_room_destruction = CaseName, Config) ->
  228:     dynamic_modules:ensure_modules(host_type(), required_modules(CaseName)),
  229:     Config1 = escalus:create_users(Config, escalus:get_users([carol])),
  230:     create_room(?ROOM, ?MUCHOST, carol, [], Config1, ver(1)),
  231:     escalus:init_per_testcase(CaseName, Config1);
  232: init_per_testcase(disco_rooms_rsm = CaseName, Config) ->
  233:     dynamic_modules:ensure_modules(host_type(), required_modules(CaseName)),
  234:     create_room(?ROOM, ?MUCHOST, alice, [bob, kate], Config, ver(1)),
  235:     create_room(?ROOM2, ?MUCHOST, alice, [bob, kate], Config, ver(1)),
  236:     escalus:init_per_testcase(disco_rooms_rsm, Config);
  237: init_per_testcase(CaseName, Config) ->
  238:     dynamic_modules:ensure_modules(host_type(), required_modules(CaseName)),
  239:     case lists:member(CaseName, ?ROOM_LESS_CASES) of
  240:         false -> create_room(?ROOM, ?MUCHOST, alice, [bob, kate], Config, ver(1));
  241:         _ -> ok
  242:     end,
  243:     escalus:init_per_testcase(CaseName, Config).
  244: 
  245: end_per_testcase(CaseName, Config) ->
  246:     muc_light_helper:clear_db(host_type()),
  247:     escalus:end_per_testcase(CaseName, Config).
  248: 
  249: %% Module configuration per test case
  250: 
  251: required_modules(CaseName) ->
  252:     [{mod_muc_light, common_muc_light_opts() ++ muc_light_opts(CaseName) ++ schema_opts(CaseName)},
  253:      {mod_mam_muc, mam_muc_config(CaseName)}].
  254: 
  255: muc_light_opts(CaseName) when CaseName =:= disco_rooms_rsm;
  256:                               CaseName =:= disco_rooms_empty_page_1;
  257:                               CaseName =:= rooms_created_page_1 ->
  258:     [{rooms_per_page, 1}];
  259: muc_light_opts(CaseName) when CaseName =:= disco_rooms_empty_page_infinity;
  260:                               CaseName =:= disco_rooms_created_page_infinity ->
  261:     [{rooms_per_page, infinity}];
  262: muc_light_opts(all_can_configure) ->
  263:     [{all_can_configure, true}];
  264: muc_light_opts(create_room_with_equal_occupants) ->
  265:     [{equal_occupants, true}];
  266: muc_light_opts(rooms_per_user) ->
  267:     [{rooms_per_user, 1}];
  268: muc_light_opts(max_occupants) ->
  269:     [{max_occupants, 1}];
  270: muc_light_opts(block_user) ->
  271:     [{all_can_invite, true}];
  272: muc_light_opts(blocking_disabled) ->
  273:     [{blocking, false}];
  274: muc_light_opts(_) ->
  275:     [].
  276: 
  277: schema_opts(CaseName) ->
  278:     case lists:member(CaseName, ?CUSTOM_CONFIG_CASES) of
  279:         true -> [{config_schema, custom_schema()}];
  280:         false -> []
  281:     end.
  282: 
  283: common_muc_light_opts() ->
  284:     [{host, subhost_pattern(muc_light_helper:muc_host_pattern())},
  285:      {backend, mongoose_helper:mnesia_or_rdbms_backend()},
  286:      {cache_affs, [{module, internal},
  287:                    {strategy, fifo},
  288:                    {time_to_live, 2},
  289:                    {number_of_segments, 3}]},
  290:      {rooms_in_rosters, true}].
  291: 
  292: mam_muc_config(CaseName) when CaseName =:= disco_features_with_mam;
  293:                               CaseName =:= disco_info_with_mam ->
  294:     [{backend, rdbms},
  295:      {host, subhost_pattern(?MUCHOST)}];
  296: mam_muc_config(_CaseName) ->
  297:     stopped.
  298: 
  299: %%--------------------------------------------------------------------
  300: %% MUC light tests
  301: %%--------------------------------------------------------------------
  302: 
  303: %% ---------------------- Service ----------------------
  304: 
  305: removing_users_from_server_triggers_room_destruction(Config) ->
  306:     escalus:delete_users(Config, escalus:get_users([carol])),
  307:     {error, not_exists} = rpc(mod_muc_light_db_backend, get_info, [host_type(), {?ROOM, ?MUCHOST}]).
  308: 
  309: %% ---------------------- Disco ----------------------
  310: 
  311: disco_service(Config) ->
  312:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  313:             Server = escalus_client:server(Alice),
  314:             escalus:send(Alice, escalus_stanza:service_discovery(Server)),
  315:             Stanza = escalus:wait_for_stanza(Alice),
  316:             Query = exml_query:subelement(Stanza, <<"query">>),
  317:             Item = exml_query:subelement_with_attr(Query, <<"jid">>, ?MUCHOST),
  318:             ?assertEqual(?NS_MUC_LIGHT, exml_query:attr(Item, <<"node">>)),
  319:             escalus:assert(is_stanza_from, [domain()], Stanza)
  320:         end).
  321: 
  322: disco_features(Config) ->
  323:     disco_features_story(Config, false).
  324: 
  325: disco_features_with_mam(Config) ->
  326:     disco_features_story(Config, true).
  327: 
  328: disco_features_story(Config, HasMAM) ->
  329:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  330:             DiscoStanza = escalus_stanza:to(escalus_stanza:iq_get(?NS_DISCO_INFO, []), ?MUCHOST),
  331:             escalus:send(Alice, DiscoStanza),
  332:             Stanza = escalus:wait_for_stanza(Alice),
  333:             <<"conference">> = exml_query:path(Stanza, [{element, <<"query">>},
  334:                                                         {element, <<"identity">>},
  335:                                                         {attr, <<"category">>}]),
  336:             check_features(Stanza, HasMAM),
  337:             escalus:assert(is_stanza_from, [?MUCHOST], Stanza)
  338:         end).
  339: 
  340: disco_info(Config) ->
  341:     disco_info_story(Config, false).
  342: 
  343: disco_info_with_mam(Config) ->
  344:     disco_info_story(Config, true).
  345: 
  346: disco_info_story(Config, HasMAM) ->
  347:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  348:             RoomJID = room_bin_jid(?ROOM),
  349:             DiscoStanza = escalus_stanza:to(escalus_stanza:iq_get(?NS_DISCO_INFO, []), RoomJID),
  350:             escalus:send(Alice, DiscoStanza),
  351:             Stanza = escalus:wait_for_stanza(Alice),
  352:             check_features(Stanza, HasMAM),
  353:             escalus:assert(is_stanza_from, [RoomJID], Stanza)
  354:         end).
  355: 
  356: check_features(Stanza, HasMAM) ->
  357:     ExpectedFeatures = expected_features(HasMAM),
  358:     ActualFeatures = exml_query:paths(Stanza, [{element, <<"query">>},
  359:                                                {element, <<"feature">>},
  360:                                                {attr, <<"var">>}]),
  361:     ?assertEqual(ExpectedFeatures, ActualFeatures).
  362: 
  363: expected_features(true) ->
  364:     lists:sort([?NS_MUC_LIGHT | mam_helper:namespaces()]);
  365: expected_features(false) ->
  366:     [?NS_MUC_LIGHT].
  367: 
  368: %% The room list is empty. Rooms_per_page set to `infinity`
  369: disco_rooms_empty_page_infinity(Config) ->
  370:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  371:         [] = get_disco_rooms(Alice)
  372:         end).
  373: 
  374: %% The room list is empty. Rooms_per_page set to 1
  375: disco_rooms_empty_page_1(Config) ->
  376:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  377:         [] = get_disco_rooms(Alice)
  378:         end).
  379: 
  380: %% There is one room created. Rooms_per_page set to 1
  381: disco_rooms_created_page_1(Config) ->
  382:     escalus:story(Config, [{alice, 1}], fun verify_user_has_one_room/1).
  383: 
  384: %% There is one room created. Rooms_per_page set to `infinity`
  385: disco_rooms_created_page_infinity(Config) ->
  386:     escalus:story(Config, [{alice, 1}], fun verify_user_has_one_room/1).
  387: 
  388: disco_rooms(Config) ->
  389:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  390:             MucHost = ?MUCHOST,
  391:             {ok, {?ROOM2, MucHost}} = create_room(?ROOM2, ?MUCHOST, kate, [], Config, ver(0)),
  392:             %% we should get 1 room, Alice is not in the second one
  393:             [Item] = get_disco_rooms(Alice),
  394:             ProperJID = room_bin_jid(?ROOM),
  395:             ProperJID = exml_query:attr(Item, <<"jid">>),
  396:             ProperVer = ver(1),
  397:             ProperVer = exml_query:attr(Item, <<"version">>)
  398:         end).
  399: 
  400: disco_rooms_rsm(Config) ->
  401:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  402:             DiscoStanza = escalus_stanza:to(escalus_stanza:iq_get(?NS_DISCO_ITEMS, []), ?MUCHOST),
  403:             escalus:send(Alice, DiscoStanza),
  404:             %% we should get 1 room with RSM info
  405:             Stanza = escalus:wait_for_stanza(Alice),
  406:             [Item] = exml_query:paths(Stanza, [{element, <<"query">>}, {element, <<"item">>}]),
  407:             ProperJID = room_bin_jid(?ROOM),
  408:             ProperJID = exml_query:attr(Item, <<"jid">>),
  409: 
  410:             RSM = #xmlel{ name = <<"set">>,
  411:                           attrs = [{<<"xmlns">>, ?NS_RSM}],
  412:                           children = [ #xmlel{ name = <<"max">>,
  413:                                                children = [#xmlcdata{ content = <<"10">> }] },
  414:                                        #xmlel{ name = <<"before">> } ]  },
  415:             DiscoStanza2 = escalus_stanza:to(
  416:                              escalus_stanza:iq_get(?NS_DISCO_ITEMS, [RSM]), ?MUCHOST),
  417:             escalus:send(Alice, DiscoStanza2),
  418:             %% we should get second room
  419:             Stanza2 = escalus:wait_for_stanza(Alice),
  420:             [Item2] = exml_query:paths(Stanza2, [{element, <<"query">>}, {element, <<"item">>}]),
  421:             ProperJID2 = room_bin_jid(?ROOM2),
  422:             ProperJID2 = exml_query:attr(Item2, <<"jid">>),
  423: 
  424:             BadAfter = #xmlel{ name = <<"after">>,
  425:                                children = [#xmlcdata{ content = <<"oops@muclight.localhost">> }] },
  426:             RSM2 = #xmlel{ name = <<"set">>,
  427:                           attrs = [{<<"xmlns">>, ?NS_RSM}],
  428:                           children = [ #xmlel{ name = <<"max">>,
  429:                                                children = [#xmlcdata{ content = <<"10">> }] },
  430:                                        BadAfter ]  },
  431:             DiscoStanza3 = escalus_stanza:to(
  432:                              escalus_stanza:iq_get(?NS_DISCO_ITEMS, [RSM2]), ?MUCHOST),
  433:             escalus:send(Alice, DiscoStanza3),
  434:             escalus:assert(is_error, [<<"cancel">>, <<"item-not-found">>],
  435:                            escalus:wait_for_stanza(Alice))
  436:         end).
  437: 
  438: rooms_in_rosters(Config) ->
  439:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  440:             AliceU = escalus_utils:jid_to_lower(escalus_client:username(Alice)),
  441:             AliceS = escalus_utils:jid_to_lower(escalus_client:server(Alice)),
  442:             escalus:send(Alice, escalus_stanza:roster_get()),
  443:             mongoose_helper:wait_until(
  444:                 fun() ->
  445:                     distributed_helper:rpc(
  446:                         distributed_helper:mim(),
  447:                         mod_roster,
  448:                         get_user_rosters_length,
  449:                         [host_type(), jid:make(AliceU, AliceS, <<>>)])
  450:                 end, 1, #{time_left => timer:seconds(10)}),
  451:             RosterResult = escalus:wait_for_stanza(Alice),
  452:             escalus_assert:is_roster_result(RosterResult),
  453: 
  454:             [Item] = exml_query:paths(
  455:                        RosterResult, [{element, <<"query">>}, {element, <<"item">>}]),
  456:             ProperJID = room_bin_jid(?ROOM),
  457:             ProperJID = exml_query:attr(Item, <<"jid">>),
  458:             ProperName = proplists:get_value(<<"roomname">>, default_config(default_schema())),
  459:             ProperName = exml_query:attr(Item, <<"name">>),
  460:             ProperVer = ver(1),
  461:             ProperVer = exml_query:path(Item, [{element, <<"version">>}, cdata])
  462:         end).
  463: 
  464: rooms_in_rosters_doesnt_break_disco_info(Config) ->
  465:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  466:             % Verify that room is in roster
  467:             escalus:send(Alice, escalus_stanza:roster_get()),
  468:             RosterResult = escalus:wait_for_stanza(Alice),
  469:             [RosterItem] = exml_query:paths(
  470:                        RosterResult, [{element, <<"query">>}, {element, <<"item">>}]),
  471:             RoomJID = room_bin_jid(?ROOM),
  472:             RoomJID = exml_query:attr(RosterItem, <<"jid">>),
  473: 
  474:             % Verify that disco#info doesn't crash when rooms are in roster
  475:             DiscoStanza = escalus_stanza:disco_info(escalus_client:short_jid(Alice)),
  476:             IQRes = escalus:send_iq_and_wait_for_result(Alice, DiscoStanza),
  477:             escalus:assert(is_iq_result, IQRes)
  478:         end).
  479: 
  480: no_roomname_in_schema_doesnt_break_disco_and_roster(Config) ->
  481:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  482:             [DiscoItem] = get_disco_rooms(Alice),
  483:             ?assert_equal_extra(?ROOM, exml_query:attr(DiscoItem, <<"name">>),
  484:                                 #{elem => DiscoItem}),
  485: 
  486:             escalus:send(Alice, escalus_stanza:roster_get()),
  487:             RosterResult = escalus:wait_for_stanza(Alice),
  488:             [RosterItem] = exml_query:paths(
  489:                        RosterResult, [{element, <<"query">>}, {element, <<"item">>}]),
  490:             ?ROOM = exml_query:attr(RosterItem, <<"name">>)
  491:         end).
  492: 
  493: unauthorized_stanza(Config) ->
  494:     escalus:story(Config, [{alice, 1}, {kate, 1}], fun(Alice, Kate) ->
  495:             MucHost = ?MUCHOST,
  496:             {ok, {?ROOM2, MucHost}} = create_room(?ROOM2, ?MUCHOST, kate, [], Config, ver(0)),
  497:             MsgStanza = escalus_stanza:groupchat_to(room_bin_jid(?ROOM2), <<"malicious">>),
  498:             escalus:send(Alice, MsgStanza),
  499:             escalus:assert(is_error, [<<"cancel">>, <<"item-not-found">>],
  500:                            escalus:wait_for_stanza(Alice)),
  501:             verify_no_stanzas([Alice, Kate])
  502:         end).
  503: 
  504: %% ---------------------- Occupant ----------------------
  505: 
  506: send_message(Config) ->
  507:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  508:             Msg = <<"Heyah!">>,
  509:             Id = <<"MyID">>,
  510:             Stanza = escalus_stanza:set_id(
  511:                        escalus_stanza:groupchat_to(room_bin_jid(?ROOM), Msg), Id),
  512:             foreach_occupant([Alice, Bob, Kate], Stanza, gc_message_verify_fun(?ROOM, Msg, Id))
  513:         end).
  514: 
  515: change_subject(Config) ->
  516:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  517:             ConfigChange = [{<<"subject">>, <<"new subject">>}],
  518:             Stanza = stanza_config_set(?ROOM, ConfigChange),
  519:             foreach_occupant([Alice, Bob, Kate], Stanza, config_msg_verify_fun(ConfigChange))
  520:         end).
  521: 
  522: change_roomname(Config) ->
  523:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  524:         %% change room name
  525:         ConfigChange = [{<<"roomname">>, <<"new_test_room">>}],
  526:         Stanza = stanza_config_set(?ROOM, ConfigChange),
  527:         escalus:send(Alice, Stanza),
  528:         escalus:wait_for_stanzas(Alice, 2),
  529:         StanzaCheck = stanza_config_get(?ROOM, ver(1)),
  530:         escalus:send(Alice, StanzaCheck),
  531:         Res = escalus:wait_for_stanza(Alice),
  532:         [_] = exml_query:paths(Res, [{element, <<"query">>}, {element, <<"roomname">>}])
  533:         end).
  534: 
  535: all_can_configure(Config) ->
  536:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  537:             ConfigChange = [{<<"roomname">>, <<"new subject">>}],
  538:             Stanza = stanza_config_set(?ROOM, ConfigChange),
  539:             foreach_occupant([Alice, Bob, Kate], Stanza, config_msg_verify_fun(ConfigChange))
  540:         end).
  541: 
  542: set_config_deny(Config) ->
  543:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  544:             ConfigChange = [{<<"roomname">>, <<"new subject">>}],
  545:             Stanza = stanza_config_set(?ROOM, ConfigChange),
  546:             escalus:send(Kate, Stanza),
  547:             escalus:assert(is_error, [<<"cancel">>, <<"not-allowed">>],
  548:                            escalus:wait_for_stanza(Kate)),
  549:             verify_no_stanzas([Alice, Bob, Kate])
  550:         end).
  551: 
  552: get_room_config(Config) ->
  553:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  554:             Stanza = stanza_config_get(?ROOM, <<"oldver">>),
  555:             ConfigKV = [{<<"version">>, ver(1)} | default_config(default_schema())],
  556:             foreach_occupant([Alice, Bob, Kate], Stanza, config_iq_verify_fun(ConfigKV)),
  557: 
  558:             %% Empty result when user has most recent version
  559:             escalus:send(Bob, stanza_config_get(?ROOM, ver(1))),
  560:             IQRes = escalus:wait_for_stanza(Bob),
  561:             escalus:assert(is_iq_result, IQRes),
  562:             undefined = exml_query:subelement(IQRes, <<"query">>)
  563:         end).
  564: 
  565: custom_default_config_works(Config) ->
  566:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  567:             Stanza = stanza_config_get(?ROOM, <<"oldver">>),
  568:             ConfigKV = [{<<"version">>, ver(1)} | default_config(custom_schema())],
  569:             foreach_occupant([Alice, Bob, Kate], Stanza, config_iq_verify_fun(ConfigKV))
  570:         end).
  571: 
  572: 
  573: get_room_occupants(Config) ->
  574:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  575:             AffUsers = [{Alice, owner}, {Bob, member}, {Kate, member}],
  576:             foreach_occupant([Alice, Bob, Kate], stanza_aff_get(?ROOM, <<"oldver">>),
  577:                              aff_iq_verify_fun(AffUsers, ver(1))),
  578: 
  579:             escalus:send(Bob, stanza_aff_get(?ROOM, ver(1))),
  580:             IQRes = escalus:wait_for_stanza(Bob),
  581:             escalus:assert(is_iq_result, IQRes),
  582:             undefined = exml_query:subelement(IQRes, <<"query">>)
  583:         end).
  584: 
  585: get_room_info(Config) ->
  586:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  587:             Stanza = stanza_info_get(?ROOM, <<"oldver">>),
  588:             ConfigKV = default_config(default_schema()),
  589:             foreach_occupant([Alice, Bob, Kate], Stanza,
  590:                              info_iq_verify_fun(?DEFAULT_AFF_USERS, ver(1), ConfigKV)),
  591: 
  592:             escalus:send(Bob, stanza_aff_get(?ROOM, ver(1))),
  593:             IQRes = escalus:wait_for_stanza(Bob),
  594:             escalus:assert(is_iq_result, IQRes),
  595:             undefined = exml_query:subelement(IQRes, <<"query">>)
  596:         end).
  597: 
  598: leave_room(Config) ->
  599:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  600:             % Users will leave one by one, owner last
  601:             lists:foldr(
  602:               fun(User, {Occupants, Outsiders}) ->
  603:                       NewOccupants = lists:keydelete(User, 1, Occupants),
  604:                       user_leave(?ROOM, User, NewOccupants),
  605:                       verify_no_stanzas(Outsiders),
  606:                       {NewOccupants, [User | Outsiders]}
  607:               end, {?DEFAULT_AFF_USERS, []}, [Alice, Bob, Kate]),
  608: 
  609:             % Now we verify that room is removed from DB
  610:             {error, not_exists} = rpc(mod_muc_light_db_backend, get_info, [host_type(), {?ROOM, ?MUCHOST}])
  611:         end).
  612: 
  613: change_other_aff_deny(Config) ->
  614:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}, {mike, 1}],
  615:                   fun(Alice, Bob, Kate, Mike) ->
  616:             AffUsersChanges1 = [{Bob, none}],
  617:             escalus:send(Kate, stanza_aff_set(?ROOM, AffUsersChanges1)),
  618:             escalus:assert(is_error, [<<"cancel">>, <<"not-allowed">>],
  619:                            escalus:wait_for_stanza(Kate)),
  620: 
  621:             AffUsersChanges2 = [{Alice, member}, {Kate, owner}],
  622:             escalus:send(Kate, stanza_aff_set(?ROOM, AffUsersChanges2)),
  623:             escalus:assert(is_error, [<<"cancel">>, <<"not-allowed">>],
  624:                            escalus:wait_for_stanza(Kate)),
  625: 
  626:             AffUsersChanges3 = [{Mike, member}],
  627:             escalus:send(Kate, stanza_aff_set(?ROOM, AffUsersChanges3)),
  628:             escalus:assert(is_error, [<<"cancel">>, <<"not-allowed">>],
  629:                            escalus:wait_for_stanza(Kate)),
  630: 
  631:             verify_no_stanzas([Alice, Bob, Kate, Mike])
  632:         end).
  633: 
  634: %% ---------------------- owner ----------------------
  635: 
  636: create_room(Config) ->
  637:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  638:             InitOccupants = [{Alice, member},
  639:                              {Kate, member}],
  640:             FinalOccupants = [{Bob, owner} | InitOccupants],
  641:             InitConfig = [{<<"roomname">>, <<"Bob's room">>}],
  642:             RoomNode = <<"bobroom">>,
  643:             escalus:send(Bob, stanza_create_room(RoomNode, InitConfig, InitOccupants)),
  644:             verify_aff_bcast(FinalOccupants, FinalOccupants),
  645:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob))
  646:         end).
  647: 
  648: create_room_unique(Config) ->
  649:     escalus:story(Config, [{bob, 1}], fun(Bob) ->
  650:             InitConfig = [{<<"roomname">>, <<"Bob's room">>}],
  651:             escalus:send(Bob, stanza_create_room(undefined, InitConfig, [])),
  652:             verify_aff_bcast([{Bob, owner}], [{Bob, owner}]),
  653:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob))
  654:         end).
  655: 
  656: create_room_with_equal_occupants(Config) ->
  657:     escalus:story(Config, [{bob, 1}], fun(Bob) ->
  658:             InitConfig = [{<<"roomname">>, <<"Bob's room">>}],
  659:             escalus:send(Bob, stanza_create_room(undefined, InitConfig, [])),
  660:             verify_aff_bcast([{Bob, member}], [{Bob, member}]),
  661:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob))
  662:         end).
  663: 
  664: create_existing_room_deny(Config) ->
  665:     escalus:story(Config, [{bob, 1}], fun(Bob) ->
  666:             escalus:send(Bob, stanza_create_room(?ROOM, [], [])),
  667:             escalus:assert(is_error, [<<"cancel">>, <<"conflict">>], escalus:wait_for_stanza(Bob))
  668:         end).
  669: 
  670: destroy_room(Config) ->
  671:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  672:             escalus:send(Alice, stanza_destroy_room(?ROOM)),
  673:             AffUsersChanges = [{Bob, none}, {Alice, none}, {Kate, none}],
  674:             verify_aff_bcast([], AffUsersChanges, [?NS_MUC_LIGHT_DESTROY]),
  675:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice))
  676:         end).
  677: 
  678: destroy_room_get_disco_items_empty(Config) ->
  679:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  680:         escalus:send(Alice, stanza_destroy_room(?ROOM)),
  681:         AffUsersChanges = [{Bob, none}, {Alice, none}, {Kate, none}],
  682:         verify_aff_bcast([], AffUsersChanges, [?NS_MUC_LIGHT_DESTROY]),
  683:         escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)),
  684:         % Send disco#items request
  685:         DiscoStanza = escalus_stanza:to(escalus_stanza:iq_get(?NS_DISCO_ITEMS, []), ?MUCHOST),
  686:         foreach_occupant([Alice, Bob, Kate], DiscoStanza, disco_items_verify_fun([]))
  687:      end).
  688: 
  689: destroy_room_get_disco_items_one_left(Config) ->
  690:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  691:         MucHost = ?MUCHOST,
  692:         {ok, {?ROOM2, MucHost}} = create_room(?ROOM2, ?MUCHOST, kate,
  693:                                               [bob, alice], Config, ver(0)),
  694:         ProperJID = room_bin_jid(?ROOM2),
  695:         %% alie destroy her room
  696:         escalus:send(Alice, stanza_destroy_room(?ROOM)),
  697:         AffUsersChanges = [{Bob, none}, {Alice, none}, {Kate, none}],
  698:         verify_aff_bcast([], AffUsersChanges, [?NS_MUC_LIGHT_DESTROY]),
  699:         escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)),
  700:         % Send disco#items request. Shoul be one room created by kate
  701:         DiscoStanza = escalus_stanza:to(escalus_stanza:iq_get(?NS_DISCO_ITEMS, []), ?MUCHOST),
  702:         foreach_occupant([Alice, Bob, Kate], DiscoStanza, disco_items_verify_fun([ProperJID]))
  703:      end).
  704: 
  705: set_config(Config) ->
  706:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  707:             ConfigChange = [{<<"roomname">>, <<"The Coven">>}],
  708:             escalus:send(Alice, stanza_config_set(?ROOM, ConfigChange)),
  709:             foreach_recipient([Alice, Bob, Kate], config_msg_verify_fun(ConfigChange)),
  710:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice))
  711:         end).
  712: 
  713: set_config_with_custom_schema(Config) ->
  714:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  715:             ConfigChange = [{<<"background">>, <<"builtin:unicorns">>},
  716:                             {<<"music">>, <<"builtin:rainbow">>}],
  717:             escalus:send(Alice, stanza_config_set(?ROOM, ConfigChange)),
  718:             foreach_recipient([Alice, Bob, Kate], config_msg_verify_fun(ConfigChange)),
  719:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice))
  720:         end).
  721: 
  722: deny_config_change_that_conflicts_with_schema(Config) ->
  723:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  724:             ConfigChange = [{<<"roomname">>, <<"The Coven">>}],
  725:             escalus:send(Alice, stanza_config_set(?ROOM, ConfigChange)),
  726:             escalus:assert(is_error, [<<"modify">>, <<"bad-request">>],
  727:                            escalus:wait_for_stanza(Alice))
  728:         end).
  729: 
  730: assorted_config_doesnt_lead_to_duplication(Config) ->
  731:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  732:             ConfigChange = [{<<"subject">>, <<"Elixirs">>},
  733:                             {<<"roomname">>, <<"The Coven">>},
  734:                             {<<"subject">>, <<"Elixirs">>}],
  735:             ConfigSetStanza = stanza_config_set(?ROOM, ConfigChange),
  736:             escalus:send(Alice, ConfigSetStanza),
  737:             foreach_recipient([Alice, Bob, Kate], config_msg_verify_fun(ConfigChange)),
  738:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)),
  739: 
  740:             Stanza = stanza_config_get(?ROOM, <<"oldver">>),
  741:             VerifyFun = fun(Incoming) ->
  742:                                 [Query] = exml_query:subelements(Incoming, <<"query">>),
  743:                                 Length = length(Query#xmlel.children),
  744:                                 Length = length(lists:ukeysort(#xmlel.name, Query#xmlel.children))
  745:                         end,
  746:             foreach_occupant([Alice, Bob, Kate], Stanza, VerifyFun)
  747:         end).
  748: 
  749: remove_and_add_users(Config) ->
  750:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  751:             AffUsersChanges1 = [{Bob, none}, {Kate, none}],
  752:             escalus:send(Alice, stanza_aff_set(?ROOM, AffUsersChanges1)),
  753:             verify_aff_bcast([{Alice, owner}], AffUsersChanges1),
  754:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)),
  755:             AffUsersChanges2 = [{Bob, member}, {Kate, member}],
  756:             escalus:send(Alice, stanza_aff_set(?ROOM, AffUsersChanges2)),
  757:             verify_aff_bcast([{Alice, owner}, {Bob, member}, {Kate, member}], AffUsersChanges2),
  758:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice))
  759:         end).
  760: 
  761: explicit_owner_change(Config) ->
  762:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  763:             AffUsersChanges1 = [{Bob, none}, {Alice, none}, {Kate, owner}],
  764:             escalus:send(Alice, stanza_aff_set(?ROOM, AffUsersChanges1)),
  765:             verify_aff_bcast([{Kate, owner}], AffUsersChanges1),
  766:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice))
  767:         end).
  768: 
  769: implicit_owner_change(Config) ->
  770:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  771:             AffUsersChanges1 = [{Bob, none}, {Alice, member}],
  772:             escalus:send(Alice, stanza_aff_set(?ROOM, AffUsersChanges1)),
  773:             verify_aff_bcast([{Kate, owner}, {Alice, member}], [{Kate, owner} | AffUsersChanges1]),
  774:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice))
  775:         end).
  776: 
  777: edge_case_owner_change(Config) ->
  778:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  779:             AffUsersChanges1 = [{Alice, member}, {Bob, none}, {Kate, none}],
  780:             escalus:send(Alice, stanza_aff_set(?ROOM, AffUsersChanges1)),
  781:             verify_aff_bcast([{Alice, owner}], [{Kate, none}, {Bob, none}]),
  782:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice))
  783:         end).
  784: 
  785: adding_wrongly_named_user_triggers_infinite_loop(Config)->
  786:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  787:             BuggyRoomName = <<"buggyroom">>,
  788:             Username = <<"buggyuser">>,
  789:             escalus:send(Alice, generate_buggy_aff_staza(BuggyRoomName, Username)),
  790:             timer:sleep(300),
  791:             AUsername = lbin(escalus_users:get_username(Config, alice)),
  792:             Host = lbin(escalus_users:get_server(Config, alice)),
  793:             Resource = <<"res1">>,
  794:             JID = mongoose_helper:make_jid(AUsername, Host, Resource),
  795:             ct:log("JID ~p", [JID]),
  796:             SessionRecPid = rpc(ejabberd_sm, get_session, [JID]),
  797:             {session, {_,Pid}, {AUsername, Host, Resource}, _, _, _} = SessionRecPid,
  798:             %% maybe throws exception
  799:             assert_process_memory_not_growing(Pid, 0, 2),
  800:             escalus:wait_for_stanzas(Alice, 2)
  801:     end).
  802: 
  803: %% ---------------------- limits ----------------------
  804: 
  805: rooms_per_user(Config) ->
  806:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}, {mike, 1}],
  807:                   fun(Alice, Bob, Kate, Mike) ->
  808:             escalus:send(Bob, stanza_create_room(undefined, [], [])),
  809:             escalus:assert(is_error, [<<"modify">>, <<"bad-request">>],
  810:                            escalus:wait_for_stanza(Bob)),
  811: 
  812:             escalus:send(Mike, stanza_create_room(<<"mikeroom">>, [], [])),
  813:             verify_aff_bcast([{Mike, owner}], [{Mike, owner}]),
  814:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Mike)),
  815:             KateAdd = [{Kate, member}],
  816:             escalus:send(Mike, stanza_aff_set(<<"mikeroom">>, KateAdd)),
  817:             %% Receives result and nothing happens, because Kate's limit is reached
  818:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Mike)),
  819: 
  820:             verify_no_stanzas([Alice, Bob, Kate, Mike])
  821:         end).
  822: 
  823: max_occupants(Config) ->
  824:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}, {mike, 1}],
  825:                   fun(Alice, Bob, Kate, Mike) ->
  826:             escalus:send(Bob, stanza_create_room(undefined, [], [{Alice, member}, {Kate, member}])),
  827:             escalus:assert(is_error, [<<"modify">>, <<"bad-request">>],
  828:                            escalus:wait_for_stanza(Bob)),
  829: 
  830:             MikeAdd = [{Mike, member}],
  831:             escalus:send(Alice, stanza_aff_set(?ROOM, MikeAdd)),
  832:             escalus:assert(is_error, [<<"modify">>, <<"bad-request">>],
  833:                            escalus:wait_for_stanza(Alice)),
  834: 
  835:             verify_no_stanzas([Alice, Bob, Kate, Mike])
  836:         end).
  837: 
  838: %% ---------------------- blocking ----------------------
  839: 
  840: manage_blocklist(Config) ->
  841:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  842:             escalus:send(Alice, stanza_blocking_get()),
  843:             GetResult1 = escalus:wait_for_stanza(Alice),
  844:             escalus:assert(is_iq_result, GetResult1),
  845:             QueryEl1 = exml_query:subelement(GetResult1, <<"query">>),
  846:             verify_blocklist(QueryEl1, []),
  847:             Domain = domain(),
  848:             BlocklistChange1 = [{user, deny, <<"user@", Domain/binary>>},
  849:                                 {room, deny, room_bin_jid(?ROOM)}],
  850:             escalus:send(Alice, stanza_blocking_set(BlocklistChange1)),
  851:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)),
  852:             escalus:send(Alice, stanza_blocking_get()),
  853:             GetResult2 = escalus:wait_for_stanza(Alice),
  854:             escalus:assert(is_iq_result, GetResult2),
  855:             QueryEl2 = exml_query:subelement(GetResult2, <<"query">>),
  856:             verify_blocklist(QueryEl2, BlocklistChange1),
  857: 
  858:             BlocklistChange2 = [{user, allow, <<"user@", Domain/binary>>},
  859:                                 {room, allow, room_bin_jid(?ROOM)}],
  860:             escalus:send(Alice, stanza_blocking_set(BlocklistChange2)),
  861:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)),
  862:             escalus:send(Alice, stanza_blocking_get()),
  863:             GetResult3 = escalus:wait_for_stanza(Alice),
  864:             escalus:assert(is_iq_result, GetResult3),
  865:             % Match below checks for empty list
  866:             QueryEl1 = exml_query:subelement(GetResult3, <<"query">>)
  867:         end).
  868: 
  869: block_room(Config) ->
  870:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  871:             BlocklistChange = [{room, deny, room_bin_jid(?ROOM)}],
  872:             escalus:send(Bob, stanza_blocking_set(BlocklistChange)),
  873:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob)),
  874:             user_leave(?ROOM, Bob, [{Alice, owner}, {Kate, member}]),
  875: 
  876:             % Alice tries to readd Bob to the room but fails
  877:             BobReadd = [{Bob, member}],
  878:             FailStanza = stanza_aff_set(?ROOM, BobReadd),
  879:             escalus:send(Alice, FailStanza),
  880:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)),
  881:             verify_no_stanzas([Alice, Bob, Kate]),
  882: 
  883:             % But Alice can add Bob to another room!
  884:             InitOccupants = [{Bob, member}],
  885:             escalus:send(Alice, stanza_create_room(<<"newroom">>, [], InitOccupants)),
  886:             verify_aff_bcast([{Alice, owner}, {Bob, member}],
  887:                              [{Alice, owner} | InitOccupants]),
  888:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice))
  889:         end).
  890: 
  891: block_user(Config) ->
  892:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  893:             AliceJIDBin = lbin(escalus_client:short_jid(Alice)),
  894:             BlocklistChange = [{user, deny, AliceJIDBin}],
  895:             escalus:send(Bob, stanza_blocking_set(BlocklistChange)),
  896:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob)),
  897:             user_leave(?ROOM, Bob, [{Alice, owner}, {Kate, member}]),
  898: 
  899:             % Alice tries to create new room with Bob but Bob is not added
  900:             escalus:send(Alice, stanza_create_room(<<"new">>, [], [{Bob, member}])),
  901:             verify_aff_bcast([{Alice, owner}], [{Alice, owner}]),
  902:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)),
  903:             verify_no_stanzas([Alice, Bob, Kate]),
  904: 
  905:             % But Kate can add Bob to the main room!
  906:             BobReadd = [{Bob, member}],
  907:             SuccessStanza = stanza_aff_set(?ROOM, BobReadd),
  908:             escalus:send(Kate, SuccessStanza),
  909:             verify_aff_bcast([{Alice, owner}, {Bob, member}, {Kate, member}], BobReadd),
  910:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Kate)),
  911:             verify_no_stanzas([Alice, Bob, Kate])
  912: 
  913:         end).
  914: 
  915: blocking_disabled(Config) ->
  916:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  917:             escalus:send(Alice, stanza_blocking_get()),
  918:             escalus:assert(is_error, [<<"modify">>, <<"bad-request">>],
  919:                            escalus:wait_for_stanza(Alice)),
  920:             Domain = domain(),
  921:             BlocklistChange1 = [{user, deny, <<"user@", Domain/binary>>},
  922:                                 {room, deny, room_bin_jid(?ROOM)}],
  923:             escalus:send(Alice, stanza_blocking_set(BlocklistChange1)),
  924:             escalus:assert(is_error, [<<"modify">>, <<"bad-request">>],
  925:                            escalus:wait_for_stanza(Alice))
  926:         end).
  927: 
  928: %%--------------------------------------------------------------------
  929: %% Subroutines
  930: %%--------------------------------------------------------------------
  931: 
  932: -spec get_disco_rooms(User :: escalus:client()) -> list(xmlel()).
  933: get_disco_rooms(User) ->
  934:     DiscoStanza = escalus_stanza:to(escalus_stanza:iq_get(?NS_DISCO_ITEMS, []), ?MUCHOST),
  935:     escalus:send(User, DiscoStanza),
  936:     Stanza =  escalus:wait_for_stanza(User),
  937:     XNamespaces = exml_query:paths(Stanza, [{element, <<"query">>}, {attr, <<"xmlns">>}]),
  938:     true = lists:member(?NS_DISCO_ITEMS, XNamespaces),
  939:     escalus:assert(is_stanza_from, [?MUCHOST], Stanza),
  940:     exml_query:paths(Stanza, [{element, <<"query">>}, {element, <<"item">>}]).
  941: 
  942: -spec generate_buggy_aff_staza(RoomName :: binary(), Username :: binary()) -> xmlel().
  943: generate_buggy_aff_staza(RoomName, Username) ->
  944:     BuggyJid = <<Username/binary, "@muclight.localhost">>,
  945:     BuggyUser = #client{jid = BuggyJid},
  946:     stanza_create_room(RoomName, [], [{BuggyUser, member}]).
  947: 
  948: %%--------------------------------------------------------------------
  949: %% IQ getters
  950: %%--------------------------------------------------------------------
  951: 
  952: -spec stanza_blocking_get() -> xmlel().
  953: stanza_blocking_get() ->
  954:     escalus_stanza:to(escalus_stanza:iq_get(?NS_MUC_LIGHT_BLOCKING, []), ?MUCHOST).
  955: 
  956: -spec stanza_config_get(Room :: binary(), Ver :: binary()) -> xmlel().
  957: stanza_config_get(Room, Ver) ->
  958:     escalus_stanza:to(
  959:       escalus_stanza:iq_get(?NS_MUC_LIGHT_CONFIGURATION, [version_el(Ver)]), room_bin_jid(Room)).
  960: 
  961: -spec stanza_info_get(Room :: binary(), Ver :: binary()) -> xmlel().
  962: stanza_info_get(Room, Ver) ->
  963:     escalus_stanza:to(
  964:       escalus_stanza:iq_get(?NS_MUC_LIGHT_INFO, [version_el(Ver)]), room_bin_jid(Room)).
  965: 
  966: -spec stanza_aff_get(Room :: binary(), Ver :: binary()) -> xmlel().
  967: stanza_aff_get(Room, Ver) ->
  968:     escalus_stanza:to(
  969:       escalus_stanza:iq_get(?NS_MUC_LIGHT_AFFILIATIONS, [version_el(Ver)]), room_bin_jid(Room)).
  970: 
  971: %%--------------------------------------------------------------------
  972: %% IQ setters
  973: %%--------------------------------------------------------------------
  974: 
  975: -spec stanza_destroy_room(Room :: binary()) -> xmlel().
  976: stanza_destroy_room(Room) ->
  977:     escalus_stanza:to(escalus_stanza:iq_set(?NS_MUC_LIGHT_DESTROY, []), room_bin_jid(Room)).
  978: 
  979: -spec stanza_config_set(Room :: binary(), ConfigChanges :: [muc_light_helper:config_item()]) -> xmlel().
  980: stanza_config_set(Room, ConfigChanges) ->
  981:     Items = [ kv_el(Key, Value) || {Key, Value} <- ConfigChanges],
  982:     escalus_stanza:to(
  983:       escalus_stanza:iq_set(?NS_MUC_LIGHT_CONFIGURATION, Items), room_bin_jid(Room)).
  984: 
  985: %%--------------------------------------------------------------------
  986: %% Verifiers
  987: %%--------------------------------------------------------------------
  988: 
  989: -spec verify_blocklist(Query :: xmlel(), ProperBlocklist :: [ct_block_item()]) -> [].
  990: verify_blocklist(Query, ProperBlocklist) ->
  991:     ?NS_MUC_LIGHT_BLOCKING = exml_query:attr(Query, <<"xmlns">>),
  992:     BlockedRooms = exml_query:subelements(Query, <<"room">>),
  993:     BlockedUsers = exml_query:subelements(Query, <<"user">>),
  994:     BlockedItems = [{list_to_atom(binary_to_list(What)), list_to_atom(binary_to_list(Action)), Who}
  995:                     || #xmlel{name = What, attrs = [{<<"action">>, Action}],
  996:                               children = [#xmlcdata{ content = Who }]}
  997:                        <- BlockedRooms ++ BlockedUsers],
  998:     ProperBlocklistLen = length(ProperBlocklist),
  999:     ProperBlocklistLen = length(BlockedItems),
 1000:     [] = lists:foldl(fun lists:delete/2, BlockedItems, ProperBlocklist).
 1001: 
 1002: -spec disco_items_verify_fun(list(Jid :: binary())) -> verify_fun().
 1003: disco_items_verify_fun(JidList) ->
 1004:     fun(Incomming) ->
 1005:         ResultItemList = exml_query:paths(Incomming,
 1006:                                           [{element, <<"query">>},
 1007:                                            {element, <<"item">>}]),
 1008:         ResultJids = [exml_query:attr(ResultItem, <<"jid">>) || ResultItem <- ResultItemList],
 1009:         {SortedResult, SortedExptected} = {lists:sort(JidList), lists:sort(ResultJids)},
 1010:         SortedResult = SortedExptected
 1011:     end.
 1012: 
 1013: 
 1014: -spec verify_no_stanzas(Users :: [escalus:client()]) -> ok.
 1015: verify_no_stanzas(Users) ->
 1016:     lists:foreach(
 1017:       fun(User) ->
 1018:               {false, _} = {escalus_client:has_stanzas(User), User}
 1019:       end, Users).
 1020: 
 1021: -spec verify_config(ConfigRoot :: xmlel(), Config :: [muc_light_helper:config_item()]) -> ok.
 1022: verify_config(ConfigRoot, Config) ->
 1023:     lists:foreach(
 1024:       fun({Key, Val}) ->
 1025:               Val = exml_query:path(ConfigRoot, [{element, Key}, cdata])
 1026:       end, Config).
 1027: 
 1028: %%--------------------------------------------------------------------
 1029: %% Verification funs generators
 1030: %%--------------------------------------------------------------------
 1031: 
 1032: -spec config_msg_verify_fun(RoomConfig :: [muc_light_helper:config_item()]) -> verify_fun().
 1033: config_msg_verify_fun(RoomConfig) ->
 1034:     fun(Incoming) ->
 1035:             escalus:assert(is_groupchat_message, Incoming),
 1036:             [X] = exml_query:subelements(Incoming, <<"x">>),
 1037:             ?NS_MUC_LIGHT_CONFIGURATION = exml_query:attr(X, <<"xmlns">>),
 1038:             PrevVersion = exml_query:path(X, [{element, <<"prev-version">>}, cdata]),
 1039:             Version = exml_query:path(X, [{element, <<"version">>}, cdata]),
 1040:             true = is_binary(Version),
 1041:             true = is_binary(PrevVersion),
 1042:             true = Version =/= PrevVersion,
 1043:             lists:foreach(
 1044:               fun({Key, Val}) ->
 1045:                       Val = exml_query:path(X, [{element, Key}, cdata])
 1046:               end, RoomConfig)
 1047:     end.
 1048: 
 1049: -spec config_iq_verify_fun(RoomConfig :: [muc_light_helper:config_item()]) -> verify_fun().
 1050: config_iq_verify_fun(RoomConfig) ->
 1051:     fun(Incoming) ->
 1052:             [Query] = exml_query:subelements(Incoming, <<"query">>),
 1053:             ?NS_MUC_LIGHT_CONFIGURATION = exml_query:attr(Query, <<"xmlns">>),
 1054:             verify_config(Query, RoomConfig)
 1055:     end.
 1056: 
 1057: -spec aff_iq_verify_fun(AffUsers :: ct_aff_users(), Version :: binary()) -> verify_fun().
 1058: aff_iq_verify_fun(AffUsers, Version) ->
 1059:     BinAffUsers = bin_aff_users(AffUsers),
 1060:     fun(Incoming) ->
 1061:             [Query] = exml_query:subelements(Incoming, <<"query">>),
 1062:             ?NS_MUC_LIGHT_AFFILIATIONS = exml_query:attr(Query, <<"xmlns">>),
 1063:             Version = exml_query:path(Query, [{element, <<"version">>}, cdata]),
 1064:             Items = exml_query:subelements(Query, <<"user">>),
 1065:             verify_aff_users(Items, BinAffUsers)
 1066:     end.
 1067: 
 1068: -spec info_iq_verify_fun(AffUsers :: ct_aff_users(), Version :: binary(),
 1069:                          ConfigKV :: [muc_light_helper:config_item()]) -> verify_fun().
 1070: info_iq_verify_fun(AffUsers, Version, ConfigKV) ->
 1071:     BinAffUsers = bin_aff_users(AffUsers),
 1072:     fun(Incoming) ->
 1073:             [Query] = exml_query:subelements(Incoming, <<"query">>),
 1074:             ?NS_MUC_LIGHT_INFO = exml_query:attr(Query, <<"xmlns">>),
 1075:             Version = exml_query:path(Query, [{element, <<"version">>}, cdata]),
 1076:             UsersItems = exml_query:paths(Query, [{element, <<"occupants">>},
 1077:                                           {element, <<"user">>}]),
 1078:             verify_aff_users(UsersItems, BinAffUsers),
 1079:             ConfigurationEl = exml_query:subelement(Query, <<"configuration">>),
 1080:             verify_config(ConfigurationEl, ConfigKV)
 1081:     end.
 1082: 
 1083: -spec verify_user_has_one_room(User :: escalus:client()) -> any().
 1084: verify_user_has_one_room(User) ->
 1085:         [Item] =  get_disco_rooms(User),
 1086:         ProperJID = room_bin_jid(?ROOM),
 1087:         ProperJID = exml_query:attr(Item, <<"jid">>).
 1088: 
 1089: %%--------------------------------------------------------------------
 1090: %% Other helpers
 1091: %%--------------------------------------------------------------------
 1092: 
 1093: -spec ver(Int :: integer()) -> binary().
 1094: ver(Int) ->
 1095:     <<"ver-", (list_to_binary(integer_to_list(Int)))/binary>>.
 1096: 
 1097: -spec version_el(Version :: binary()) -> xmlel().
 1098: version_el(Version) ->
 1099:     #xmlel{ name = <<"version">>, children = [#xmlcdata{ content = Version }] }.
 1100: 
 1101: -spec assert_process_memory_not_growing(pid(), integer(), integer()) -> any().
 1102: assert_process_memory_not_growing(_, _, Counter) when Counter > 4 ->
 1103:     throw({memory_consumption_is_growing});
 1104: assert_process_memory_not_growing(_, _, Counter) when Counter == 0 ->
 1105:     ok;
 1106: assert_process_memory_not_growing(Pid, OldMemory, Counter) ->
 1107:     {memory, Memory} = rpc(erlang, process_info, [Pid, memory]),
 1108:     timer:sleep(1000),
 1109:     NewCounter = case Memory =< OldMemory of
 1110:                    true ->
 1111:                      Counter - 1;
 1112:                    _ ->
 1113:                      Counter + 1
 1114:                  end,
 1115:   assert_process_memory_not_growing(Pid, Memory, NewCounter).
 1116: 
 1117: -spec custom_schema() -> [muc_light_helper:schema_item()].
 1118: custom_schema() ->
 1119:     % This list needs to be sorted
 1120:     [{<<"background">>, <<"builtin:hell">>, background, binary},
 1121:      {<<"music">>, <<"builtin:screams">>, music, binary}].