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