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