1: -module(muc_light_legacy_SUITE).
    2: 
    3: -include_lib("escalus/include/escalus.hrl").
    4: -include_lib("escalus/include/escalus_xmlns.hrl").
    5: -include_lib("common_test/include/ct.hrl").
    6: -include_lib("exml/include/exml.hrl").
    7: 
    8: -export([
    9:          disco_service/1,
   10:          disco_features/1,
   11:          disco_features_with_mam/1,
   12:          disco_info/1,
   13:          disco_info_with_mam/1,
   14:          disco_rooms/1,
   15:          disco_rooms_rsm/1,
   16:          unauthorized_stanza/1
   17:         ]).
   18: -export([
   19:          send_message/1,
   20:          change_subject/1,
   21:          all_can_configure/1,
   22:          set_config_deny/1,
   23:          get_room_config/1,
   24:          get_room_occupants/1,
   25:          leave_room/1,
   26:          change_other_aff_deny/1
   27:         ]).
   28: -export([
   29:          create_room/1,
   30:          create_room_with_equal_occupants/1,
   31:          create_existing_room_deny/1,
   32:          destroy_room/1,
   33:          set_config/1,
   34:          assorted_config_doesnt_lead_to_duplication/1,
   35:          remove_and_add_users/1,
   36:          explicit_owner_change/1,
   37:          implicit_owner_change/1,
   38:          edge_case_owner_change/1
   39:         ]).
   40: -export([
   41:          manage_blocklist/1,
   42:          block_room/1,
   43:          block_user/1,
   44:          blocking_disabled/1
   45:         ]).
   46: 
   47: -export([all/0, groups/0, suite/0,
   48:          init_per_suite/1, end_per_suite/1,
   49:          init_per_group/2, end_per_group/2,
   50:          init_per_testcase/2, end_per_testcase/2]).
   51: 
   52: -import(escalus_ejabberd, [rpc/3]).
   53: -import(muc_helper, [foreach_occupant/3, foreach_recipient/2]).
   54: -import(distributed_helper, [subhost_pattern/1]).
   55: -import(domain_helper, [host_type/0, domain/0]).
   56: -import(muc_light_helper, [
   57:                            bin_aff_users/1,
   58:                            to_lus/2,
   59:                            lbin/1,
   60:                            create_room/6,
   61:                            default_config/1,
   62:                            default_schema/0
   63:                           ]).
   64: 
   65: -define(ROOM, <<"testroom">>).
   66: -define(ROOM2, <<"testroom2">>).
   67: 
   68: -define(NS_MUC_LIGHT, <<"urn:xmpp:muclight:0">>).
   69: -define(NS_MUC_ROOMCONFIG, <<"http://jabber.org/protocol/muc#roomconfig">>).
   70: 
   71: -define(MUCHOST, (muc_helper:muc_host())).
   72: 
   73: -type ct_aff_user() :: {EscalusClient :: escalus:client(), Aff :: atom()}.
   74: -type ct_aff_users() :: [ct_aff_user()].
   75: -type ct_block_item() :: {What :: atom(), Action :: atom(), Who :: binary()}.
   76: -type verify_fun() :: muc_helper:verify_fun().
   77: -type xmlel() :: exml:element().
   78: 
   79: -define(DEFAULT_AFF_USERS, [{Alice, owner}, {Bob, member}, {Kate, member}]).
   80: 
   81: %%--------------------------------------------------------------------
   82: %% Suite configuration
   83: %%--------------------------------------------------------------------
   84: 
   85: all() ->
   86:     [
   87:      {group, entity},
   88:      {group, occupant},
   89:      {group, owner},
   90:      {group, blocking}
   91:     ].
   92: 
   93: groups() ->
   94:     G = [
   95:          {entity, [sequence], [
   96:                                disco_service,
   97:                                disco_features,
   98:                                disco_features_with_mam,
   99:                                disco_info,
  100:                                disco_info_with_mam,
  101:                                disco_rooms,
  102:                                disco_rooms_rsm,
  103:                                unauthorized_stanza
  104:                               ]},
  105:          {occupant, [sequence], [
  106:                                  send_message,
  107:                                  change_subject,
  108:                                  all_can_configure,
  109:                                  set_config_deny,
  110:                                  get_room_config,
  111:                                  get_room_occupants,
  112:                                  leave_room,
  113:                                  change_other_aff_deny
  114:                                 ]},
  115:          {owner, [sequence], [
  116:                               create_room,
  117:                               create_room_with_equal_occupants,
  118:                               create_existing_room_deny,
  119:                               destroy_room,
  120:                               set_config,
  121:                               assorted_config_doesnt_lead_to_duplication,
  122:                               remove_and_add_users,
  123:                               explicit_owner_change,
  124:                               implicit_owner_change,
  125:                               edge_case_owner_change
  126:                              ]},
  127:          {blocking, [sequence], [
  128:                                  manage_blocklist,
  129:                                  block_room,
  130:                                  block_user,
  131:                                  blocking_disabled
  132:                                 ]}
  133:         ],
  134:     ct_helper:repeat_all_until_all_ok(G).
  135: 
  136: suite() ->
  137:     escalus:suite().
  138: 
  139: %%--------------------------------------------------------------------
  140: %% Init & teardown
  141: %%--------------------------------------------------------------------
  142: 
  143: init_per_suite(Config) ->
  144:     Config1 = dynamic_modules:save_modules(host_type(), Config),
  145:     Config2 = escalus:init_per_suite(Config1),
  146:     escalus:create_users(Config2, escalus:get_users([alice, bob, kate, mike])).
  147: 
  148: end_per_suite(Config) ->
  149:     clear_db(),
  150:     Config1 = escalus:delete_users(Config, escalus:get_users([alice, bob, kate, mike])),
  151:     dynamic_modules:restore_modules(Config),
  152:     escalus:end_per_suite(Config1).
  153: 
  154: init_per_group(_GroupName, Config) ->
  155:     Config.
  156: 
  157: end_per_group(_GroupName, Config) ->
  158:     Config.
  159: 
  160: init_per_testcase(CaseName, Config) when CaseName =:= disco_rooms_rsm;
  161:                                          CaseName =:= block_room;
  162:                                          CaseName =:= block_user ->
  163:     dynamic_modules:ensure_modules(host_type(), required_modules(CaseName)),
  164:     create_room(?ROOM, ?MUCHOST, alice, [bob, kate], Config),
  165:     create_room(?ROOM2, ?MUCHOST, alice, [kate], Config),
  166:     escalus:init_per_testcase(CaseName, Config);
  167: init_per_testcase(create_existing_room_deny = CaseName, Config) ->
  168:     dynamic_modules:ensure_modules(host_type(), required_modules(CaseName)),
  169:     create_room(?ROOM, ?MUCHOST, alice, [], Config),
  170:     escalus:init_per_testcase(CaseName, Config);
  171: init_per_testcase(CaseName, Config) when CaseName =:= disco_features_with_mam;
  172:                                          CaseName =:= disco_info_with_mam ->
  173:     case mam_helper:backend() of
  174:         disabled ->
  175:             {skip, "No MAM backend available"};
  176:         Backend ->
  177:             dynamic_modules:ensure_modules(host_type(), required_modules(CaseName, Backend)),
  178:             create_room(?ROOM, ?MUCHOST, alice, [bob, kate], Config),
  179:             escalus:init_per_testcase(CaseName, Config)
  180:     end;
  181: init_per_testcase(CaseName, Config) ->
  182:     dynamic_modules:ensure_modules(host_type(), required_modules(CaseName)),
  183:     create_room(?ROOM, ?MUCHOST, alice, [bob, kate], Config),
  184:     escalus:init_per_testcase(CaseName, Config).
  185: 
  186: end_per_testcase(CaseName, Config) ->
  187:     clear_db(),
  188:     escalus:end_per_testcase(CaseName, Config).
  189: 
  190: %% Module configuration per test case
  191: 
  192: required_modules(CaseName, MAMBackend) ->
  193:     [{mod_mam_meta, mam_helper:config_opts(#{backend => MAMBackend,
  194:                                              muc => #{host => subhost_pattern(?MUCHOST)}})} |
  195:      common_required_modules(CaseName)].
  196: 
  197: required_modules(CaseName) ->
  198:     [{mod_mam_meta, stopped} | common_required_modules(CaseName)].
  199: 
  200: common_required_modules(CaseName) ->
  201:     [{mod_muc_light, common_muc_light_opts() ++ muc_light_opts(CaseName)}].
  202: 
  203: muc_light_opts(disco_rooms_rsm) ->
  204:     [{rooms_per_page, 1}];
  205: muc_light_opts(all_can_configure) ->
  206:     [{all_can_configure, true}];
  207: muc_light_opts(create_room_with_equal_occupants) ->
  208:     [{equal_occupants, true}];
  209: muc_light_opts(block_user) ->
  210:     [{all_can_invite, true}];
  211: muc_light_opts(blocking_disabled) ->
  212:     [{blocking, false}];
  213: muc_light_opts(_) ->
  214:     [].
  215: 
  216: common_muc_light_opts() ->
  217:     [{host, subhost_pattern(muc_helper:muc_host_pattern())},
  218:      {backend, mongoose_helper:mnesia_or_rdbms_backend()},
  219:      {legacy_mode, true}].
  220: 
  221: %% ---------------------- Helpers ----------------------
  222: 
  223: create_room(RoomU, MUCHost, Owner, Members, Config) ->
  224:     create_room(RoomU, MUCHost, Owner, Members, Config, <<"-">>).
  225: 
  226: clear_db() ->
  227:     muc_light_helper:clear_db(host_type()).
  228: 
  229: %%--------------------------------------------------------------------
  230: %% MUC light tests
  231: %%--------------------------------------------------------------------
  232: 
  233: %% ---------------------- Disco ----------------------
  234: 
  235: disco_service(Config) ->
  236:     muc_helper:disco_service_story(Config).
  237: 
  238: disco_features(Config) ->
  239:     muc_helper:disco_features_story(Config, [?NS_MUC]).
  240: 
  241: disco_features_with_mam(Config) ->
  242:     muc_helper:disco_features_story(Config, [?NS_MUC] ++ mam_helper:namespaces()).
  243: 
  244: disco_info(Config) ->
  245:     muc_helper:disco_info_story(Config, [?NS_MUC]).
  246: 
  247: disco_info_with_mam(Config) ->
  248:     muc_helper:disco_info_story(Config, [?NS_MUC] ++ mam_helper:namespaces()).
  249: 
  250: disco_rooms(Config) ->
  251:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  252:             {ok, {?ROOM2, _}} = create_room(?ROOM2, ?MUCHOST, kate, [], Config),
  253:             DiscoStanza = escalus_stanza:to(escalus_stanza:iq_get(?NS_DISCO_ITEMS, []), ?MUCHOST),
  254:             escalus:send(Alice, DiscoStanza),
  255:             %% we should get 1 room, Alice is not in the second one
  256:             Stanza = escalus:wait_for_stanza(Alice),
  257:             [Item] = exml_query:paths(Stanza, [{element, <<"query">>}, {element, <<"item">>}]),
  258:             ProperJID = room_bin_jid(?ROOM),
  259:             ProperJID = exml_query:attr(Item, <<"jid">>),
  260:             escalus:assert(is_stanza_from, [?MUCHOST], Stanza)
  261:         end).
  262: 
  263: disco_rooms_rsm(Config) ->
  264:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  265:             DiscoStanza = escalus_stanza:to(escalus_stanza:iq_get(?NS_DISCO_ITEMS, []), ?MUCHOST),
  266:             escalus:send(Alice, DiscoStanza),
  267:             %% we should get 1 room with RSM info
  268:             Stanza = escalus:wait_for_stanza(Alice),
  269:             [Item] = exml_query:paths(Stanza, [{element, <<"query">>}, {element, <<"item">>}]),
  270:             ProperJID = room_bin_jid(?ROOM),
  271:             ProperJID = exml_query:attr(Item, <<"jid">>),
  272: 
  273:             RSM = #xmlel{ name = <<"set">>,
  274:                           attrs = [{<<"xmlns">>, ?NS_RSM}],
  275:                           children = [ #xmlel{ name = <<"max">>,
  276:                                                children = [#xmlcdata{ content = <<"10">> }] },
  277:                                        #xmlel{ name = <<"before">> } ]  },
  278:             DiscoStanza2 = escalus_stanza:to(
  279:                              escalus_stanza:iq_get(?NS_DISCO_ITEMS, [RSM]), ?MUCHOST),
  280:             escalus:send(Alice, DiscoStanza2),
  281:             %% we should get second room
  282:             Stanza2 = escalus:wait_for_stanza(Alice),
  283:             [Item2] = exml_query:paths(Stanza2, [{element, <<"query">>}, {element, <<"item">>}]),
  284:             ProperJID2 = room_bin_jid(?ROOM2),
  285:             ProperJID2 = exml_query:attr(Item2, <<"jid">>),
  286: 
  287:             BadAfter = #xmlel{ name = <<"after">>,
  288:                                children = [#xmlcdata{ content = <<"oops@", (?MUCHOST)/binary>> }] },
  289:             RSM2 = #xmlel{ name = <<"set">>,
  290:                           attrs = [{<<"xmlns">>, ?NS_RSM}],
  291:                           children = [ #xmlel{ name = <<"max">>,
  292:                                                children = [#xmlcdata{ content = <<"10">> }] },
  293:                                        BadAfter ]  },
  294:             DiscoStanza3 = escalus_stanza:to(
  295:                              escalus_stanza:iq_get(?NS_DISCO_ITEMS, [RSM2]), ?MUCHOST),
  296:             escalus:send(Alice, DiscoStanza3),
  297:             escalus:assert(is_error, [<<"cancel">>, <<"item-not-found">>],
  298:                            escalus:wait_for_stanza(Alice))
  299:         end).
  300: 
  301: unauthorized_stanza(Config) ->
  302:     escalus:story(Config, [{alice, 1}, {kate, 1}], fun(Alice, Kate) ->
  303:             {ok, {?ROOM2, _}} = create_room(?ROOM2, ?MUCHOST, kate, [], Config),
  304:             MsgStanza = escalus_stanza:groupchat_to(room_bin_jid(?ROOM2), <<"malicious">>),
  305:             escalus:send(Alice, MsgStanza),
  306:             escalus:assert(is_error, [<<"cancel">>, <<"item-not-found">>],
  307:                            escalus:wait_for_stanza(Alice)),
  308:             verify_no_stanzas([Alice, Kate])
  309:         end).
  310: 
  311: %% ---------------------- Occupant ----------------------
  312: 
  313: send_message(Config) ->
  314:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  315:             Msg = <<"Heyah!">>,
  316:             Id = <<"LegacyId">>,
  317:             Stanza = escalus_stanza:set_id(
  318:                        escalus_stanza:groupchat_to(room_bin_jid(?ROOM), Msg), Id),
  319:             foreach_occupant([Alice, Bob, Kate], Stanza, gc_message_verify_fun(?ROOM, Msg, Id))
  320:         end).
  321: 
  322: change_subject(Config) ->
  323:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  324:             Subject = <<"new subject">>,
  325:             SubjectStanza = #xmlel{name = <<"message">>,
  326:                                    attrs = [{<<"type">>, <<"groupchat">>}],
  327:                                    children = [#xmlel{
  328:                                                   name = <<"subject">>,
  329:                                                   children = [#xmlcdata{content = Subject}]
  330:                                                  }]
  331:                                   },
  332:             foreach_occupant([Alice, Bob, Kate],
  333:                              escalus_stanza:to(SubjectStanza, room_bin_jid(?ROOM)),
  334:                              subject_message_verify_fun(?ROOM, Subject))
  335:         end).
  336: 
  337: all_can_configure(Config) ->
  338:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  339:             ConfigChange = [{<<"roomname">>, <<"new subject">>}],
  340:             Stanza = stanza_config_set(?ROOM, ConfigChange),
  341:             foreach_occupant([Alice, Bob, Kate], Stanza, config_msg_verify_fun())
  342:         end).
  343: 
  344: set_config_deny(Config) ->
  345:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  346:             ConfigChange = [{<<"roomname">>, <<"new subject">>}],
  347:             Stanza = stanza_config_set(?ROOM, ConfigChange),
  348:             escalus:send(Kate, Stanza),
  349:             escalus:assert(is_error, [<<"cancel">>, <<"not-allowed">>],
  350:                            escalus:wait_for_stanza(Kate)),
  351:             verify_no_stanzas([Alice, Bob, Kate])
  352:         end).
  353: 
  354: get_room_config(Config) ->
  355:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  356:             Stanza = stanza_config_get(?ROOM),
  357:             ConfigKV = default_config(default_schema()),
  358:             foreach_occupant([Alice, Bob, Kate], Stanza, config_iq_verify_fun(ConfigKV))
  359:         end).
  360: 
  361: get_room_occupants(Config) ->
  362:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  363:             AffUsers = [{Alice, owner}, {Bob, member}, {Kate, member}],
  364:             foreach_occupant([Alice, Bob, Kate], stanza_aff_get(?ROOM), aff_iq_verify_fun(AffUsers))
  365:         end).
  366: 
  367: leave_room(Config) ->
  368:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  369:             % Users will leave one by one, owner last
  370:             lists:foldr(
  371:               fun(User, {Occupants, Outsiders}) ->
  372:                       NewOccupants = lists:keydelete(User, 1, Occupants),
  373:                       user_leave(User, [ U || {U, _} <- NewOccupants]),
  374:                       verify_no_stanzas(Outsiders),
  375:                       {NewOccupants, [User | Outsiders]}
  376:               end, {?DEFAULT_AFF_USERS, []}, [Alice, Bob, Kate])
  377:         end).
  378: 
  379: change_other_aff_deny(Config) ->
  380:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}, {mike, 1}],
  381:                   fun(Alice, Bob, Kate, Mike) ->
  382:             AffUsersChanges1 = [{Bob, none}],
  383:             escalus:send(Kate, stanza_aff_set(?ROOM, AffUsersChanges1)),
  384:             escalus:assert(is_error, [<<"cancel">>, <<"not-allowed">>],
  385:                            escalus:wait_for_stanza(Kate)),
  386: 
  387:             AffUsersChanges2 = [{Alice, member}, {Kate, owner}],
  388:             escalus:send(Kate, stanza_aff_set(?ROOM, AffUsersChanges2)),
  389:             escalus:assert(is_error, [<<"cancel">>, <<"not-allowed">>],
  390:                            escalus:wait_for_stanza(Kate)),
  391: 
  392:             AffUsersChanges3 = [{Mike, member}],
  393:             escalus:send(Kate, stanza_aff_set(?ROOM, AffUsersChanges3)),
  394:             escalus:assert(is_error, [<<"cancel">>, <<"not-allowed">>],
  395:                            escalus:wait_for_stanza(Kate)),
  396: 
  397:             verify_no_stanzas([Alice, Bob, Kate, Mike])
  398:         end).
  399: 
  400: %% ---------------------- owner ----------------------
  401: 
  402: create_room(Config) ->
  403:     escalus:story(Config, [{bob, 1}], fun(Bob) ->
  404:             escalus:send(Bob, stanza_create_room(<<"bobroom">>, Bob)),
  405:             Result = escalus:wait_for_stanza(Bob),
  406:             presence_verify(Bob, owner, Result)
  407:         end).
  408: 
  409: create_room_with_equal_occupants(Config) ->
  410:     escalus:story(Config, [{bob, 1}], fun(Bob) ->
  411:             escalus:send(Bob, stanza_create_room(<<"bobroom">>, Bob)),
  412:             Result = escalus:wait_for_stanza(Bob),
  413:             presence_verify(Bob, member, Result)
  414:         end).
  415: 
  416: create_existing_room_deny(Config) ->
  417:     escalus:story(Config, [{bob, 1}], fun(Bob) ->
  418:             escalus:send(Bob, stanza_create_room(?ROOM, Bob)),
  419:             Result = escalus:wait_for_stanza(Bob),
  420:             escalus:assert(is_presence_with_type, [<<"error">>], Result),
  421:             escalus:assert(is_error, [<<"auth">>, <<"registration-required">>], Result),
  422:             X = exml_query:subelement(Result, <<"x">>),
  423:             ?NS_MUC = exml_query:attr(X, <<"xmlns">>)
  424:         end).
  425: 
  426: destroy_room(Config) ->
  427:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  428:             escalus:send(Alice, stanza_destroy_room(?ROOM)),
  429:             AffUsersChanges = [{Alice, none}, {Bob, none}, {Kate, none}],
  430:             verify_aff_bcast([], AffUsersChanges, [], Alice),
  431:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice))
  432:         end).
  433: 
  434: set_config(Config) ->
  435:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  436:             ConfigChange = [{<<"roomname">>, <<"The Coven">>}],
  437:             escalus:send(Alice, stanza_config_set(?ROOM, ConfigChange)),
  438:             foreach_recipient([Alice, Bob, Kate], config_msg_verify_fun()),
  439:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)),
  440:             Stanza = stanza_config_get(?ROOM),
  441:             foreach_occupant([Alice, Bob, Kate], Stanza, config_iq_verify_fun(ConfigChange))
  442:         end).
  443: 
  444: assorted_config_doesnt_lead_to_duplication(Config) ->
  445:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  446:             ConfigChange = [{<<"subject">>, <<"Elixirs">>},
  447:                             {<<"roomname">>, <<"The Coven">>},
  448:                             {<<"subject">>, <<"Elixirs">>}],
  449:             escalus:send(Alice, stanza_config_set(?ROOM, ConfigChange)),
  450:             foreach_recipient([Alice, Bob, Kate], config_msg_verify_fun()),
  451:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)),
  452: 
  453:             Stanza = stanza_config_get(?ROOM),
  454:             VerifyFun = fun(Incoming) ->
  455:                                 Fields = exml_query:paths(Incoming, [{element, <<"query">>},
  456:                                                                      {element, <<"x">>},
  457:                                                                      {element, <<"field">>}]),
  458:                                 ConfigKV = [{exml_query:attr(F, <<"var">>),
  459:                                              exml_query:path(F, [{element, <<"value">>}, cdata])}
  460:                                             || F <- Fields],
  461:                                 Length = length(ConfigKV),
  462:                                 Length = length(lists:ukeysort(1, ConfigKV))
  463:                         end,
  464:             foreach_occupant([Alice, Bob, Kate], Stanza, VerifyFun)
  465:          end).
  466: 
  467: remove_and_add_users(Config) ->
  468:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  469:             AffUsersChanges1 = [{Bob, none}, {Kate, none}],
  470:             escalus:send(Alice, stanza_aff_set(?ROOM, AffUsersChanges1)),
  471:             verify_aff_bcast([Alice], AffUsersChanges1, [], Alice),
  472:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)),
  473:             AffUsersChanges2 = [{Bob, member}, {Kate, member}],
  474:             escalus:send(Alice, stanza_aff_set(?ROOM, AffUsersChanges2)),
  475:             verify_aff_bcast([Alice, Bob, Kate], AffUsersChanges2, [Bob, Kate], Alice),
  476:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice))
  477:         end).
  478: 
  479: explicit_owner_change(Config) ->
  480:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  481:             AffUsersChanges1 = [{Bob, none}, {Alice, none}, {Kate, owner}],
  482:             escalus:send(Alice, stanza_aff_set(?ROOM, AffUsersChanges1)),
  483:             verify_aff_bcast([Kate], AffUsersChanges1, [], Alice),
  484:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice))
  485:         end).
  486: 
  487: implicit_owner_change(Config) ->
  488:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  489:             AffUsersChanges1 = [{Bob, none}, {Alice, member}],
  490:             escalus:send(Alice, stanza_aff_set(?ROOM, AffUsersChanges1)),
  491:             verify_aff_bcast([Kate, Alice], [{Kate, owner} | AffUsersChanges1], [], Alice),
  492:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice))
  493:         end).
  494: 
  495: edge_case_owner_change(Config) ->
  496:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  497:             AffUsersChanges1 = [{Alice, member}, {Bob, none}, {Kate, none}],
  498:             escalus:send(Alice, stanza_aff_set(?ROOM, AffUsersChanges1)),
  499:             verify_aff_bcast([Alice], [{Kate, none}, {Bob, none}], [], Alice),
  500:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice))
  501:         end).
  502: 
  503: %% ---------------------- blocking ----------------------
  504: 
  505: manage_blocklist(Config) ->
  506:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  507:             escalus:send(Alice, stanza_blocking_get()),
  508:             GetResult1 = escalus:wait_for_stanza(Alice),
  509:             escalus:assert(is_iq_result, GetResult1),
  510:             QueryEl1 = exml_query:subelement(GetResult1, <<"query">>),
  511:             verify_blocklist(QueryEl1, []),
  512:             Domain = domain(),
  513:             BlocklistChange1 = [{user, deny, <<"user@", Domain/binary>>},
  514:                                 {room, deny, room_bin_jid(?ROOM)}],
  515:             escalus:send(Alice, stanza_blocking_set(BlocklistChange1)),
  516:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)),
  517:             escalus:send(Alice, stanza_blocking_get()),
  518:             GetResult2 = escalus:wait_for_stanza(Alice),
  519:             escalus:assert(is_iq_result, GetResult2),
  520:             QueryEl2 = exml_query:subelement(GetResult2, <<"query">>),
  521:             verify_blocklist(QueryEl2, BlocklistChange1),
  522: 
  523:             BlocklistChange2 = [{user, allow, <<"user@", Domain/binary>>},
  524:                                 {room, allow, room_bin_jid(?ROOM)}],
  525:             escalus:send(Alice, stanza_blocking_set(BlocklistChange2)),
  526:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)),
  527:             escalus:send(Alice, stanza_blocking_get()),
  528:             GetResult3 = escalus:wait_for_stanza(Alice),
  529:             escalus:assert(is_iq_result, GetResult3),
  530:             % Match below checks for empty list
  531:             QueryEl1 = exml_query:subelement(GetResult3, <<"query">>)
  532:         end).
  533: 
  534: block_room(Config) ->
  535:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  536:             BlocklistChange = [{room, deny, room_bin_jid(?ROOM)}],
  537:             escalus:send(Bob, stanza_blocking_set(BlocklistChange)),
  538:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob)),
  539:             user_leave(Bob, [Alice, Kate]),
  540: 
  541:             % Alice tries to readd Bob to the room but fails
  542:             BobReadd = [{Bob, member}],
  543:             escalus:send(Alice, stanza_aff_set(?ROOM, BobReadd)),
  544:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)),
  545:             verify_no_stanzas([Alice, Bob, Kate]),
  546: 
  547:             % But Alice can add Bob to another room!
  548:             escalus:send(Alice, stanza_aff_set(?ROOM2, BobReadd)),
  549:             verify_aff_bcast([Alice, Bob, Kate], BobReadd, [Bob], Alice),
  550:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice))
  551:         end).
  552: 
  553: block_user(Config) ->
  554:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  555:             AliceJIDBin = lbin(escalus_client:short_jid(Alice)),
  556:             BlocklistChange = [{user, deny, AliceJIDBin}],
  557:             escalus:send(Bob, stanza_blocking_set(BlocklistChange)),
  558:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob)),
  559: 
  560:             % Alice tries to add Bob to the room but fails
  561:             BobAdd = [{Bob, member}],
  562:             escalus:send(Alice, stanza_aff_set(?ROOM2, BobAdd)),
  563:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)),
  564:             verify_no_stanzas([Alice, Bob, Kate]),
  565: 
  566:             % But Kate can add Bob to the room!
  567:             escalus:send(Kate, stanza_aff_set(?ROOM2, BobAdd)),
  568:             verify_aff_bcast([Alice, Bob, Kate], BobAdd, [Bob], Kate),
  569:             escalus:assert(is_iq_result, escalus:wait_for_stanza(Kate)),
  570:             verify_no_stanzas([Alice, Bob, Kate])
  571:         end).
  572: 
  573: blocking_disabled(Config) ->
  574:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  575:             escalus:send(Alice, stanza_blocking_get()),
  576:             escalus:assert(is_error, [<<"modify">>, <<"bad-request">>],
  577:                            escalus:wait_for_stanza(Alice)),
  578:             Domain = domain(),
  579:             BlocklistChange1 = [{user, deny, <<"user@", Domain/binary>>},
  580:                                 {room, deny, room_bin_jid(?ROOM)}],
  581:             escalus:send(Alice, stanza_blocking_set(BlocklistChange1)),
  582:             escalus:assert(is_error, [<<"modify">>, <<"bad-request">>],
  583:                            escalus:wait_for_stanza(Alice))
  584:         end).
  585: 
  586: %%--------------------------------------------------------------------
  587: %% Subroutines
  588: %%--------------------------------------------------------------------
  589: 
  590: -spec user_leave(User :: escalus:client(), RemainingOccupants :: [escalus:client()]) -> ok.
  591: user_leave(User, RemainingOccupants) ->
  592:     AffUsersChanges = [{User, none}],
  593:     Stanza = stanza_aff_set(?ROOM, AffUsersChanges),
  594:     escalus:send(User, Stanza),
  595:     verify_aff_bcast(RemainingOccupants, AffUsersChanges, [], User),
  596:     escalus:assert(is_iq_result, escalus:wait_for_stanza(User)).
  597: 
  598: %%--------------------------------------------------------------------
  599: %% IQ getters
  600: %%--------------------------------------------------------------------
  601: 
  602: -spec stanza_blocking_get() -> xmlel().
  603: stanza_blocking_get() ->
  604:     escalus_stanza:privacy_get_lists([?NS_MUC_LIGHT]).
  605: 
  606: -spec stanza_config_get(Room :: binary()) -> xmlel().
  607: stanza_config_get(Room) ->
  608:     escalus_stanza:to(
  609:       escalus_stanza:iq_get(?NS_MUC_OWNER, []), room_bin_jid(Room)).
  610: 
  611: -spec stanza_aff_get(Room :: binary()) -> xmlel().
  612: stanza_aff_get(Room) ->
  613:     escalus_stanza:to(
  614:       escalus_stanza:iq_get(?NS_MUC_ADMIN, []), room_bin_jid(Room)).
  615: 
  616: %%--------------------------------------------------------------------
  617: %% IQ setters
  618: %%--------------------------------------------------------------------
  619: 
  620: -spec stanza_blocking_set(BlocklistChanges :: [ct_block_item()]) -> xmlel().
  621: stanza_blocking_set(BlocklistChanges) ->
  622:     Items = [ encode_privacy_item(What, Action, Who) || {What, Action, Who} <- BlocklistChanges ],
  623:     escalus_stanza:privacy_set_list(escalus_stanza:privacy_list(?NS_MUC_LIGHT, Items)).
  624: 
  625: encode_privacy_item(What, Action, Who) ->
  626:     Value = case What of
  627:                 room -> Who;
  628:                 user -> <<(?MUCHOST)/binary, $/, Who/binary>>
  629:             end,
  630:     ActionBin = atom_to_binary(Action, utf8),
  631:     escalus_stanza:privacy_list_item(<<"1">>, ActionBin, <<"jid">>, Value, []).
  632: 
  633: -spec stanza_create_room(RoomNode :: binary(), Creator :: escalus:client()) -> xmlel().
  634: stanza_create_room(RoomNode, Creator) ->
  635:     ToBinJID = <<(room_bin_jid(RoomNode))/binary, $/,
  636:                  (lbin(escalus_client:short_jid(Creator)))/binary>>,
  637:     X = #xmlel{ name = <<"x">>, attrs = [{<<"xmlns">>, ?NS_MUC}] },
  638:     escalus_stanza:to(escalus_stanza:presence(<<"available">>, [X]), ToBinJID).
  639: 
  640: -spec stanza_destroy_room(Room :: binary()) -> xmlel().
  641: stanza_destroy_room(Room) ->
  642:     escalus_stanza:to(escalus_stanza:iq_set(?NS_MUC_OWNER, [#xmlel{ name = <<"destroy">> }]),
  643:                       room_bin_jid(Room)).
  644: 
  645: -spec stanza_config_set(Room :: binary(), ConfigChanges :: [muc_light_helper:config_item()]) -> xmlel().
  646: stanza_config_set(Room, ConfigChanges) ->
  647:     IQ = escalus_stanza:iq_set(?NS_MUC_OWNER, [form_x_el(ConfigChanges)]),
  648:     escalus_stanza:to(IQ, room_bin_jid(Room)).
  649: 
  650: -spec form_x_el(Fields :: [xmlel()]) -> xmlel().
  651: form_x_el(Fields) ->
  652:     #xmlel{
  653:        name = <<"x">>,
  654:        attrs = [{<<"xmlns">>, <<"jabber:x:data">>}, {<<"type">>, <<"submit">>}],
  655:        children = [form_field(<<"FORM_TYPE">>, ?NS_MUC_ROOMCONFIG, <<"hidden">>)
  656:                    | [form_field(K, V, <<"text-single">>) || {K, V} <- Fields ]]
  657:       }.
  658: 
  659: -spec form_field(Var :: binary(), Value :: binary(), Type :: binary()) -> xmlel().
  660: form_field(Var, Value, Type) ->
  661:     #xmlel{ name  = <<"field">>,
  662:             attrs = [{<<"type">>, Type}, {<<"var">>, Var}],
  663:             children  = [#xmlel{name = <<"value">>,
  664:                                 children = [#xmlcdata{content = Value}] }] }.
  665: 
  666: -spec stanza_aff_set(Room :: binary(), AffUsers :: ct_aff_users()) -> xmlel().
  667: stanza_aff_set(Room, AffUsers) ->
  668:     Items = [#xmlel{ name = <<"item">>, attrs = [{<<"affiliation">>, AffBin},
  669:                                                  {<<"jid">>, UserBin}] }
  670:              || {UserBin, AffBin} <- bin_aff_users(AffUsers)],
  671:     escalus_stanza:to(escalus_stanza:iq_set(?NS_MUC_ADMIN, Items), room_bin_jid(Room)).
  672: 
  673: %%--------------------------------------------------------------------
  674: %% Verifiers
  675: %%--------------------------------------------------------------------
  676: 
  677: -spec verify_blocklist(Query :: xmlel(), ProperBlocklist :: [ct_block_item()]) -> [].
  678: verify_blocklist(Query, ProperBlocklist) ->
  679:     ?NS_PRIVACY = exml_query:attr(Query, <<"xmlns">>),
  680:     RawItems = exml_query:paths(Query, [{element, <<"list">>}, {element, <<"item">>}]),
  681:     Blocklist = [ parse_blocked_item(Item) || Item <- RawItems ],
  682:     ProperBlocklistLen = length(ProperBlocklist),
  683:     ProperBlocklistLen = length(Blocklist),
  684:     [] = lists:foldl(fun lists:delete/2, Blocklist, ProperBlocklist).
  685: 
  686: -spec parse_blocked_item(Item :: xmlel()) -> ct_block_item().
  687: parse_blocked_item(Item) ->
  688:     <<"deny">> = exml_query:attr(Item, <<"action">>),
  689:     <<"jid">> = exml_query:attr(Item, <<"type">>),
  690:     Value = exml_query:attr(Item, <<"value">>),
  691:     MucHost = ?MUCHOST,
  692:     case binary:split(Value, <<"/">>) of
  693:         [MucHost, User] -> {user, deny, User};
  694:         [Room] -> {room, deny, Room};
  695:         Other ->
  696:             CfgHost = rpc(gen_mod, get_module_opt, [host_type(), mod_muc_light, host, undefined]),
  697:             ct:fail(#{what => parse_blocked_item_failed,
  698:                       muc_host => MucHost, other => Other,
  699:                       cfg_host => CfgHost})
  700:     end.
  701: 
  702: -spec verify_aff_bcast(CurrentOccupants :: [escalus:client()], AffUsersChanges :: ct_aff_users(),
  703:                        Newcomers :: [escalus:client()], Changer :: escalus:client()) -> ok.
  704: verify_aff_bcast(CurrentOccupants, AffUsersChanges, Newcomers, Changer) ->
  705:     MucHost = ?MUCHOST,
  706:     PredList = [ presence_verify_fun(AffUser) || AffUser <- AffUsersChanges ],
  707:     lists:foreach(
  708:       fun(Occupant) ->
  709:               case lists:member(Occupant, Newcomers) of
  710:                   false ->
  711:                       Stanzas = escalus:wait_for_stanzas(Occupant, length(PredList)),
  712:                       escalus_new_assert:mix_match(PredList, Stanzas);
  713:                   true ->
  714:                       ok
  715:               end
  716:       end, CurrentOccupants),
  717:     lists:foreach(
  718:       fun(Newcomer) ->
  719:               #xmlel{ name = <<"message">> } = Incoming = escalus:wait_for_stanza(Newcomer),
  720:               RoomBareJIDBin = exml_query:attr(Incoming, <<"from">>),
  721:               [_, MucHost] = binary:split(RoomBareJIDBin, <<"@">>),
  722:               X = exml_query:subelement(Incoming, <<"x">>),
  723:               ?NS_MUC_USER = exml_query:attr(X, <<"xmlns">>),
  724:               [Invite] = exml_query:subelements(X, <<"invite">>),
  725:               ChangerBareJIDBin = lbin(escalus_client:short_jid(Changer)),
  726:               ChangerBareJIDBin = exml_query:attr(Invite, <<"from">>)
  727:       end, Newcomers),
  728:     lists:foreach(
  729:       fun({Leaver, none}) ->
  730:               presence_verify(Leaver, none, escalus:wait_for_stanza(Leaver));
  731:          (_) ->
  732:               ignore
  733:       end, AffUsersChanges).
  734: 
  735: -spec verify_no_stanzas(Users :: [escalus:client()]) -> ok.
  736: verify_no_stanzas(Users) ->
  737:     lists:foreach(
  738:       fun(User) ->
  739:               {false, _} = {escalus_client:has_stanzas(User), User}
  740:       end, Users).
  741: 
  742: -spec verify_config(ConfigFields :: [xmlel()], Config :: [muc_light_helper:config_item()]) -> ok.
  743: verify_config(ConfigFields, Config) ->
  744:     [] = lists:foldl(
  745:            fun(Field, ConfigAcc) ->
  746:                    Key = exml_query:attr(Field, <<"var">>),
  747:                    Val = exml_query:path(Field, [{element, <<"value">>}, cdata]),
  748:                    case lists:keytake(Key, 1, ConfigAcc) of
  749:                        {value, {_, ProperVal}, NewConfig} when Val =:= ProperVal -> NewConfig;
  750:                        false -> ConfigAcc
  751:                    end
  752:            end, Config, ConfigFields).
  753: 
  754: -spec verify_aff_users(Items :: [xmlel()], BinAffUsers :: [{binary(), binary()}]) -> [].
  755: verify_aff_users(Items, BinAffUsers) ->
  756:     true = (length(Items) == length(BinAffUsers)),
  757:     [] = lists:foldl(
  758:            fun(Item, AffAcc) ->
  759:                    JID = exml_query:attr(Item, <<"jid">>),
  760:                    JID = exml_query:attr(Item, <<"nick">>),
  761:                    Aff = exml_query:attr(Item, <<"affiliation">>),
  762:                    verify_keytake(lists:keytake(JID, 1, AffAcc), JID, Aff, AffAcc)
  763:            end, BinAffUsers, Items).
  764: 
  765: -spec verify_keytake(Result :: {value, Item :: tuple(), Acc :: list()}, JID :: binary(),
  766:                      Aff :: binary(), AffAcc :: list()) -> list().
  767: verify_keytake({value, {_, Aff}, NewAffAcc}, _JID, Aff, _AffAcc) -> NewAffAcc.
  768: 
  769: %%--------------------------------------------------------------------
  770: %% Verification funs generators
  771: %%--------------------------------------------------------------------
  772: 
  773: -spec gc_message_verify_fun(Room :: binary(), MsgText :: binary(), Id :: binary()) -> verify_fun().
  774: gc_message_verify_fun(Room, MsgText, Id) ->
  775:     MucHost = ?MUCHOST,
  776:     fun(Incoming) ->
  777:             escalus:assert(is_groupchat_message, [MsgText], Incoming),
  778:             [RoomBareJID, FromNick] = binary:split(exml_query:attr(Incoming, <<"from">>), <<"/">>),
  779:             [Room, MucHost] = binary:split(RoomBareJID, <<"@">>),
  780:             [_] = binary:split(FromNick, <<"/">>), % nick is bare JID
  781:             Id = exml_query:attr(Incoming, <<"id">>)
  782:     end.
  783: 
  784: -spec subject_message_verify_fun(Room :: binary(), Subject :: binary()) -> verify_fun().
  785: subject_message_verify_fun(Room, Subject) ->
  786:     MucHost = ?MUCHOST,
  787:     fun(Incoming) ->
  788:             escalus:assert(is_groupchat_message, Incoming),
  789:             Subject = exml_query:path(Incoming, [{element, <<"subject">>}, cdata]),
  790:             RoomBareJID = exml_query:attr(Incoming, <<"from">>),
  791:             [Room, MucHost] = binary:split(RoomBareJID, <<"@">>)
  792:     end.
  793: 
  794: -spec config_msg_verify_fun() -> verify_fun().
  795: config_msg_verify_fun() ->
  796:     fun(Incoming) ->
  797:             escalus:assert(is_groupchat_message, Incoming),
  798:             [X] = exml_query:subelements(Incoming, <<"x">>),
  799:             ?NS_MUC_USER = exml_query:attr(X, <<"xmlns">>),
  800:             <<"104">> = exml_query:path(X, [{element, <<"status">>}, {attr, <<"code">>}])
  801:     end.
  802: 
  803: -spec config_iq_verify_fun(RoomConfig :: [muc_light_helper:config_item()]) -> verify_fun().
  804: config_iq_verify_fun(RoomConfig) ->
  805:     fun(Incoming) ->
  806:             Fields = exml_query:paths(Incoming, [{element, <<"query">>}, {element, <<"x">>},
  807:                                                   {element, <<"field">>}]),
  808:             ?NS_MUC_OWNER = exml_query:path(Incoming, [{element, <<"query">>},
  809:                                                        {attr, <<"xmlns">>}]),
  810:             verify_config(Fields, RoomConfig)
  811:     end.
  812: 
  813: -spec aff_iq_verify_fun(AffUsers :: ct_aff_users()) -> verify_fun().
  814: aff_iq_verify_fun(AffUsers) ->
  815:     BinAffUsers = bin_aff_users(AffUsers),
  816:     fun(Incoming) ->
  817:             [Query] = exml_query:subelements(Incoming, <<"query">>),
  818:             ?NS_MUC_ADMIN = exml_query:attr(Query, <<"xmlns">>),
  819:             Items = exml_query:subelements(Query, <<"item">>),
  820:             verify_aff_users(Items, BinAffUsers)
  821:     end.
  822: 
  823: -spec presence_verify_fun(AffUser :: ct_aff_user()) -> verify_fun().
  824: presence_verify_fun({User, UserAff}) ->
  825:     fun(Incoming) ->
  826:             true == (catch presence_verify(User, UserAff, Incoming))
  827:     end.
  828: 
  829: -spec presence_verify(User :: escalus:client(), UserAff :: none | member | owner,
  830:                       Incoming :: xmlel()) -> true.
  831: presence_verify(User, UserAff, #xmlel{ name = <<"presence">> } = Incoming) ->
  832:     MucHost = ?MUCHOST,
  833:     UserJIDBin = lbin(escalus_client:short_jid(User)),
  834:     [RoomBareJIDBin, UserJIDBin] = binary:split(exml_query:attr(Incoming, <<"from">>), <<"/">>),
  835:     [_, MucHost] = binary:split(RoomBareJIDBin, <<"@">>),
  836:     X = exml_query:subelement(Incoming, <<"x">>),
  837:     HasDestroy = exml_query:subelement(X, <<"destroy">>) =/= undefined,
  838:     {ProperAff, ProperRole}
  839:     = case {UserAff, exml_query:attr(Incoming, <<"type">>)} of
  840:           {none, _} ->
  841:               <<"unavailable">> = exml_query:attr(Incoming, <<"type">>),
  842:               case HasDestroy of
  843:                   false ->
  844:                       <<"321">> = exml_query:path(X, [{element, <<"status">>}, {attr, <<"code">>}]);
  845:                   true ->
  846:                       ok
  847:               end,
  848:               {<<"none">>, <<"none">>};
  849:           {_, Type} when Type =/= <<"unavailable">> ->
  850:               case UserAff of
  851:                   member -> {<<"member">>, <<"participant">>};
  852:                   owner -> {<<"owner">>, <<"moderator">>}
  853:               end
  854:       end,
  855:     ?NS_MUC_USER = exml_query:attr(X, <<"xmlns">>),
  856:     [Item] = exml_query:subelements(X, <<"item">>),
  857:     ProperAff = exml_query:attr(Item, <<"affiliation">>),
  858:     ProperRole = exml_query:attr(Item, <<"role">>),
  859:     case exml_query:subelements(X, <<"status">>) of
  860:         [_, _] -> % room create request
  861:             [<<"110">>, <<"201">>]
  862:             = lists:sort(exml_query:paths(X, [{element, <<"status">>}, {attr, <<"code">>}]));
  863:         _ ->
  864:             case HasDestroy of
  865:                 false -> UserJIDBin = exml_query:attr(Item, <<"jid">>);
  866:                 true -> ok
  867:             end
  868:     end,
  869:     true.
  870: 
  871: %%--------------------------------------------------------------------
  872: %% Other helpers
  873: %%--------------------------------------------------------------------
  874: 
  875: -spec room_bin_jid(Room :: binary()) -> binary().
  876: room_bin_jid(Room) ->
  877:     <<Room/binary, $@, (muc_helper:muc_host())/binary>>.