%%%------------------------------------------------------------------- %%% Author : Evgeny Khramtsov %%% Created : 15 Oct 2016 by Evgeny Khramtsov %%% %%% %%% ejabberd, Copyright (C) 2002-2024 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as %%% published by the Free Software Foundation; either version 2 of the %%% License, or (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU %%% General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License along %%% with this program; if not, write to the Free Software Foundation, Inc., %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% %%%---------------------------------------------------------------------- -module(muc_tests). %% API -compile(export_all). -import(suite, [recv_presence/1, send_recv/2, my_jid/1, muc_room_jid/1, send/2, recv_message/1, recv_iq/1, muc_jid/1, alt_room_jid/1, wait_for_slave/1, wait_for_master/1, disconnect/1, put_event/2, get_event/1, peer_muc_jid/1, my_muc_jid/1, get_features/2, set_opt/3]). -include("suite.hrl"). %%%=================================================================== %%% API %%%=================================================================== %%%=================================================================== %%% Single tests %%%=================================================================== single_cases() -> {muc_single, [sequence], [single_test(service_presence_error), single_test(service_message_error), single_test(service_unknown_ns_iq_error), single_test(service_iq_set_error), single_test(service_improper_iq_error), single_test(service_features), single_test(service_disco_info_node_error), single_test(service_disco_items), single_test(service_unique), single_test(service_vcard), single_test(configure_non_existent), single_test(cancel_configure_non_existent), single_test(service_subscriptions), single_test(set_room_affiliation)]}. service_presence_error(Config) -> Service = muc_jid(Config), ServiceResource = jid:replace_resource(Service, p1_rand:get_string()), lists:foreach( fun(To) -> send(Config, #presence{type = error, to = To}), lists:foreach( fun(Type) -> #presence{type = error} = Err = send_recv(Config, #presence{type = Type, to = To}), #stanza_error{reason = 'service-unavailable'} = xmpp:get_error(Err) end, [available, unavailable]) end, [Service, ServiceResource]), disconnect(Config). service_message_error(Config) -> Service = muc_jid(Config), send(Config, #message{type = error, to = Service}), lists:foreach( fun(Type) -> #message{type = error} = Err1 = send_recv(Config, #message{type = Type, to = Service}), #stanza_error{reason = 'forbidden'} = xmpp:get_error(Err1) end, [chat, normal, headline, groupchat]), ServiceResource = jid:replace_resource(Service, p1_rand:get_string()), send(Config, #message{type = error, to = ServiceResource}), lists:foreach( fun(Type) -> #message{type = error} = Err2 = send_recv(Config, #message{type = Type, to = ServiceResource}), #stanza_error{reason = 'service-unavailable'} = xmpp:get_error(Err2) end, [chat, normal, headline, groupchat]), disconnect(Config). service_unknown_ns_iq_error(Config) -> Service = muc_jid(Config), ServiceResource = jid:replace_resource(Service, p1_rand:get_string()), lists:foreach( fun(To) -> send(Config, #iq{type = result, to = To}), send(Config, #iq{type = error, to = To}), lists:foreach( fun(Type) -> #iq{type = error} = Err1 = send_recv(Config, #iq{type = Type, to = To, sub_els = [#presence{}]}), #stanza_error{reason = 'service-unavailable'} = xmpp:get_error(Err1) end, [set, get]) end, [Service, ServiceResource]), disconnect(Config). service_iq_set_error(Config) -> Service = muc_jid(Config), lists:foreach( fun(SubEl) -> send(Config, #iq{type = result, to = Service, sub_els = [SubEl]}), #iq{type = error} = Err2 = send_recv(Config, #iq{type = set, to = Service, sub_els = [SubEl]}), #stanza_error{reason = 'not-allowed'} = xmpp:get_error(Err2) end, [#disco_items{}, #disco_info{}, #vcard_temp{}, #muc_unique{}, #muc_subscriptions{}]), disconnect(Config). service_improper_iq_error(Config) -> Service = muc_jid(Config), lists:foreach( fun(SubEl) -> send(Config, #iq{type = result, to = Service, sub_els = [SubEl]}), lists:foreach( fun(Type) -> #iq{type = error} = Err3 = send_recv(Config, #iq{type = Type, to = Service, sub_els = [SubEl]}), #stanza_error{reason = Reason} = xmpp:get_error(Err3), true = Reason /= 'internal-server-error' end, [set, get]) end, [#disco_item{jid = Service}, #identity{category = <<"category">>, type = <<"type">>}, #vcard_email{}, #muc_subscribe{nick = ?config(nick, Config)}]), disconnect(Config). service_features(Config) -> ServerHost = ?config(server_host, Config), MUC = muc_jid(Config), Features = sets:from_list(get_features(Config, MUC)), MAMFeatures = case gen_mod:is_loaded(ServerHost, mod_mam) of true -> [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1]; false -> [] end, RequiredFeatures = sets:from_list( [?NS_DISCO_INFO, ?NS_DISCO_ITEMS, ?NS_REGISTER, ?NS_MUC, ?NS_VCARD, ?NS_MUCSUB, ?NS_MUC_UNIQUE | MAMFeatures]), ct:comment("Checking if all needed disco features are set"), true = sets:is_subset(RequiredFeatures, Features), disconnect(Config). service_disco_info_node_error(Config) -> MUC = muc_jid(Config), Node = p1_rand:get_string(), #iq{type = error} = Err = send_recv(Config, #iq{type = get, to = MUC, sub_els = [#disco_info{node = Node}]}), #stanza_error{reason = 'item-not-found'} = xmpp:get_error(Err), disconnect(Config). service_disco_items(Config) -> #jid{server = Service} = muc_jid(Config), Rooms = lists:sort( lists:map( fun(I) -> RoomName = integer_to_binary(I), jid:make(RoomName, Service) end, lists:seq(1, 5))), lists:foreach( fun(Room) -> ok = join_new(Config, Room) end, Rooms), Items = disco_items(Config), Rooms = [J || #disco_item{jid = J} <- Items], lists:foreach( fun(Room) -> ok = leave(Config, Room) end, Rooms), [] = disco_items(Config), disconnect(Config). service_vcard(Config) -> MUC = muc_jid(Config), ct:comment("Retrieving vCard from ~s", [jid:encode(MUC)]), VCard = mod_muc_opt:vcard(?config(server, Config)), #iq{type = result, sub_els = [VCard]} = send_recv(Config, #iq{type = get, to = MUC, sub_els = [#vcard_temp{}]}), disconnect(Config). service_unique(Config) -> MUC = muc_jid(Config), ct:comment("Requesting muc unique from ~s", [jid:encode(MUC)]), #iq{type = result, sub_els = [#muc_unique{name = Name}]} = send_recv(Config, #iq{type = get, to = MUC, sub_els = [#muc_unique{}]}), ct:comment("Checking if unique name is set in the response"), <<_, _/binary>> = Name, disconnect(Config). configure_non_existent(Config) -> [_|_] = get_config(Config), disconnect(Config). cancel_configure_non_existent(Config) -> Room = muc_room_jid(Config), #iq{type = result, sub_els = []} = send_recv(Config, #iq{to = Room, type = set, sub_els = [#muc_owner{config = #xdata{type = cancel}}]}), disconnect(Config). service_subscriptions(Config) -> MUC = #jid{server = Service} = muc_jid(Config), Rooms = lists:sort( lists:map( fun(I) -> RoomName = integer_to_binary(I), jid:make(RoomName, Service) end, lists:seq(1, 5))), lists:foreach( fun(Room) -> ok = join_new(Config, Room), [104] = set_config(Config, [{allow_subscription, true}], Room), [] = subscribe(Config, [], Room) end, Rooms), #iq{type = result, sub_els = [#muc_subscriptions{list = JIDs}]} = send_recv(Config, #iq{type = get, to = MUC, sub_els = [#muc_subscriptions{}]}), Rooms = lists:sort([J || #muc_subscription{jid = J, events = []} <- JIDs]), lists:foreach( fun(Room) -> ok = unsubscribe(Config, Room), ok = leave(Config, Room) end, Rooms), disconnect(Config). set_room_affiliation(Config) -> #jid{server = RoomService} = muc_jid(Config), RoomName = <<"set_room_affiliation">>, RoomJID = jid:make(RoomName, RoomService), MyJID = my_jid(Config), PeerJID = jid:remove_resource(?config(slave, Config)), ct:pal("joining room ~p", [RoomJID]), ok = join_new(Config, RoomJID), ct:pal("setting affiliation in room ~p to 'member' for ~p", [RoomJID, PeerJID]), ServerHost = ?config(server_host, Config), WebPort = ct:get_config(web_port, 5280), RequestURL = "http://" ++ ServerHost ++ ":" ++ integer_to_list(WebPort) ++ "/api/set_room_affiliation", Headers = [{"X-Admin", "true"}], ContentType = "application/json", Body = misc:json_encode(#{room => RoomName, service => RoomService, user => PeerJID#jid.luser, host => PeerJID#jid.lserver, affiliation => member}), {ok, {{_, 200, _}, _, _}} = httpc:request(post, {RequestURL, Headers, ContentType, Body}, [], []), #message{id = _, from = RoomJID, to = MyJID, sub_els = [ #muc_user{items = [ #muc_item{affiliation = member, role = none, jid = PeerJID}]}]} = recv_message(Config), ok = leave(Config, RoomJID), disconnect(Config). %%%=================================================================== %%% Master-slave tests %%%=================================================================== master_slave_cases() -> {muc_master_slave, [sequence], [master_slave_test(register), master_slave_test(groupchat_msg), master_slave_test(private_msg), master_slave_test(set_subject), master_slave_test(history), master_slave_test(invite), master_slave_test(invite_members_only), master_slave_test(invite_password_protected), master_slave_test(voice_request), master_slave_test(change_role), master_slave_test(kick), master_slave_test(change_affiliation), master_slave_test(destroy), master_slave_test(vcard), master_slave_test(nick_change), master_slave_test(config_title_desc), master_slave_test(config_public_list), master_slave_test(config_password), master_slave_test(config_whois), master_slave_test(config_members_only), master_slave_test(config_moderated), master_slave_test(config_private_messages), master_slave_test(config_query), master_slave_test(config_allow_invites), master_slave_test(config_visitor_status), master_slave_test(config_allow_voice_requests), master_slave_test(config_voice_request_interval), master_slave_test(config_visitor_nickchange), master_slave_test(join_conflict)]}. join_conflict_master(Config) -> ok = join_new(Config), put_event(Config, join), ct:comment("Waiting for 'leave' command from the slave"), leave = get_event(Config), ok = leave(Config), disconnect(Config). join_conflict_slave(Config) -> NewConfig = set_opt(nick, ?config(peer_nick, Config), Config), ct:comment("Waiting for 'join' command from the master"), join = get_event(Config), ct:comment("Fail trying to join the room with conflicting nick"), #stanza_error{reason = 'conflict'} = join(NewConfig), put_event(Config, leave), disconnect(NewConfig). groupchat_msg_master(Config) -> Room = muc_room_jid(Config), PeerJID = ?config(slave, Config), PeerNick = ?config(slave_nick, Config), PeerNickJID = jid:replace_resource(Room, PeerNick), MyNick = ?config(nick, Config), MyNickJID = jid:replace_resource(Room, MyNick), ok = master_join(Config), lists:foreach( fun(I) -> Body = xmpp:mk_text(integer_to_binary(I)), send(Config, #message{type = groupchat, to = Room, body = Body}), #message{type = groupchat, from = MyNickJID, body = Body} = recv_message(Config) end, lists:seq(1, 5)), #muc_user{items = [#muc_item{jid = PeerJID, role = none, affiliation = none}]} = recv_muc_presence(Config, PeerNickJID, unavailable), ok = leave(Config), disconnect(Config). groupchat_msg_slave(Config) -> Room = muc_room_jid(Config), PeerNick = ?config(master_nick, Config), PeerNickJID = jid:replace_resource(Room, PeerNick), {[], _, _} = slave_join(Config), lists:foreach( fun(I) -> Body = xmpp:mk_text(integer_to_binary(I)), #message{type = groupchat, from = PeerNickJID, body = Body} = recv_message(Config) end, lists:seq(1, 5)), ok = leave(Config), disconnect(Config). private_msg_master(Config) -> Room = muc_room_jid(Config), PeerJID = ?config(slave, Config), PeerNick = ?config(slave_nick, Config), PeerNickJID = jid:replace_resource(Room, PeerNick), ok = master_join(Config), lists:foreach( fun(I) -> Body = xmpp:mk_text(integer_to_binary(I)), send(Config, #message{type = chat, to = PeerNickJID, body = Body}) end, lists:seq(1, 5)), #muc_user{items = [#muc_item{jid = PeerJID, role = none, affiliation = none}]} = recv_muc_presence(Config, PeerNickJID, unavailable), ct:comment("Fail trying to send a private message to non-existing occupant"), send(Config, #message{type = chat, to = PeerNickJID}), #message{from = PeerNickJID, type = error} = ErrMsg = recv_message(Config), #stanza_error{reason = 'item-not-found'} = xmpp:get_error(ErrMsg), ok = leave(Config), disconnect(Config). private_msg_slave(Config) -> Room = muc_room_jid(Config), PeerNick = ?config(master_nick, Config), PeerNickJID = jid:replace_resource(Room, PeerNick), {[], _, _} = slave_join(Config), lists:foreach( fun(I) -> Body = xmpp:mk_text(integer_to_binary(I)), #message{type = chat, from = PeerNickJID, body = Body} = recv_message(Config) end, lists:seq(1, 5)), ok = leave(Config), disconnect(Config). set_subject_master(Config) -> Room = muc_room_jid(Config), PeerJID = ?config(slave, Config), PeerNick = ?config(slave_nick, Config), PeerNickJID = jid:replace_resource(Room, PeerNick), Subject1 = xmpp:mk_text(?config(room_subject, Config)), Subject2 = xmpp:mk_text(<<"new-", (?config(room_subject, Config))/binary>>), ok = master_join(Config), ct:comment("Setting 1st subject"), send(Config, #message{type = groupchat, to = Room, subject = Subject1}), #message{type = groupchat, from = MyNickJID, subject = Subject1} = recv_message(Config), ct:comment("Waiting for the slave to leave"), recv_muc_presence(Config, PeerNickJID, unavailable), ct:comment("Setting 2nd subject"), send(Config, #message{type = groupchat, to = Room, subject = Subject2}), #message{type = groupchat, from = MyNickJID, subject = Subject2} = recv_message(Config), ct:comment("Asking the slave to join"), put_event(Config, join), recv_muc_presence(Config, PeerNickJID, available), ct:comment("Receiving 1st subject set by the slave"), #message{type = groupchat, from = PeerNickJID, subject = Subject1} = recv_message(Config), ct:comment("Disallow subject change"), [104] = set_config(Config, [{changesubject, false}]), ct:comment("Waiting for the slave to leave"), #muc_user{items = [#muc_item{jid = PeerJID, role = none, affiliation = none}]} = recv_muc_presence(Config, PeerNickJID, unavailable), ok = leave(Config), disconnect(Config). set_subject_slave(Config) -> Room = muc_room_jid(Config), MyNickJID = my_muc_jid(Config), PeerNick = ?config(master_nick, Config), PeerNickJID = jid:replace_resource(Room, PeerNick), Subject1 = xmpp:mk_text(?config(room_subject, Config)), Subject2 = xmpp:mk_text(<<"new-", (?config(room_subject, Config))/binary>>), {[], _, _} = slave_join(Config), ct:comment("Receiving 1st subject set by the master"), #message{type = groupchat, from = PeerNickJID, subject = Subject1} = recv_message(Config), ok = leave(Config), ct:comment("Waiting for 'join' command from the master"), join = get_event(Config), {[], SubjMsg2, _} = join(Config), ct:comment("Checking if the master has set 2nd subject during our absence"), #message{type = groupchat, from = PeerNickJID, subject = Subject2} = SubjMsg2, ct:comment("Setting 1st subject"), send(Config, #message{to = Room, type = groupchat, subject = Subject1}), #message{type = groupchat, from = MyNickJID, subject = Subject1} = recv_message(Config), ct:comment("Waiting for the master to disallow subject change"), [104] = recv_config_change_message(Config), ct:comment("Fail trying to change the subject"), send(Config, #message{to = Room, type = groupchat, subject = Subject2}), #message{from = Room, type = error} = ErrMsg = recv_message(Config), #stanza_error{reason = 'forbidden'} = xmpp:get_error(ErrMsg), ok = leave(Config), disconnect(Config). history_master(Config) -> Room = muc_room_jid(Config), ServerHost = ?config(server_host, Config), MyNick = ?config(nick, Config), MyNickJID = jid:replace_resource(Room, MyNick), PeerNickJID = peer_muc_jid(Config), Size = mod_muc_opt:history_size(iolist_to_binary(ServerHost)), ok = join_new(Config), ct:comment("Putting ~p+1 messages in the history", [Size]), %% Only Size messages will be stored lists:foreach( fun(I) -> Body = xmpp:mk_text(integer_to_binary(I)), send(Config, #message{to = Room, type = groupchat, body = Body}), #message{type = groupchat, from = MyNickJID, body = Body} = recv_message(Config) end, lists:seq(0, Size)), put_event(Config, join), lists:foreach( fun(Type) -> recv_muc_presence(Config, PeerNickJID, Type) end, [available, unavailable, available, unavailable, available, unavailable, available, unavailable]), ok = leave(Config), disconnect(Config). history_slave(Config) -> Room = muc_room_jid(Config), PeerNick = ?config(peer_nick, Config), PeerNickJID = jid:replace_resource(Room, PeerNick), ServerHost = ?config(server_host, Config), Size = mod_muc_opt:history_size(iolist_to_binary(ServerHost)), ct:comment("Waiting for 'join' command from the master"), join = get_event(Config), {History, _, _} = join(Config), ct:comment("Checking ordering of history events"), BodyList = [binary_to_integer(xmpp:get_text(Body)) || #message{type = groupchat, from = From, body = Body} <- History, From == PeerNickJID], BodyList = lists:seq(1, Size), ok = leave(Config), %% If the client wishes to receive no history, it MUST set the 'maxchars' %% attribute to a value of "0" (zero) %% (http://xmpp.org/extensions/xep-0045.html#enter-managehistory) ct:comment("Checking if maxchars=0 yields to no history"), {[], _, _} = join(Config, #muc{history = #muc_history{maxchars = 0}}), ok = leave(Config), ct:comment("Receiving only 10 last stanzas"), {History10, _, _} = join(Config, #muc{history = #muc_history{maxstanzas = 10}}), BodyList10 = [binary_to_integer(xmpp:get_text(Body)) || #message{type = groupchat, from = From, body = Body} <- History10, From == PeerNickJID], BodyList10 = lists:nthtail(Size-10, lists:seq(1, Size)), ok = leave(Config), #delay{stamp = TS} = xmpp:get_subtag(hd(History), #delay{}), ct:comment("Receiving all history without the very first element"), {HistoryWithoutFirst, _, _} = join(Config, #muc{history = #muc_history{since = TS}}), BodyListWithoutFirst = [binary_to_integer(xmpp:get_text(Body)) || #message{type = groupchat, from = From, body = Body} <- HistoryWithoutFirst, From == PeerNickJID], BodyListWithoutFirst = lists:nthtail(1, lists:seq(1, Size)), ok = leave(Config), disconnect(Config). invite_master(Config) -> Room = muc_room_jid(Config), PeerJID = ?config(peer, Config), ok = join_new(Config), wait_for_slave(Config), %% Inviting the peer send(Config, #message{to = Room, type = normal, sub_els = [#muc_user{ invites = [#muc_invite{to = PeerJID}]}]}), #message{from = Room} = DeclineMsg = recv_message(Config), #muc_user{decline = #muc_decline{from = PeerJID}} = xmpp:get_subtag(DeclineMsg, #muc_user{}), ok = leave(Config), disconnect(Config). invite_slave(Config) -> Room = muc_room_jid(Config), wait_for_master(Config), PeerJID = ?config(master, Config), #message{from = Room, type = normal} = Msg = recv_message(Config), #muc_user{invites = [#muc_invite{from = PeerJID}]} = xmpp:get_subtag(Msg, #muc_user{}), %% Decline invitation send(Config, #message{to = Room, sub_els = [#muc_user{ decline = #muc_decline{to = PeerJID}}]}), disconnect(Config). invite_members_only_master(Config) -> Room = muc_room_jid(Config), PeerJID = ?config(slave, Config), ok = join_new(Config), %% Setting the room to members-only [_|_] = set_config(Config, [{membersonly, true}]), wait_for_slave(Config), %% Inviting the peer send(Config, #message{to = Room, type = normal, sub_els = [#muc_user{ invites = [#muc_invite{to = PeerJID}]}]}), #message{from = Room, type = normal} = AffMsg = recv_message(Config), #muc_user{items = [#muc_item{jid = PeerJID, affiliation = member}]} = xmpp:get_subtag(AffMsg, #muc_user{}), ok = leave(Config), disconnect(Config). invite_members_only_slave(Config) -> Room = muc_room_jid(Config), wait_for_master(Config), %% Receiving invitation #message{from = Room, type = normal} = recv_message(Config), disconnect(Config). invite_password_protected_master(Config) -> Room = muc_room_jid(Config), PeerJID = ?config(slave, Config), Password = p1_rand:get_string(), ok = join_new(Config), [104] = set_config(Config, [{passwordprotectedroom, true}, {roomsecret, Password}]), put_event(Config, Password), %% Inviting the peer send(Config, #message{to = Room, type = normal, sub_els = [#muc_user{ invites = [#muc_invite{to = PeerJID}]}]}), ok = leave(Config), disconnect(Config). invite_password_protected_slave(Config) -> Room = muc_room_jid(Config), Password = get_event(Config), %% Receiving invitation #message{from = Room, type = normal} = Msg = recv_message(Config), #muc_user{password = Password} = xmpp:get_subtag(Msg, #muc_user{}), disconnect(Config). voice_request_master(Config) -> Room = muc_room_jid(Config), PeerJID = ?config(slave, Config), PeerNick = ?config(slave_nick, Config), PeerNickJID = jid:replace_resource(Room, PeerNick), ok = join_new(Config), [104] = set_config(Config, [{members_by_default, false}]), wait_for_slave(Config), #muc_user{ items = [#muc_item{role = visitor, jid = PeerJID, affiliation = none}]} = recv_muc_presence(Config, PeerNickJID, available), ct:comment("Receiving voice request"), #message{from = Room, type = normal} = VoiceReq = recv_message(Config), #xdata{type = form, fields = Fs} = xmpp:get_subtag(VoiceReq, #xdata{}), [{jid, PeerJID}, {request_allow, false}, {role, participant}, {roomnick, PeerNick}] = lists:sort(muc_request:decode(Fs)), ct:comment("Approving voice request"), ApprovalFs = muc_request:encode([{jid, PeerJID}, {role, participant}, {roomnick, PeerNick}, {request_allow, true}]), send(Config, #message{to = Room, sub_els = [#xdata{type = submit, fields = ApprovalFs}]}), #muc_user{ items = [#muc_item{role = participant, jid = PeerJID, affiliation = none}]} = recv_muc_presence(Config, PeerNickJID, available), ct:comment("Waiting for the slave to leave"), recv_muc_presence(Config, PeerNickJID, unavailable), ok = leave(Config), disconnect(Config). voice_request_slave(Config) -> Room = muc_room_jid(Config), MyJID = my_jid(Config), MyNick = ?config(nick, Config), MyNickJID = jid:replace_resource(Room, MyNick), wait_for_master(Config), {[], _, _} = join(Config, visitor), ct:comment("Requesting voice"), Fs = muc_request:encode([{role, participant}]), X = #xdata{type = submit, fields = Fs}, send(Config, #message{to = Room, sub_els = [X]}), ct:comment("Waiting to become a participant"), #muc_user{ items = [#muc_item{role = participant, jid = MyJID, affiliation = none}]} = recv_muc_presence(Config, MyNickJID, available), ok = leave(Config), disconnect(Config). change_role_master(Config) -> Room = muc_room_jid(Config), MyJID = my_jid(Config), MyNick = ?config(nick, Config), PeerJID = ?config(slave, Config), PeerNick = ?config(slave_nick, Config), PeerNickJID = jid:replace_resource(Room, PeerNick), ok = join_new(Config), ct:comment("Waiting for the slave to join"), wait_for_slave(Config), #muc_user{items = [#muc_item{role = participant, jid = PeerJID, affiliation = none}]} = recv_muc_presence(Config, PeerNickJID, available), lists:foreach( fun(Role) -> ct:comment("Checking if the slave is not in the roles list"), case get_role(Config, Role) of [#muc_item{jid = MyJID, affiliation = owner, role = moderator, nick = MyNick}] when Role == moderator -> ok; [] -> ok end, Reason = p1_rand:get_string(), put_event(Config, {Role, Reason}), ok = set_role(Config, Role, Reason), ct:comment("Receiving role change to ~s", [Role]), #muc_user{ items = [#muc_item{role = Role, affiliation = none, reason = Reason}]} = recv_muc_presence(Config, PeerNickJID, available), [#muc_item{role = Role, affiliation = none, nick = PeerNick}|_] = get_role(Config, Role) end, [visitor, participant, moderator]), put_event(Config, disconnect), recv_muc_presence(Config, PeerNickJID, unavailable), ok = leave(Config), disconnect(Config). change_role_slave(Config) -> wait_for_master(Config), {[], _, _} = join(Config), change_role_slave(Config, get_event(Config)). change_role_slave(Config, {Role, Reason}) -> Room = muc_room_jid(Config), MyNick = ?config(slave_nick, Config), MyNickJID = jid:replace_resource(Room, MyNick), ct:comment("Receiving role change to ~s", [Role]), #muc_user{status_codes = Codes, items = [#muc_item{role = Role, affiliation = none, reason = Reason}]} = recv_muc_presence(Config, MyNickJID, available), true = lists:member(110, Codes), change_role_slave(Config, get_event(Config)); change_role_slave(Config, disconnect) -> ok = leave(Config), disconnect(Config). change_affiliation_master(Config) -> Room = muc_room_jid(Config), MyJID = my_jid(Config), MyBareJID = jid:remove_resource(MyJID), MyNick = ?config(nick, Config), PeerJID = ?config(slave, Config), PeerBareJID = jid:remove_resource(PeerJID), PeerNick = ?config(slave_nick, Config), PeerNickJID = jid:replace_resource(Room, PeerNick), ok = join_new(Config), ct:comment("Waiting for the slave to join"), wait_for_slave(Config), #muc_user{items = [#muc_item{role = participant, jid = PeerJID, affiliation = none}]} = recv_muc_presence(Config, PeerNickJID, available), lists:foreach( fun({Aff, Role, Status}) -> ct:comment("Checking if slave is not in affiliation list"), case get_affiliation(Config, Aff) of [#muc_item{jid = MyBareJID, affiliation = owner}] when Aff == owner -> ok; [] -> ok end, Reason = p1_rand:get_string(), put_event(Config, {Aff, Role, Status, Reason}), ok = set_affiliation(Config, Aff, Reason), ct:comment("Receiving affiliation change to ~s", [Aff]), #muc_user{ items = [#muc_item{role = Role, affiliation = Aff, actor = Actor, reason = Reason}]} = recv_muc_presence(Config, PeerNickJID, Status), if Aff == outcast -> ct:comment("Checking if actor is set"), #muc_actor{nick = MyNick} = Actor; true -> ok end, Affs = get_affiliation(Config, Aff), ct:comment("Checking if the affiliation was correctly set"), case lists:keyfind(PeerBareJID, #muc_item.jid, Affs) of false when Aff == none -> ok; #muc_item{affiliation = Aff} -> ok end end, [{member, participant, available}, {none, visitor, available}, {admin, moderator, available}, {owner, moderator, available}, {outcast, none, unavailable}]), ok = leave(Config), disconnect(Config). change_affiliation_slave(Config) -> wait_for_master(Config), {[], _, _} = join(Config), change_affiliation_slave(Config, get_event(Config)). change_affiliation_slave(Config, {Aff, Role, Status, Reason}) -> Room = muc_room_jid(Config), PeerNick = ?config(master_nick, Config), MyNick = ?config(nick, Config), MyNickJID = jid:replace_resource(Room, MyNick), ct:comment("Receiving affiliation change to ~s", [Aff]), if Aff == outcast -> #presence{from = Room, type = unavailable} = recv_presence(Config); true -> ok end, #muc_user{status_codes = Codes, items = [#muc_item{role = Role, actor = Actor, affiliation = Aff, reason = Reason}]} = recv_muc_presence(Config, MyNickJID, Status), true = lists:member(110, Codes), if Aff == outcast -> ct:comment("Checking for status code '301' (banned)"), true = lists:member(301, Codes), ct:comment("Checking if actor is set"), #muc_actor{nick = PeerNick} = Actor, disconnect(Config); true -> change_affiliation_slave(Config, get_event(Config)) end. kick_master(Config) -> Room = muc_room_jid(Config), MyNick = ?config(nick, Config), PeerJID = ?config(slave, Config), PeerNick = ?config(slave_nick, Config), PeerNickJID = jid:replace_resource(Room, PeerNick), Reason = <<"Testing">>, ok = join_new(Config), ct:comment("Waiting for the slave to join"), wait_for_slave(Config), #muc_user{items = [#muc_item{role = participant, jid = PeerJID, affiliation = none}]} = recv_muc_presence(Config, PeerNickJID, available), [#muc_item{role = participant, affiliation = none, nick = PeerNick}|_] = get_role(Config, participant), ct:comment("Kicking slave"), ok = set_role(Config, none, Reason), ct:comment("Receiving role change to 'none'"), #muc_user{ status_codes = Codes, items = [#muc_item{role = none, affiliation = none, actor = #muc_actor{nick = MyNick}, reason = Reason}]} = recv_muc_presence(Config, PeerNickJID, unavailable), [] = get_role(Config, participant), ct:comment("Checking if the code is '307' (kicked)"), true = lists:member(307, Codes), ok = leave(Config), disconnect(Config). kick_slave(Config) -> Room = muc_room_jid(Config), PeerNick = ?config(master_nick, Config), MyNick = ?config(nick, Config), MyNickJID = jid:replace_resource(Room, MyNick), Reason = <<"Testing">>, wait_for_master(Config), {[], _, _} = join(Config), ct:comment("Receiving role change to 'none'"), #presence{from = Room, type = unavailable} = recv_presence(Config), #muc_user{status_codes = Codes, items = [#muc_item{role = none, affiliation = none, actor = #muc_actor{nick = PeerNick}, reason = Reason}]} = recv_muc_presence(Config, MyNickJID, unavailable), ct:comment("Checking if codes '110' (self-presence) " "and '307' (kicked) are present"), true = lists:member(110, Codes), true = lists:member(307, Codes), disconnect(Config). destroy_master(Config) -> Reason = <<"Testing">>, Room = muc_room_jid(Config), AltRoom = alt_room_jid(Config), PeerJID = ?config(peer, Config), PeerNick = ?config(slave_nick, Config), PeerNickJID = jid:replace_resource(Room, PeerNick), MyNick = ?config(nick, Config), MyNickJID = jid:replace_resource(Room, MyNick), ok = join_new(Config), ct:comment("Waiting for slave to join"), wait_for_slave(Config), #muc_user{items = [#muc_item{role = participant, jid = PeerJID, affiliation = none}]} = recv_muc_presence(Config, PeerNickJID, available), wait_for_slave(Config), ok = destroy(Config, Reason), ct:comment("Receiving destruction presence"), #presence{from = Room, type = unavailable} = recv_presence(Config), #muc_user{items = [#muc_item{role = none, affiliation = none}], destroy = #muc_destroy{jid = AltRoom, reason = Reason}} = recv_muc_presence(Config, MyNickJID, unavailable), disconnect(Config). destroy_slave(Config) -> Reason = <<"Testing">>, Room = muc_room_jid(Config), AltRoom = alt_room_jid(Config), MyNick = ?config(nick, Config), MyNickJID = jid:replace_resource(Room, MyNick), wait_for_master(Config), {[], _, _} = join(Config), #stanza_error{reason = 'forbidden'} = destroy(Config, Reason), wait_for_master(Config), ct:comment("Receiving destruction presence"), #presence{from = Room, type = unavailable} = recv_presence(Config), #muc_user{items = [#muc_item{role = none, affiliation = none}], destroy = #muc_destroy{jid = AltRoom, reason = Reason}} = recv_muc_presence(Config, MyNickJID, unavailable), disconnect(Config). vcard_master(Config) -> Room = muc_room_jid(Config), PeerNick = ?config(slave_nick, Config), PeerNickJID = jid:replace_resource(Room, PeerNick), FN = p1_rand:get_string(), VCard = #vcard_temp{fn = FN}, ok = join_new(Config), ct:comment("Waiting for slave to join"), wait_for_slave(Config), #muc_user{items = [#muc_item{role = participant, affiliation = none}]} = recv_muc_presence(Config, PeerNickJID, available), #stanza_error{reason = 'item-not-found'} = get_vcard(Config), ok = set_vcard(Config, VCard), VCard = get_vcard(Config), put_event(Config, VCard), recv_muc_presence(Config, PeerNickJID, unavailable), leave = get_event(Config), ok = leave(Config), disconnect(Config). vcard_slave(Config) -> wait_for_master(Config), {[], _, _} = join(Config), [104] = recv_config_change_message(Config), VCard = get_event(Config), VCard = get_vcard(Config), #stanza_error{reason = 'forbidden'} = set_vcard(Config, VCard), ok = leave(Config), VCard = get_vcard(Config), put_event(Config, leave), disconnect(Config). nick_change_master(Config) -> NewNick = p1_rand:get_string(), PeerJID = ?config(peer, Config), PeerNickJID = peer_muc_jid(Config), ok = master_join(Config), put_event(Config, {new_nick, NewNick}), ct:comment("Waiting for nickchange presence from the slave"), #muc_user{status_codes = Codes, items = [#muc_item{jid = PeerJID, nick = NewNick}]} = recv_muc_presence(Config, PeerNickJID, unavailable), ct:comment("Checking if code '303' (nick change) is set"), true = lists:member(303, Codes), ct:comment("Waiting for updated presence from the slave"), PeerNewNickJID = jid:replace_resource(PeerNickJID, NewNick), recv_muc_presence(Config, PeerNewNickJID, available), ct:comment("Waiting for the slave to leave"), recv_muc_presence(Config, PeerNewNickJID, unavailable), ok = leave(Config), disconnect(Config). nick_change_slave(Config) -> MyJID = my_jid(Config), MyNickJID = my_muc_jid(Config), {[], _, _} = slave_join(Config), {new_nick, NewNick} = get_event(Config), MyNewNickJID = jid:replace_resource(MyNickJID, NewNick), ct:comment("Sending new presence"), send(Config, #presence{to = MyNewNickJID}), ct:comment("Receiving nickchange self-presence"), #muc_user{status_codes = Codes1, items = [#muc_item{role = participant, jid = MyJID, nick = NewNick}]} = recv_muc_presence(Config, MyNickJID, unavailable), ct:comment("Checking if codes '110' (self-presence) and " "'303' (nickchange) are present"), lists:member(110, Codes1), lists:member(303, Codes1), ct:comment("Receiving self-presence update"), #muc_user{status_codes = Codes2, items = [#muc_item{jid = MyJID, role = participant}]} = recv_muc_presence(Config, MyNewNickJID, available), ct:comment("Checking if code '110' (self-presence) is set"), lists:member(110, Codes2), NewConfig = set_opt(nick, NewNick, Config), ok = leave(NewConfig), disconnect(NewConfig). config_title_desc_master(Config) -> Title = p1_rand:get_string(), Desc = p1_rand:get_string(), Room = muc_room_jid(Config), PeerNick = ?config(slave_nick, Config), PeerNickJID = jid:replace_resource(Room, PeerNick), ok = master_join(Config), [104] = set_config(Config, [{roomname, Title}, {roomdesc, Desc}]), RoomCfg = get_config(Config), Title = proplists:get_value(roomname, RoomCfg), Desc = proplists:get_value(roomdesc, RoomCfg), recv_muc_presence(Config, PeerNickJID, unavailable), ok = leave(Config), disconnect(Config). config_title_desc_slave(Config) -> {[], _, _} = slave_join(Config), [104] = recv_config_change_message(Config), ok = leave(Config), disconnect(Config). config_public_list_master(Config) -> Room = muc_room_jid(Config), PeerNick = ?config(slave_nick, Config), PeerNickJID = jid:replace_resource(Room, PeerNick), ok = join_new(Config), wait_for_slave(Config), recv_muc_presence(Config, PeerNickJID, available), lists:member(<<"muc_public">>, get_features(Config, Room)), [104] = set_config(Config, [{public_list, false}, {publicroom, false}]), recv_muc_presence(Config, PeerNickJID, unavailable), lists:member(<<"muc_hidden">>, get_features(Config, Room)), wait_for_slave(Config), ok = leave(Config), disconnect(Config). config_public_list_slave(Config) -> Room = muc_room_jid(Config), wait_for_master(Config), PeerNick = ?config(peer_nick, Config), PeerNickJID = peer_muc_jid(Config), [#disco_item{jid = Room}] = disco_items(Config), [#disco_item{jid = PeerNickJID, name = PeerNick}] = disco_room_items(Config), {[], _, _} = join(Config), [104] = recv_config_change_message(Config), ok = leave(Config), [] = disco_items(Config), [] = disco_room_items(Config), wait_for_master(Config), disconnect(Config). config_password_master(Config) -> Password = p1_rand:get_string(), Room = muc_room_jid(Config), PeerNick = ?config(slave_nick, Config), PeerNickJID = jid:replace_resource(Room, PeerNick), ok = join_new(Config), lists:member(<<"muc_unsecured">>, get_features(Config, Room)), [104] = set_config(Config, [{passwordprotectedroom, true}, {roomsecret, Password}]), lists:member(<<"muc_passwordprotected">>, get_features(Config, Room)), put_event(Config, Password), recv_muc_presence(Config, PeerNickJID, available), recv_muc_presence(Config, PeerNickJID, unavailable), ok = leave(Config), disconnect(Config). config_password_slave(Config) -> Password = get_event(Config), #stanza_error{reason = 'not-authorized'} = join(Config), #stanza_error{reason = 'not-authorized'} = join(Config, #muc{password = p1_rand:get_string()}), {[], _, _} = join(Config, #muc{password = Password}), ok = leave(Config), disconnect(Config). config_whois_master(Config) -> Room = muc_room_jid(Config), PeerNickJID = peer_muc_jid(Config), MyNickJID = my_muc_jid(Config), ok = master_join(Config), lists:member(<<"muc_semianonymous">>, get_features(Config, Room)), [172] = set_config(Config, [{whois, anyone}]), lists:member(<<"muc_nonanonymous">>, get_features(Config, Room)), recv_muc_presence(Config, PeerNickJID, unavailable), recv_muc_presence(Config, PeerNickJID, available), send(Config, #presence{to = Room}), recv_muc_presence(Config, MyNickJID, available), [173] = set_config(Config, [{whois, moderators}]), recv_muc_presence(Config, PeerNickJID, unavailable), ok = leave(Config), disconnect(Config). config_whois_slave(Config) -> PeerJID = ?config(peer, Config), PeerNickJID = peer_muc_jid(Config), {[], _, _} = slave_join(Config), ct:comment("Checking if the room becomes non-anonymous (code '172')"), [172] = recv_config_change_message(Config), ct:comment("Re-joining in order to check status codes"), ok = leave(Config), {[], _, Codes} = join(Config), ct:comment("Checking if code '100' (non-anonymous) present"), true = lists:member(100, Codes), ct:comment("Receiving presence from peer with JID exposed"), #muc_user{items = [#muc_item{jid = PeerJID}]} = recv_muc_presence(Config, PeerNickJID, available), ct:comment("Waiting for the room to become anonymous again (code '173')"), [173] = recv_config_change_message(Config), ok = leave(Config), disconnect(Config). config_members_only_master(Config) -> Room = muc_room_jid(Config), PeerJID = ?config(peer, Config), PeerBareJID = jid:remove_resource(PeerJID), PeerNickJID = peer_muc_jid(Config), ok = master_join(Config), lists:member(<<"muc_open">>, get_features(Config, Room)), [104] = set_config(Config, [{membersonly, true}]), #muc_user{status_codes = Codes, items = [#muc_item{jid = PeerJID, affiliation = none, role = none}]} = recv_muc_presence(Config, PeerNickJID, unavailable), ct:comment("Checking if code '322' (non-member) is set"), true = lists:member(322, Codes), lists:member(<<"muc_membersonly">>, get_features(Config, Room)), ct:comment("Waiting for slave to fail joining the room"), set_member = get_event(Config), ok = set_affiliation(Config, member, p1_rand:get_string()), #message{from = Room, type = normal} = Msg = recv_message(Config), #muc_user{items = [#muc_item{jid = PeerBareJID, affiliation = member}]} = xmpp:get_subtag(Msg, #muc_user{}), ct:comment("Asking peer to join"), put_event(Config, join), ct:comment("Waiting for peer to join"), recv_muc_presence(Config, PeerNickJID, available), ok = set_affiliation(Config, none, p1_rand:get_string()), ct:comment("Waiting for peer to be kicked"), #muc_user{status_codes = NewCodes, items = [#muc_item{affiliation = none, role = none}]} = recv_muc_presence(Config, PeerNickJID, unavailable), ct:comment("Checking if code '321' (became non-member in " "members-only room) is set"), true = lists:member(321, NewCodes), ok = leave(Config), disconnect(Config). config_members_only_slave(Config) -> Room = muc_room_jid(Config), MyJID = my_jid(Config), MyNickJID = my_muc_jid(Config), {[], _, _} = slave_join(Config), [104] = recv_config_change_message(Config), ct:comment("Getting kicked because the room has become members-only"), #presence{from = Room, type = unavailable} = recv_presence(Config), #muc_user{status_codes = Codes, items = [#muc_item{jid = MyJID, role = none, affiliation = none}]} = recv_muc_presence(Config, MyNickJID, unavailable), ct:comment("Checking if the code '110' (self-presence) " "and '322' (non-member) is set"), true = lists:member(110, Codes), true = lists:member(322, Codes), ct:comment("Fail trying to join members-only room"), #stanza_error{reason = 'registration-required'} = join(Config), ct:comment("Asking the peer to set us member"), put_event(Config, set_member), ct:comment("Waiting for the peer to ask for join"), join = get_event(Config), {[], _, _} = join(Config, participant, member), #presence{from = Room, type = unavailable} = recv_presence(Config), #muc_user{status_codes = NewCodes, items = [#muc_item{jid = MyJID, role = none, affiliation = none}]} = recv_muc_presence(Config, MyNickJID, unavailable), ct:comment("Checking if the code '110' (self-presence) " "and '321' (became non-member in members-only room) is set"), true = lists:member(110, NewCodes), true = lists:member(321, NewCodes), disconnect(Config). config_moderated_master(Config) -> Room = muc_room_jid(Config), PeerNickJID = peer_muc_jid(Config), ok = master_join(Config), lists:member(<<"muc_moderated">>, get_features(Config, Room)), ok = set_role(Config, visitor, p1_rand:get_string()), #muc_user{items = [#muc_item{role = visitor}]} = recv_muc_presence(Config, PeerNickJID, available), set_unmoderated = get_event(Config), [104] = set_config(Config, [{moderatedroom, false}]), #message{from = PeerNickJID, type = groupchat} = recv_message(Config), recv_muc_presence(Config, PeerNickJID, unavailable), lists:member(<<"muc_unmoderated">>, get_features(Config, Room)), ok = leave(Config), disconnect(Config). config_moderated_slave(Config) -> Room = muc_room_jid(Config), MyNickJID = my_muc_jid(Config), {[], _, _} = slave_join(Config), #muc_user{items = [#muc_item{role = visitor}]} = recv_muc_presence(Config, MyNickJID, available), send(Config, #message{to = Room, type = groupchat}), ErrMsg = #message{from = Room, type = error} = recv_message(Config), #stanza_error{reason = 'forbidden'} = xmpp:get_error(ErrMsg), put_event(Config, set_unmoderated), [104] = recv_config_change_message(Config), send(Config, #message{to = Room, type = groupchat}), #message{from = MyNickJID, type = groupchat} = recv_message(Config), ok = leave(Config), disconnect(Config). config_private_messages_master(Config) -> PeerNickJID = peer_muc_jid(Config), ok = master_join(Config), ct:comment("Waiting for a private message from the slave"), #message{from = PeerNickJID, type = chat} = recv_message(Config), ok = set_role(Config, visitor, <<>>), ct:comment("Waiting for the peer to become a visitor"), recv_muc_presence(Config, PeerNickJID, available), ct:comment("Waiting for a private message from the slave"), #message{from = PeerNickJID, type = chat} = recv_message(Config), [104] = set_config(Config, [{allow_private_messages_from_visitors, moderators}]), ct:comment("Waiting for a private message from the slave"), #message{from = PeerNickJID, type = chat} = recv_message(Config), [104] = set_config(Config, [{allow_private_messages_from_visitors, nobody}]), wait_for_slave(Config), [104] = set_config(Config, [{allow_private_messages_from_visitors, anyone}, {allowpm, none}]), ct:comment("Fail trying to send a private message"), send(Config, #message{to = PeerNickJID, type = chat}), #message{from = PeerNickJID, type = error} = ErrMsg = recv_message(Config), #stanza_error{reason = 'forbidden'} = xmpp:get_error(ErrMsg), ok = set_role(Config, participant, <<>>), ct:comment("Waiting for the peer to become a participant"), recv_muc_presence(Config, PeerNickJID, available), ct:comment("Waiting for the peer to leave"), recv_muc_presence(Config, PeerNickJID, unavailable), ok = leave(Config), disconnect(Config). config_private_messages_slave(Config) -> MyNickJID = my_muc_jid(Config), PeerNickJID = peer_muc_jid(Config), {[], _, _} = slave_join(Config), ct:comment("Sending a private message"), send(Config, #message{to = PeerNickJID, type = chat}), ct:comment("Waiting to become a visitor"), #muc_user{items = [#muc_item{role = visitor}]} = recv_muc_presence(Config, MyNickJID, available), ct:comment("Sending a private message"), send(Config, #message{to = PeerNickJID, type = chat}), [104] = recv_config_change_message(Config), ct:comment("Sending a private message"), send(Config, #message{to = PeerNickJID, type = chat}), [104] = recv_config_change_message(Config), ct:comment("Fail trying to send a private message"), send(Config, #message{to = PeerNickJID, type = chat}), #message{from = PeerNickJID, type = error} = ErrMsg1 = recv_message(Config), #stanza_error{reason = 'forbidden'} = xmpp:get_error(ErrMsg1), wait_for_master(Config), [104] = recv_config_change_message(Config), ct:comment("Waiting to become a participant again"), #muc_user{items = [#muc_item{role = participant}]} = recv_muc_presence(Config, MyNickJID, available), ct:comment("Fail trying to send a private message"), send(Config, #message{to = PeerNickJID, type = chat}), #message{from = PeerNickJID, type = error} = ErrMsg2 = recv_message(Config), #stanza_error{reason = 'forbidden'} = xmpp:get_error(ErrMsg2), ok = leave(Config), disconnect(Config). config_query_master(Config) -> PeerNickJID = peer_muc_jid(Config), ok = join_new(Config), wait_for_slave(Config), recv_muc_presence(Config, PeerNickJID, available), ct:comment("Receiving IQ query from the slave"), #iq{type = get, from = PeerNickJID, id = I, sub_els = [#ping{}]} = recv_iq(Config), send(Config, #iq{type = result, to = PeerNickJID, id = I}), [104] = set_config(Config, [{allow_query_users, false}]), ct:comment("Fail trying to send IQ"), #iq{type = error, from = PeerNickJID} = Err = send_recv(Config, #iq{type = get, to = PeerNickJID, sub_els = [#ping{}]}), #stanza_error{reason = 'not-allowed'} = xmpp:get_error(Err), recv_muc_presence(Config, PeerNickJID, unavailable), ok = leave(Config), disconnect(Config). config_query_slave(Config) -> PeerNickJID = peer_muc_jid(Config), wait_for_master(Config), ct:comment("Checking if IQ queries are denied from non-occupants"), #iq{type = error, from = PeerNickJID} = Err1 = send_recv(Config, #iq{type = get, to = PeerNickJID, sub_els = [#ping{}]}), #stanza_error{reason = 'not-acceptable'} = xmpp:get_error(Err1), {[], _, _} = join(Config), ct:comment("Sending IQ to the master"), #iq{type = result, from = PeerNickJID, sub_els = []} = send_recv(Config, #iq{to = PeerNickJID, type = get, sub_els = [#ping{}]}), [104] = recv_config_change_message(Config), ct:comment("Fail trying to send IQ"), #iq{type = error, from = PeerNickJID} = Err2 = send_recv(Config, #iq{type = get, to = PeerNickJID, sub_els = [#ping{}]}), #stanza_error{reason = 'not-allowed'} = xmpp:get_error(Err2), ok = leave(Config), disconnect(Config). config_allow_invites_master(Config) -> Room = muc_room_jid(Config), PeerJID = ?config(peer, Config), PeerNickJID = peer_muc_jid(Config), ok = master_join(Config), [104] = set_config(Config, [{allowinvites, true}]), ct:comment("Receiving an invitation from the slave"), #message{from = Room, type = normal} = recv_message(Config), [104] = set_config(Config, [{allowinvites, false}]), send_invitation = get_event(Config), ct:comment("Sending an invitation"), send(Config, #message{to = Room, type = normal, sub_els = [#muc_user{ invites = [#muc_invite{to = PeerJID}]}]}), recv_muc_presence(Config, PeerNickJID, unavailable), ok = leave(Config), disconnect(Config). config_allow_invites_slave(Config) -> Room = muc_room_jid(Config), PeerJID = ?config(peer, Config), InviteMsg = #message{to = Room, type = normal, sub_els = [#muc_user{ invites = [#muc_invite{to = PeerJID}]}]}, {[], _, _} = slave_join(Config), [104] = recv_config_change_message(Config), ct:comment("Sending an invitation"), send(Config, InviteMsg), [104] = recv_config_change_message(Config), ct:comment("Fail sending an invitation"), send(Config, InviteMsg), #message{from = Room, type = error} = Err = recv_message(Config), #stanza_error{reason = 'not-allowed'} = xmpp:get_error(Err), ct:comment("Checking if the master is still able to send invitations"), put_event(Config, send_invitation), #message{from = Room, type = normal} = recv_message(Config), ok = leave(Config), disconnect(Config). config_visitor_status_master(Config) -> PeerNickJID = peer_muc_jid(Config), Status = xmpp:mk_text(p1_rand:get_string()), ok = join_new(Config), [104] = set_config(Config, [{members_by_default, false}]), ct:comment("Asking the slave to join as a visitor"), put_event(Config, {join, Status}), #muc_user{items = [#muc_item{role = visitor}]} = recv_muc_presence(Config, PeerNickJID, available), ct:comment("Receiving status change from the visitor"), #presence{from = PeerNickJID, status = Status} = recv_presence(Config), [104] = set_config(Config, [{allow_visitor_status, false}]), ct:comment("Receiving status change with stripped"), #presence{from = PeerNickJID, status = []} = recv_presence(Config), ct:comment("Waiting for the slave to leave"), recv_muc_presence(Config, PeerNickJID, unavailable), ok = leave(Config), disconnect(Config). config_visitor_status_slave(Config) -> Room = muc_room_jid(Config), MyNickJID = my_muc_jid(Config), ct:comment("Waiting for 'join' command from the master"), {join, Status} = get_event(Config), {[], _, _} = join(Config, visitor, none), ct:comment("Sending status change"), send(Config, #presence{to = Room, status = Status}), #presence{from = MyNickJID, status = Status} = recv_presence(Config), [104] = recv_config_change_message(Config), ct:comment("Sending status change again"), send(Config, #presence{to = Room, status = Status}), #presence{from = MyNickJID, status = []} = recv_presence(Config), ok = leave(Config), disconnect(Config). config_allow_voice_requests_master(Config) -> PeerNickJID = peer_muc_jid(Config), ok = join_new(Config), [104] = set_config(Config, [{members_by_default, false}]), ct:comment("Asking the slave to join as a visitor"), put_event(Config, join), #muc_user{items = [#muc_item{role = visitor}]} = recv_muc_presence(Config, PeerNickJID, available), [104] = set_config(Config, [{allow_voice_requests, false}]), ct:comment("Waiting for the slave to leave"), recv_muc_presence(Config, PeerNickJID, unavailable), ok = leave(Config), disconnect(Config). config_allow_voice_requests_slave(Config) -> Room = muc_room_jid(Config), ct:comment("Waiting for 'join' command from the master"), join = get_event(Config), {[], _, _} = join(Config, visitor), [104] = recv_config_change_message(Config), ct:comment("Fail sending voice request"), Fs = muc_request:encode([{role, participant}]), X = #xdata{type = submit, fields = Fs}, send(Config, #message{to = Room, sub_els = [X]}), #message{from = Room, type = error} = Err = recv_message(Config), #stanza_error{reason = 'forbidden'} = xmpp:get_error(Err), ok = leave(Config), disconnect(Config). config_voice_request_interval_master(Config) -> Room = muc_room_jid(Config), PeerJID = ?config(peer, Config), PeerNick = ?config(peer_nick, Config), PeerNickJID = peer_muc_jid(Config), ok = join_new(Config), [104] = set_config(Config, [{members_by_default, false}]), ct:comment("Asking the slave to join as a visitor"), put_event(Config, join), #muc_user{items = [#muc_item{role = visitor}]} = recv_muc_presence(Config, PeerNickJID, available), [104] = set_config(Config, [{voice_request_min_interval, 5}]), ct:comment("Receiving a voice request from slave"), #message{from = Room, type = normal} = recv_message(Config), ct:comment("Deny voice request at first"), Fs = muc_request:encode([{jid, PeerJID}, {role, participant}, {roomnick, PeerNick}, {request_allow, false}]), send(Config, #message{to = Room, sub_els = [#xdata{type = submit, fields = Fs}]}), put_event(Config, denied), ct:comment("Waiting for repeated voice request from the slave"), #message{from = Room, type = normal} = recv_message(Config), ct:comment("Waiting for the slave to leave"), recv_muc_presence(Config, PeerNickJID, unavailable), ok = leave(Config), disconnect(Config). config_voice_request_interval_slave(Config) -> Room = muc_room_jid(Config), Fs = muc_request:encode([{role, participant}]), X = #xdata{type = submit, fields = Fs}, ct:comment("Waiting for 'join' command from the master"), join = get_event(Config), {[], _, _} = join(Config, visitor), [104] = recv_config_change_message(Config), ct:comment("Sending voice request"), send(Config, #message{to = Room, sub_els = [X]}), ct:comment("Waiting for the master to deny our voice request"), denied = get_event(Config), ct:comment("Requesting voice again"), send(Config, #message{to = Room, sub_els = [X]}), ct:comment("Receiving voice request error because we're sending to fast"), #message{from = Room, type = error} = Err = recv_message(Config), #stanza_error{reason = 'resource-constraint'} = xmpp:get_error(Err), ct:comment("Waiting for 5 seconds"), timer:sleep(timer:seconds(5)), ct:comment("Repeating again"), send(Config, #message{to = Room, sub_els = [X]}), ok = leave(Config), disconnect(Config). config_visitor_nickchange_master(Config) -> PeerNickJID = peer_muc_jid(Config), ok = join_new(Config), [104] = set_config(Config, [{members_by_default, false}]), ct:comment("Asking the slave to join as a visitor"), put_event(Config, join), ct:comment("Waiting for the slave to join"), #muc_user{items = [#muc_item{role = visitor}]} = recv_muc_presence(Config, PeerNickJID, available), [104] = set_config(Config, [{allow_visitor_nickchange, false}]), ct:comment("Waiting for the slave to leave"), recv_muc_presence(Config, PeerNickJID, unavailable), ok = leave(Config), disconnect(Config). config_visitor_nickchange_slave(Config) -> NewNick = p1_rand:get_string(), MyNickJID = my_muc_jid(Config), MyNewNickJID = jid:replace_resource(MyNickJID, NewNick), ct:comment("Waiting for 'join' command from the master"), join = get_event(Config), {[], _, _} = join(Config, visitor), [104] = recv_config_change_message(Config), ct:comment("Fail trying to change nickname"), send(Config, #presence{to = MyNewNickJID}), #presence{from = MyNewNickJID, type = error} = Err = recv_presence(Config), #stanza_error{reason = 'not-allowed'} = xmpp:get_error(Err), ok = leave(Config), disconnect(Config). register_master(Config) -> MUC = muc_jid(Config), %% Register nick "master1" register_nick(Config, MUC, <<"">>, <<"master1">>), %% Unregister nick "master1" via jabber:register #iq{type = result, sub_els = []} = send_recv(Config, #iq{type = set, to = MUC, sub_els = [#register{remove = true}]}), %% Register nick "master2" register_nick(Config, MUC, <<"">>, <<"master2">>), %% Now register nick "master" register_nick(Config, MUC, <<"master2">>, <<"master">>), %% Wait for slave to fail trying to register nick "master" wait_for_slave(Config), wait_for_slave(Config), %% Now register empty ("") nick, which means we're unregistering register_nick(Config, MUC, <<"master">>, <<"">>), disconnect(Config). register_slave(Config) -> MUC = muc_jid(Config), wait_for_master(Config), %% Trying to register occupied nick "master" Fs = muc_register:encode([{roomnick, <<"master">>}]), X = #xdata{type = submit, fields = Fs}, #iq{type = error} = send_recv(Config, #iq{type = set, to = MUC, sub_els = [#register{xdata = X}]}), wait_for_master(Config), disconnect(Config). %%%=================================================================== %%% Internal functions %%%=================================================================== single_test(T) -> list_to_atom("muc_" ++ atom_to_list(T)). master_slave_test(T) -> {list_to_atom("muc_" ++ atom_to_list(T)), [parallel], [list_to_atom("muc_" ++ atom_to_list(T) ++ "_master"), list_to_atom("muc_" ++ atom_to_list(T) ++ "_slave")]}. recv_muc_presence(Config, From, Type) -> Pres = #presence{from = From, type = Type} = recv_presence(Config), xmpp:get_subtag(Pres, #muc_user{}). join_new(Config) -> join_new(Config, muc_room_jid(Config)). join_new(Config, Room) -> MyJID = my_jid(Config), MyNick = ?config(nick, Config), MyNickJID = jid:replace_resource(Room, MyNick), ct:comment("Joining new room ~p", [Room]), send(Config, #presence{to = MyNickJID, sub_els = [#muc{}]}), #presence{from = Room, type = available} = recv_presence(Config), %% As per XEP-0045 we MUST receive stanzas in the following order: %% 1. In-room presence from other occupants %% 2. In-room presence from the joining entity itself (so-called "self-presence") %% 3. Room history (if any) %% 4. The room subject %% 5. Live messages, presence updates, new user joins, etc. %% As this is the newly created room, we receive only the 2nd and 4th stanza. #muc_user{ status_codes = Codes, items = [#muc_item{role = moderator, jid = MyJID, affiliation = owner}]} = recv_muc_presence(Config, MyNickJID, available), ct:comment("Checking if codes '110' (self-presence) and " "'201' (new room) is set"), true = lists:member(110, Codes), true = lists:member(201, Codes), ct:comment("Receiving empty room subject"), #message{from = Room, type = groupchat, body = [], subject = [#text{data = <<>>}]} = recv_message(Config), case ?config(persistent_room, Config) of true -> [104] = set_config(Config, [{persistentroom, true}], Room), ok; false -> ok end. recv_history_and_subject(Config) -> ct:comment("Receiving room history and/or subject"), recv_history_and_subject(Config, []). recv_history_and_subject(Config, History) -> Room = muc_room_jid(Config), #message{type = groupchat, subject = Subj, body = Body, thread = Thread} = Msg = recv_message(Config), case xmpp:get_subtag(Msg, #delay{}) of #delay{from = Room} -> recv_history_and_subject(Config, [Msg|History]); false when Subj /= [], Body == [], Thread == undefined -> {lists:reverse(History), Msg} end. join(Config) -> join(Config, participant, none, #muc{}). join(Config, Role) when is_atom(Role) -> join(Config, Role, none, #muc{}); join(Config, #muc{} = SubEl) -> join(Config, participant, none, SubEl). join(Config, Role, Aff) when is_atom(Role), is_atom(Aff) -> join(Config, Role, Aff, #muc{}); join(Config, Role, #muc{} = SubEl) when is_atom(Role) -> join(Config, Role, none, SubEl). join(Config, Role, Aff, SubEl) -> ct:comment("Joining existing room as ~s/~s", [Aff, Role]), MyJID = my_jid(Config), Room = muc_room_jid(Config), MyNick = ?config(nick, Config), MyNickJID = jid:replace_resource(Room, MyNick), PeerNick = ?config(peer_nick, Config), PeerNickJID = jid:replace_resource(Room, PeerNick), send(Config, #presence{to = MyNickJID, sub_els = [SubEl]}), case recv_presence(Config) of #presence{type = error, from = MyNickJID} = Err -> xmpp:get_subtag(Err, #stanza_error{}); #presence{from = Room, type = available} -> case recv_presence(Config) of #presence{type = available, from = PeerNickJID} = Pres -> #muc_user{items = [#muc_item{role = moderator, affiliation = owner}]} = xmpp:get_subtag(Pres, #muc_user{}), ct:comment("Receiving initial self-presence"), #muc_user{status_codes = Codes, items = [#muc_item{role = Role, jid = MyJID, affiliation = Aff}]} = recv_muc_presence(Config, MyNickJID, available), ct:comment("Checking if code '110' (self-presence) is set"), true = lists:member(110, Codes), {History, Subj} = recv_history_and_subject(Config), {History, Subj, Codes}; #presence{type = available, from = MyNickJID} = Pres -> #muc_user{status_codes = Codes, items = [#muc_item{role = Role, jid = MyJID, affiliation = Aff}]} = xmpp:get_subtag(Pres, #muc_user{}), ct:comment("Checking if code '110' (self-presence) is set"), true = lists:member(110, Codes), {History, Subj} = recv_history_and_subject(Config), {empty, History, Subj, Codes} end end. leave(Config) -> leave(Config, muc_room_jid(Config)). leave(Config, Room) -> MyJID = my_jid(Config), MyNick = ?config(nick, Config), MyNickJID = jid:replace_resource(Room, MyNick), Mode = ?config(mode, Config), IsPersistent = ?config(persistent_room, Config), if Mode /= slave, IsPersistent -> [104] = set_config(Config, [{persistentroom, false}], Room); true -> ok end, ct:comment("Leaving the room"), send(Config, #presence{to = MyNickJID, type = unavailable}), #presence{from = Room, type = unavailable} = recv_presence(Config), #muc_user{ status_codes = Codes, items = [#muc_item{role = none, jid = MyJID}]} = recv_muc_presence(Config, MyNickJID, unavailable), ct:comment("Checking if code '110' (self-presence) is set"), true = lists:member(110, Codes), ok. get_config(Config) -> ct:comment("Get room config"), Room = muc_room_jid(Config), case send_recv(Config, #iq{type = get, to = Room, sub_els = [#muc_owner{}]}) of #iq{type = result, sub_els = [#muc_owner{config = #xdata{type = form} = X}]} -> muc_roomconfig:decode(X#xdata.fields); #iq{type = error} = Err -> xmpp:get_subtag(Err, #stanza_error{}) end. set_config(Config, RoomConfig) -> set_config(Config, RoomConfig, muc_room_jid(Config)). set_config(Config, RoomConfig, Room) -> ct:comment("Set room config: ~p", [RoomConfig]), Fs = case RoomConfig of [] -> []; _ -> muc_roomconfig:encode(RoomConfig) end, case send_recv(Config, #iq{type = set, to = Room, sub_els = [#muc_owner{config = #xdata{type = submit, fields = Fs}}]}) of #iq{type = result, sub_els = []} -> #presence{from = Room, type = available} = recv_presence(Config), #message{from = Room, type = groupchat} = Msg = recv_message(Config), #muc_user{status_codes = Codes} = xmpp:get_subtag(Msg, #muc_user{}), lists:sort(Codes); #iq{type = error} = Err -> xmpp:get_subtag(Err, #stanza_error{}) end. create_persistent(Config) -> [_|_] = get_config(Config), [] = set_config(Config, [{persistentroom, true}], false), ok. destroy(Config) -> destroy(Config, <<>>). destroy(Config, Reason) -> Room = muc_room_jid(Config), AltRoom = alt_room_jid(Config), ct:comment("Destroying a room"), case send_recv(Config, #iq{type = set, to = Room, sub_els = [#muc_owner{destroy = #muc_destroy{ reason = Reason, jid = AltRoom}}]}) of #iq{type = result, sub_els = []} -> ok; #iq{type = error} = Err -> xmpp:get_subtag(Err, #stanza_error{}) end. disco_items(Config) -> MUC = muc_jid(Config), ct:comment("Performing disco#items request to ~s", [jid:encode(MUC)]), #iq{type = result, from = MUC, sub_els = [DiscoItems]} = send_recv(Config, #iq{type = get, to = MUC, sub_els = [#disco_items{}]}), lists:keysort(#disco_item.jid, DiscoItems#disco_items.items). disco_room_items(Config) -> Room = muc_room_jid(Config), #iq{type = result, from = Room, sub_els = [DiscoItems]} = send_recv(Config, #iq{type = get, to = Room, sub_els = [#disco_items{}]}), DiscoItems#disco_items.items. get_affiliations(Config, Aff) -> Room = muc_room_jid(Config), case send_recv(Config, #iq{type = get, to = Room, sub_els = [#muc_admin{items = [#muc_item{affiliation = Aff}]}]}) of #iq{type = result, sub_els = [#muc_admin{items = Items}]} -> Items; #iq{type = error} = Err -> xmpp:get_subtag(Err, #stanza_error{}) end. master_join(Config) -> Room = muc_room_jid(Config), PeerJID = ?config(slave, Config), PeerNick = ?config(slave_nick, Config), PeerNickJID = jid:replace_resource(Room, PeerNick), ok = join_new(Config), wait_for_slave(Config), #muc_user{items = [#muc_item{jid = PeerJID, role = participant, affiliation = none}]} = recv_muc_presence(Config, PeerNickJID, available), ok. slave_join(Config) -> wait_for_master(Config), join(Config). set_role(Config, Role, Reason) -> ct:comment("Changing role to ~s", [Role]), Room = muc_room_jid(Config), PeerNick = ?config(slave_nick, Config), case send_recv( Config, #iq{type = set, to = Room, sub_els = [#muc_admin{ items = [#muc_item{role = Role, reason = Reason, nick = PeerNick}]}]}) of #iq{type = result, sub_els = []} -> ok; #iq{type = error} = Err -> xmpp:get_subtag(Err, #stanza_error{}) end. get_role(Config, Role) -> ct:comment("Requesting list for role '~s'", [Role]), Room = muc_room_jid(Config), case send_recv( Config, #iq{type = get, to = Room, sub_els = [#muc_admin{ items = [#muc_item{role = Role}]}]}) of #iq{type = result, sub_els = [#muc_admin{items = Items}]} -> lists:keysort(#muc_item.affiliation, Items); #iq{type = error} = Err -> xmpp:get_subtag(Err, #stanza_error{}) end. set_affiliation(Config, Aff, Reason) -> ct:comment("Changing affiliation to ~s", [Aff]), Room = muc_room_jid(Config), PeerJID = ?config(slave, Config), PeerBareJID = jid:remove_resource(PeerJID), case send_recv( Config, #iq{type = set, to = Room, sub_els = [#muc_admin{ items = [#muc_item{affiliation = Aff, reason = Reason, jid = PeerBareJID}]}]}) of #iq{type = result, sub_els = []} -> ok; #iq{type = error} = Err -> xmpp:get_subtag(Err, #stanza_error{}) end. get_affiliation(Config, Aff) -> ct:comment("Requesting list for affiliation '~s'", [Aff]), Room = muc_room_jid(Config), case send_recv( Config, #iq{type = get, to = Room, sub_els = [#muc_admin{ items = [#muc_item{affiliation = Aff}]}]}) of #iq{type = result, sub_els = [#muc_admin{items = Items}]} -> Items; #iq{type = error} = Err -> xmpp:get_subtag(Err, #stanza_error{}) end. set_vcard(Config, VCard) -> Room = muc_room_jid(Config), ct:comment("Setting vCard for ~s", [jid:encode(Room)]), case send_recv(Config, #iq{type = set, to = Room, sub_els = [VCard]}) of #iq{type = result, sub_els = []} -> [104] = recv_config_change_message(Config), ok; #iq{type = error} = Err -> xmpp:get_subtag(Err, #stanza_error{}) end. get_vcard(Config) -> Room = muc_room_jid(Config), ct:comment("Retrieving vCard from ~s", [jid:encode(Room)]), case send_recv(Config, #iq{type = get, to = Room, sub_els = [#vcard_temp{}]}) of #iq{type = result, sub_els = [VCard]} -> VCard; #iq{type = error} = Err -> xmpp:get_subtag(Err, #stanza_error{}) end. recv_config_change_message(Config) -> ct:comment("Receiving configuration change notification message"), Room = muc_room_jid(Config), #presence{from = Room, type = available} = recv_presence(Config), #message{type = groupchat, from = Room} = Msg = recv_message(Config), #muc_user{status_codes = Codes} = xmpp:get_subtag(Msg, #muc_user{}), lists:sort(Codes). register_nick(Config, MUC, PrevNick, Nick) -> PrevRegistered = if PrevNick /= <<"">> -> true; true -> false end, NewRegistered = if Nick /= <<"">> -> true; true -> false end, ct:comment("Requesting registration form"), #iq{type = result, sub_els = [#register{registered = PrevRegistered, xdata = #xdata{type = form, fields = FsWithoutNick}}]} = send_recv(Config, #iq{type = get, to = MUC, sub_els = [#register{}]}), ct:comment("Checking if previous nick is registered"), PrevNick = proplists:get_value( roomnick, muc_register:decode(FsWithoutNick)), X = #xdata{type = submit, fields = muc_register:encode([{roomnick, Nick}])}, ct:comment("Submitting registration form"), #iq{type = result, sub_els = []} = send_recv(Config, #iq{type = set, to = MUC, sub_els = [#register{xdata = X}]}), ct:comment("Checking if new nick was registered"), #iq{type = result, sub_els = [#register{registered = NewRegistered, xdata = #xdata{type = form, fields = FsWithNick}}]} = send_recv(Config, #iq{type = get, to = MUC, sub_els = [#register{}]}), Nick = proplists:get_value( roomnick, muc_register:decode(FsWithNick)). subscribe(Config, Events, Room) -> MyNick = ?config(nick, Config), case send_recv(Config, #iq{type = set, to = Room, sub_els = [#muc_subscribe{nick = MyNick, events = Events}]}) of #iq{type = result, sub_els = [#muc_subscribe{events = ResEvents}]} -> lists:sort(ResEvents); #iq{type = error} = Err -> xmpp:get_error(Err) end. unsubscribe(Config, Room) -> case send_recv(Config, #iq{type = set, to = Room, sub_els = [#muc_unsubscribe{}]}) of #iq{type = result, sub_els = []} -> ok; #iq{type = error} = Err -> xmpp:get_error(Err) end.