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