diff options
author | Evgeniy Khramtsov <ekhramtsov@process-one.net> | 2016-10-17 13:37:23 +0300 |
---|---|---|
committer | Evgeniy Khramtsov <ekhramtsov@process-one.net> | 2016-10-17 13:37:23 +0300 |
commit | 67720c77137da2e80907f67dccc96fcbdb44c3bf (patch) | |
tree | d3b44d577312ee0f94c46d3c03691279d329f16d /test | |
parent | Update riakc to support r19 (diff) |
Add more MUC tests
Diffstat (limited to 'test')
-rw-r--r-- | test/ejabberd_SUITE.erl | 581 | ||||
-rw-r--r-- | test/ejabberd_SUITE_data/ejabberd.yml | 1 | ||||
-rw-r--r-- | test/muc_tests.erl | 1877 | ||||
-rw-r--r-- | test/suite.erl | 80 |
4 files changed, 2147 insertions, 392 deletions
diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl index b4249bbdf..1a5c89076 100644 --- a/test/ejabberd_SUITE.erl +++ b/test/ejabberd_SUITE.erl @@ -10,23 +10,23 @@ -compile(export_all). --import(suite, [init_config/1, connect/1, disconnect/1, - recv/1, send/2, send_recv/2, my_jid/1, server_jid/1, - pubsub_jid/1, proxy_jid/1, muc_jid/1, muc_room_jid/1, - mix_jid/1, mix_room_jid/1, get_features/2, re_register/1, - is_feature_advertised/2, subscribe_to_events/1, +-import(suite, [init_config/1, connect/1, disconnect/1, recv_message/1, + recv/1, recv_presence/1, send/2, send_recv/2, my_jid/1, + server_jid/1, pubsub_jid/1, proxy_jid/1, muc_jid/1, + muc_room_jid/1, my_muc_jid/1, peer_muc_jid/1, + mix_jid/1, mix_room_jid/1, get_features/2, recv_iq/1, + re_register/1, is_feature_advertised/2, subscribe_to_events/1, is_feature_advertised/3, set_opt/3, auth_SASL/2, - wait_for_master/1, wait_for_slave/1, - make_iq_result/1, start_event_relay/0, + wait_for_master/1, wait_for_slave/1, flush/1, + make_iq_result/1, start_event_relay/0, alt_room_jid/1, stop_event_relay/1, put_event/2, get_event/1, bind/1, auth/1, auth/2, open_session/1, open_session/2, zlib/1, starttls/1, starttls/2, close_socket/1, init_stream/1, auth_legacy/2, auth_legacy/3, tcp_connect/1, send_text/2]). - -include("suite.hrl"). suite() -> - [{timetrap, {seconds,60}}]. + [{timetrap, {seconds, 30}}]. init_per_suite(Config) -> NewConfig = init_config(Config), @@ -83,7 +83,7 @@ init_per_group(Group, Config) -> do_init_per_group(no_db, Config) -> re_register(Config), - Config; + set_opt(persistent_room, false, Config); do_init_per_group(mnesia, Config) -> mod_muc:shutdown_rooms(?MNESIA_VHOST), set_opt(server, ?MNESIA_VHOST, Config); @@ -202,6 +202,10 @@ init_per_testcase(TestCase, OrigConfig) -> Test = atom_to_list(TestCase), IsMaster = lists:suffix("_master", Test), IsSlave = lists:suffix("_slave", Test), + Mode = if IsSlave -> slave; + IsMaster -> master; + true -> single + end, IsCarbons = lists:prefix("carbons_", Test), IsReplaced = lists:prefix("replaced_", Test), User = if IsReplaced -> <<"test_single!#$%^*()`~+-;_=[]{}|\\">>; @@ -209,6 +213,10 @@ init_per_testcase(TestCase, OrigConfig) -> IsSlave -> <<"test_slave!#$%^*()`~+-;_=[]{}|\\">>; true -> <<"test_single!#$%^*()`~+-;_=[]{}|\\">> end, + Nick = if IsSlave -> ?config(slave_nick, OrigConfig); + IsMaster -> ?config(master_nick, OrigConfig); + true -> ?config(nick, OrigConfig) + end, MyResource = if IsMaster and IsCarbons -> MasterResource; IsSlave and IsCarbons -> SlaveResource; true -> Resource @@ -227,10 +235,23 @@ init_per_testcase(TestCase, OrigConfig) -> true -> jid:make(<<"test_master!#$%^*()`~+-;_=[]{}|\\">>, Server, Resource) end, - Config = set_opt(user, User, - set_opt(slave, Slave, - set_opt(master, Master, - set_opt(resource, MyResource, OrigConfig)))), + Config1 = set_opt(user, User, + set_opt(slave, Slave, + set_opt(master, Master, + set_opt(resource, MyResource, + set_opt(nick, Nick, + set_opt(mode, Mode, OrigConfig)))))), + Config2 = if IsSlave -> + set_opt(peer_nick, ?config(master_nick, Config1), Config1); + IsMaster -> + set_opt(peer_nick, ?config(slave_nick, Config1), Config1); + true -> + Config1 + end, + Config = if IsSlave -> set_opt(peer, Master, Config2); + IsMaster -> set_opt(peer, Slave, Config2); + true -> Config2 + end, case Test of "test_connect" ++ _ -> Config; @@ -320,6 +341,8 @@ no_db_tests() -> [sm, sm_resume, sm_resume_failed]}, + muc_tests:single_cases(), + muc_tests:master_slave_cases(), {test_proxy65, [parallel], [proxy65_master, proxy65_slave]}, {replaced, [parallel], @@ -369,9 +392,9 @@ db_tests(riak) -> privacy, blocking, vcard, + muc_tests:single_cases(), test_unregister]}, - {test_muc_register, [parallel], - [muc_register_master, muc_register_slave]}, + muc_tests:master_slave_cases(), {test_roster_subscribe, [parallel], [roster_subscribe_master, roster_subscribe_slave]}, @@ -379,8 +402,6 @@ db_tests(riak) -> [flex_offline_master, flex_offline_slave]}, {test_offline, [sequence], [offline_master, offline_slave]}, - {test_muc, [parallel], - [muc_master, muc_slave]}, {test_announce, [sequence], [announce_master, announce_slave]}, {test_vcard_xupdate, [parallel], @@ -403,10 +424,10 @@ db_tests(DB) when DB == mnesia; DB == redis -> blocking, vcard, pubsub_single_tests(), + muc_tests:single_cases(), test_unregister]}, + muc_tests:master_slave_cases(), pubsub_multiple_tests(), - {test_muc_register, [parallel], - [muc_register_master, muc_register_slave]}, {test_mix, [parallel], [mix_master, mix_slave]}, {test_roster_subscribe, [parallel], @@ -424,8 +445,6 @@ db_tests(DB) when DB == mnesia; DB == redis -> [carbons_master, carbons_slave]}, {test_client_state, [parallel], [client_state_master, client_state_slave]}, - {test_muc, [parallel], - [muc_master, muc_slave]}, {test_muc_mam, [parallel], [muc_mam_master, muc_mam_slave]}, {test_announce, [sequence], @@ -440,21 +459,21 @@ db_tests(_) -> [{single_user, [sequence], [test_register, legacy_auth_tests(), - auth_plain, - auth_md5, - presence_broadcast, - last, - roster_get, - roster_ver, - private, - privacy, - blocking, - vcard, - pubsub_single_tests(), - test_unregister]}, + auth_plain, + auth_md5, + presence_broadcast, + last, + roster_get, + roster_ver, + private, + privacy, + blocking, + vcard, + pubsub_single_tests(), + muc_tests:single_cases(), + test_unregister]}, + muc_tests:master_slave_cases(), pubsub_multiple_tests(), - {test_muc_register, [parallel], - [muc_register_master, muc_register_slave]}, {test_mix, [parallel], [mix_master, mix_slave]}, {test_roster_subscribe, [parallel], @@ -468,8 +487,6 @@ db_tests(_) -> [mam_old_master, mam_old_slave]}, {test_new_mam, [parallel], [mam_new_master, mam_new_slave]}, - {test_muc, [parallel], - [muc_master, muc_slave]}, {test_muc_mam, [parallel], [muc_mam_master, muc_mam_slave]}, {test_announce, [sequence], @@ -604,7 +621,8 @@ test_connect_bad_ns_stream(Config) -> close_socket(Config0). test_connect_bad_lang(Config) -> - Config0 = init_stream(set_opt(lang, lists:duplicate(36, $x), Config)), + Lang = iolist_to_binary(lists:duplicate(36, $x)), + Config0 = init_stream(set_opt(lang, Lang, Config)), ?recv1(#stream_error{reason = 'policy-violation'}), ?recv1({xmlstreamend, <<"stream:stream">>}), close_socket(Config0). @@ -2272,352 +2290,151 @@ muc_mam_master(Config) -> muc_mam_slave(Config) -> disconnect(Config). -muc_master(Config) -> - MyJID = my_jid(Config), - PeerJID = ?config(slave, Config), - PeerBareJID = jid:remove_resource(PeerJID), - PeerJIDStr = jid:to_string(PeerJID), - MUC = muc_jid(Config), - Room = muc_room_jid(Config), - MyNick = ?config(master_nick, Config), - MyNickJID = jid:replace_resource(Room, MyNick), - PeerNick = ?config(slave_nick, Config), - PeerNickJID = jid:replace_resource(Room, PeerNick), - Subject = ?config(room_subject, Config), - Localhost = jid:make(<<"">>, <<"localhost">>, <<"">>), - true = is_feature_advertised(Config, ?NS_MUC, MUC), - %% Joining - send(Config, #presence{to = MyNickJID, sub_els = [#muc{}]}), - %% 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 stanza. - #muc_user{ - status_codes = Codes, - items = [#muc_item{role = moderator, - jid = MyJID, - affiliation = owner}]} = - xmpp:get_subtag(?recv1(#presence{from = MyNickJID}), #muc_user{}), - %% 110 -> Inform user that presence refers to itself - %% 201 -> Inform user that a new room has been created - [110, 201] = lists:sort(Codes), - %% Request the configuration - #iq{type = result, sub_els = [#muc_owner{config = #xdata{} = RoomCfg}]} = - send_recv(Config, #iq{type = get, sub_els = [#muc_owner{}], - to = Room}), - NewFields = - lists:flatmap( - fun(#xdata_field{var = Var, values = OrigVals}) -> - Vals = case Var of - <<"FORM_TYPE">> -> - OrigVals; - <<"muc#roomconfig_roomname">> -> - [<<"Test room">>]; - <<"muc#roomconfig_roomdesc">> -> - [<<"Trying to break the server">>]; - <<"muc#roomconfig_persistentroom">> -> - [<<"1">>]; - <<"members_by_default">> -> - [<<"0">>]; - <<"muc#roomconfig_allowvoicerequests">> -> - [<<"1">>]; - <<"public_list">> -> - [<<"1">>]; - <<"muc#roomconfig_publicroom">> -> - [<<"1">>]; - _ -> - [] - end, - if Vals /= [] -> - [#xdata_field{values = Vals, var = Var}]; - true -> - [] - end - end, RoomCfg#xdata.fields), - NewRoomCfg = #xdata{type = submit, fields = NewFields}, - ID = send(Config, #iq{type = set, to = Room, - sub_els = [#muc_owner{config = NewRoomCfg}]}), - ?recv2(#iq{type = result, id = ID}, - #message{from = Room, type = groupchat, - sub_els = [#muc_user{status_codes = [104]}]}), - %% Set subject - send(Config, #message{to = Room, type = groupchat, - body = [#text{data = Subject}]}), - ?recv1(#message{from = MyNickJID, type = groupchat, - body = [#text{data = Subject}]}), - %% Sending messages (and thus, populating history for our peer) - lists:foreach( - fun(N) -> - Text = #text{data = integer_to_binary(N)}, - I = send(Config, #message{to = Room, body = [Text], - type = groupchat}), - ?recv1(#message{from = MyNickJID, id = I, - type = groupchat, - body = [Text]}) - end, lists:seq(1, 5)), - %% Inviting the peer - send(Config, #message{to = Room, type = normal, - sub_els = - [#muc_user{ - invites = - [#muc_invite{to = PeerJID}]}]}), - #muc_user{ - items = [#muc_item{role = visitor, - jid = PeerJID, - affiliation = none}]} = - xmpp:get_subtag(?recv1(#presence{from = PeerNickJID}), #muc_user{}), - %% Receiving a voice request - #message{from = Room, - sub_els = [#xdata{type = form, - instructions = [_], - fields = VoiceReqFs}]} = recv(Config), - %% Approving the voice request - ReplyVoiceReqFs = - lists:map( - fun(#xdata_field{var = Var, values = OrigVals}) -> - Vals = case {Var, OrigVals} of - {<<"FORM_TYPE">>, - [<<"http://jabber.org/protocol/muc#request">>]} -> - OrigVals; - {<<"muc#role">>, [<<"participant">>]} -> - [<<"participant">>]; - {<<"muc#jid">>, [PeerJIDStr]} -> - [PeerJIDStr]; - {<<"muc#roomnick">>, [PeerNick]} -> - [PeerNick]; - {<<"muc#request_allow">>, [<<"0">>]} -> - [<<"1">>] - end, - #xdata_field{values = Vals, var = Var} - end, VoiceReqFs), - send(Config, #message{to = Room, - sub_els = [#xdata{type = submit, - fields = ReplyVoiceReqFs}]}), - %% Peer is becoming a participant - #muc_user{items = [#muc_item{role = participant, - jid = PeerJID, - affiliation = none}]} = - xmpp:get_subtag(?recv1(#presence{from = PeerNickJID}), #muc_user{}), - %% Receive private message from the peer - ?recv1(#message{from = PeerNickJID, body = [#text{data = Subject}]}), - %% Granting membership to the peer and localhost server - I1 = send(Config, - #iq{type = set, to = Room, - sub_els = - [#muc_admin{ - items = [#muc_item{jid = Localhost, - affiliation = member}, - #muc_item{nick = PeerNick, - jid = PeerBareJID, - affiliation = member}]}]}), - %% Peer became a member - #muc_user{items = [#muc_item{affiliation = member, - jid = PeerJID, - role = participant}]} = - xmpp:get_subtag(?recv1(#presence{from = PeerNickJID}), #muc_user{}), - ?recv1(#message{from = Room, - sub_els = [#muc_user{ - items = [#muc_item{affiliation = member, - jid = Localhost, - role = none}]}]}), - ?recv1(#iq{type = result, id = I1, sub_els = []}), - %% Receive groupchat message from the peer - ?recv1(#message{type = groupchat, from = PeerNickJID, - body = [#text{data = Subject}]}), - %% Retrieving a member list - #iq{type = result, sub_els = [#muc_admin{items = MemberList}]} = - send_recv(Config, - #iq{type = get, to = Room, - sub_els = - [#muc_admin{items = [#muc_item{affiliation = member}]}]}), - [#muc_item{affiliation = member, - jid = Localhost}, - #muc_item{affiliation = member, - jid = PeerBareJID}] = lists:keysort(#muc_item.jid, MemberList), - %% Kick the peer - I2 = send(Config, - #iq{type = set, to = Room, - sub_els = [#muc_admin{ - items = [#muc_item{nick = PeerNick, - role = none}]}]}), - %% Got notification the peer is kicked - %% 307 -> Inform user that he or she has been kicked from the room - ?recv1(#presence{from = PeerNickJID, type = unavailable, - sub_els = [#muc_user{ - status_codes = [307], - items = [#muc_item{affiliation = member, - jid = PeerJID, - role = none}]}]}), - ?recv1(#iq{type = result, id = I2, sub_els = []}), - %% Destroying the room - I3 = send(Config, - #iq{type = set, to = Room, - sub_els = [#muc_owner{ - destroy = #muc_destroy{ - reason = Subject}}]}), - %% Kicked off - ?recv1(#presence{from = MyNickJID, type = unavailable, - sub_els = [#muc_user{items = [#muc_item{role = none, - affiliation = none}], - destroy = #muc_destroy{ - reason = Subject}}]}), - ?recv1(#iq{type = result, id = I3, sub_els = []}), - disconnect(Config). - -muc_slave(Config) -> - PeerJID = ?config(master, Config), - MUC = muc_jid(Config), - Room = muc_room_jid(Config), - MyNick = ?config(slave_nick, Config), - MyNickJID = jid:replace_resource(Room, MyNick), - PeerNick = ?config(master_nick, Config), - PeerNickJID = jid:replace_resource(Room, PeerNick), - Subject = ?config(room_subject, Config), - %% Receive an invite from the peer - #muc_user{invites = [#muc_invite{from = PeerJID}]} = - xmpp:get_subtag(?recv1(#message{from = Room, type = normal}), - #muc_user{}), - %% But before joining we discover the MUC service first - %% to check if the room is in the disco list - #iq{type = result, - sub_els = [#disco_items{items = [#disco_item{jid = Room}]}]} = - send_recv(Config, #iq{type = get, to = MUC, - sub_els = [#disco_items{}]}), - %% Now check if the peer is in the room. We check this via disco#items - #iq{type = result, - sub_els = [#disco_items{items = [#disco_item{jid = PeerNickJID, - name = PeerNick}]}]} = - send_recv(Config, #iq{type = get, to = Room, - sub_els = [#disco_items{}]}), - %% Now joining - send(Config, #presence{to = MyNickJID, sub_els = [#muc{}]}), - %% First presence is from the participant, i.e. from the peer - #muc_user{ - status_codes = [], - items = [#muc_item{role = moderator, - affiliation = owner}]} = - xmpp:get_subtag(?recv1(#presence{from = PeerNickJID}), #muc_user{}), - %% The next is the self-presence (code 110 means it) - #muc_user{status_codes = [110], - items = [#muc_item{role = visitor, - affiliation = none}]} = - xmpp:get_subtag(?recv1(#presence{from = MyNickJID}), #muc_user{}), - %% Receive the room subject - ?recv1(#message{from = PeerNickJID, type = groupchat, - body = [#text{data = Subject}], - sub_els = [#delay{}]}), - %% Receive MUC history - lists:foreach( - fun(N) -> - Text = #text{data = integer_to_binary(N)}, - ?recv1(#message{from = PeerNickJID, - type = groupchat, - body = [Text], - sub_els = [#delay{}]}) - end, lists:seq(1, 5)), - %% Sending a voice request - VoiceReq = #xdata{ - type = submit, - fields = - [#xdata_field{ - var = <<"FORM_TYPE">>, - values = [<<"http://jabber.org/protocol/muc#request">>]}, - #xdata_field{ - var = <<"muc#role">>, - type = 'text-single', - values = [<<"participant">>]}]}, - send(Config, #message{to = Room, sub_els = [VoiceReq]}), - %% Becoming a participant - #muc_user{items = [#muc_item{role = participant, - affiliation = none}]} = - xmpp:get_subtag(?recv1(#presence{from = MyNickJID}), #muc_user{}), - %% Sending private message to the peer - send(Config, #message{to = PeerNickJID, - body = [#text{data = Subject}]}), - %% Becoming a member - #muc_user{items = [#muc_item{role = participant, - affiliation = member}]} = - xmpp:get_subtag(?recv1(#presence{from = MyNickJID}), #muc_user{}), - %% Sending groupchat message - send(Config, #message{to = Room, type = groupchat, - body = [#text{data = Subject}]}), - %% Receive this message back - ?recv1(#message{type = groupchat, from = MyNickJID, - body = [#text{data = Subject}]}), - %% We're kicked off - %% 307 -> Inform user that he or she has been kicked from the room - ?recv1(#presence{from = MyNickJID, type = unavailable, - sub_els = [#muc_user{ - status_codes = [307], - items = [#muc_item{affiliation = member, - role = none}]}]}), - disconnect(Config). - -muc_register_nick(Config, MUC, PrevNick, Nick) -> - PrevRegistered = if PrevNick /= <<"">> -> true; - true -> false - end, - NewRegistered = if Nick /= <<"">> -> true; - true -> false - end, - %% Request register 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{}]}), - %% Check if previous nick is registered - PrevNick = proplists:get_value( - roomnick, muc_register:decode(FsWithoutNick)), - X = #xdata{type = submit, fields = muc_register:encode([{roomnick, Nick}])}, - %% Submitting form - #iq{type = result, sub_els = []} = - send_recv(Config, #iq{type = set, to = MUC, - sub_els = [#register{xdata = X}]}), - %% Check 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)). +%% OK, I know this is retarded, but I didn't find a better way to +%% split the test cases into different modules +muc_service_presence_error(Config) -> + muc_tests:muc_service_presence_error(Config). +muc_service_message_error(Config) -> + muc_tests:muc_service_message_error(Config). +muc_service_unknown_ns_iq_error(Config) -> + muc_tests:muc_service_unknown_ns_iq_error(Config). +muc_service_iq_set_error(Config) -> + muc_tests:muc_service_iq_set_error(Config). +muc_service_improper_iq_error(Config) -> + muc_tests:muc_service_improper_iq_error(Config). +muc_service_features(Config) -> + muc_tests:muc_service_features(Config). +muc_service_disco_info_node_error(Config) -> + muc_tests:muc_service_disco_info_node_error(Config). +muc_service_disco_items(Config) -> + muc_tests:muc_service_disco_items(Config). +muc_service_vcard(Config) -> + muc_tests:muc_service_vcard(Config). +muc_service_unique(Config) -> + muc_tests:muc_service_unique(Config). +muc_service_subscriptions(Config) -> + muc_tests:muc_service_subscriptions(Config). +muc_configure_non_existent(Config) -> + muc_tests:muc_configure_non_existent(Config). +muc_cancel_configure_non_existent(Config) -> + muc_tests:muc_cancel_configure_non_existent(Config). muc_register_master(Config) -> - MUC = muc_jid(Config), - %% Register nick "master1" - muc_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" - muc_register_nick(Config, MUC, <<"">>, <<"master2">>), - %% Now register nick "master" - muc_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 - muc_register_nick(Config, MUC, <<"master">>, <<"">>), - disconnect(Config). - + muc_tests:muc_register_master(Config). muc_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). + muc_tests:muc_register_slave(Config). +muc_join_conflict_master(Config) -> + muc_tests:muc_join_conflict_master(Config). +muc_join_conflict_slave(Config) -> + muc_tests:muc_join_conflict_slave(Config). +muc_groupchat_msg_master(Config) -> + muc_tests:muc_groupchat_msg_master(Config). +muc_groupchat_msg_slave(Config) -> + muc_tests:muc_groupchat_msg_slave(Config). +muc_private_msg_master(Config) -> + muc_tests:muc_private_msg_master(Config). +muc_private_msg_slave(Config) -> + muc_tests:muc_private_msg_slave(Config). +muc_set_subject_master(Config) -> + muc_tests:muc_set_subject_master(Config). +muc_set_subject_slave(Config) -> + muc_tests:muc_set_subject_slave(Config). +muc_history_master(Config) -> + muc_tests:muc_history_master(Config). +muc_history_slave(Config) -> + muc_tests:muc_history_slave(Config). +muc_invite_master(Config) -> + muc_tests:muc_invite_master(Config). +muc_invite_slave(Config) -> + muc_tests:muc_invite_slave(Config). +muc_invite_members_only_master(Config) -> + muc_tests:muc_invite_members_only_master(Config). +muc_invite_members_only_slave(Config) -> + muc_tests:muc_invite_members_only_slave(Config). +muc_invite_password_protected_master(Config) -> + muc_tests:muc_invite_password_protected_master(Config). +muc_invite_password_protected_slave(Config) -> + muc_tests:muc_invite_password_protected_slave(Config). +muc_voice_request_master(Config) -> + muc_tests:muc_voice_request_master(Config). +muc_voice_request_slave(Config) -> + muc_tests:muc_voice_request_slave(Config). +muc_change_role_master(Config) -> + muc_tests:muc_change_role_master(Config). +muc_change_role_slave(Config) -> + muc_tests:muc_change_role_slave(Config). +muc_kick_master(Config) -> + muc_tests:muc_kick_master(Config). +muc_kick_slave(Config) -> + muc_tests:muc_kick_slave(Config). +muc_change_affiliation_master(Config) -> + muc_tests:muc_change_affiliation_master(Config). +muc_change_affiliation_slave(Config) -> + muc_tests:muc_change_affiliation_slave(Config). +muc_destroy_master(Config) -> + muc_tests:muc_destroy_master(Config). +muc_destroy_slave(Config) -> + muc_tests:muc_destroy_slave(Config). +muc_vcard_master(Config) -> + muc_tests:muc_vcard_master(Config). +muc_vcard_slave(Config) -> + muc_tests:muc_vcard_slave(Config). +muc_nick_change_master(Config) -> + muc_tests:muc_nick_change_master(Config). +muc_nick_change_slave(Config) -> + muc_tests:muc_nick_change_slave(Config). +muc_config_title_desc_master(Config) -> + muc_tests:muc_config_title_desc_master(Config). +muc_config_title_desc_slave(Config) -> + muc_tests:muc_config_title_desc_slave(Config). +muc_config_public_list_master(Config) -> + muc_tests:muc_config_public_list_master(Config). +muc_config_public_list_slave(Config) -> + muc_tests:muc_config_public_list_slave(Config). +muc_config_password_master(Config) -> + muc_tests:muc_config_password_master(Config). +muc_config_password_slave(Config) -> + muc_tests:muc_config_password_slave(Config). +muc_config_whois_master(Config) -> + muc_tests:muc_config_whois_master(Config). +muc_config_whois_slave(Config) -> + muc_tests:muc_config_whois_slave(Config). +muc_config_members_only_master(Config) -> + muc_tests:muc_config_members_only_master(Config). +muc_config_members_only_slave(Config) -> + muc_tests:muc_config_members_only_slave(Config). +muc_config_moderated_master(Config) -> + muc_tests:muc_config_moderated_master(Config). +muc_config_moderated_slave(Config) -> + muc_tests:muc_config_moderated_slave(Config). +muc_config_private_messages_master(Config) -> + muc_tests:muc_config_private_messages_master(Config). +muc_config_private_messages_slave(Config) -> + muc_tests:muc_config_private_messages_slave(Config). +muc_config_query_master(Config) -> + muc_tests:muc_config_query_master(Config). +muc_config_query_slave(Config) -> + muc_tests:muc_config_query_slave(Config). +muc_config_allow_invites_master(Config) -> + muc_tests:muc_config_allow_invites_master(Config). +muc_config_allow_invites_slave(Config) -> + muc_tests:muc_config_allow_invites_slave(Config). +muc_config_visitor_status_master(Config) -> + muc_tests:muc_config_visitor_status_master(Config). +muc_config_visitor_status_slave(Config) -> + muc_tests:muc_config_visitor_status_slave(Config). +muc_config_allow_voice_requests_master(Config) -> + muc_tests:muc_config_allow_voice_requests_master(Config). +muc_config_allow_voice_requests_slave(Config) -> + muc_tests:muc_config_allow_voice_requests_slave(Config). +muc_config_voice_request_interval_master(Config) -> + muc_tests:muc_config_voice_request_interval_master(Config). +muc_config_voice_request_interval_slave(Config) -> + muc_tests:muc_config_voice_request_interval_slave(Config). +muc_config_visitor_nickchange_master(Config) -> + muc_tests:muc_config_visitor_nickchange_master(Config). +muc_config_visitor_nickchange_slave(Config) -> + muc_tests:muc_config_visitor_nickchange_slave(Config). announce_master(Config) -> MyJID = my_jid(Config), diff --git a/test/ejabberd_SUITE_data/ejabberd.yml b/test/ejabberd_SUITE_data/ejabberd.yml index 2d2e098de..3a6d4947f 100644 --- a/test/ejabberd_SUITE_data/ejabberd.yml +++ b/test/ejabberd_SUITE_data/ejabberd.yml @@ -443,6 +443,7 @@ modules: mod_ping: [] mod_proxy65: [] mod_legacy: [] + mod_muc: [] mod_register: welcome_message: subject: "Welcome!" diff --git a/test/muc_tests.erl b/test/muc_tests.erl new file mode 100644 index 000000000..c89b97742 --- /dev/null +++ b/test/muc_tests.erl @@ -0,0 +1,1877 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net> +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 15 Oct 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net> +%%%------------------------------------------------------------------- +-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, recv/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, flush/1, set_opt/3]). +-include("suite.hrl"). +-include("jid.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +%%%=================================================================== +%%% Single tests +%%%=================================================================== +single_cases() -> + {muc_single, [sequence], + [muc_service_presence_error, + muc_service_message_error, + muc_service_unknown_ns_iq_error, + muc_service_iq_set_error, + muc_service_improper_iq_error, + muc_service_features, + muc_service_disco_info_node_error, + muc_service_disco_items, + muc_service_unique, + muc_service_vcard, + muc_configure_non_existent, + muc_cancel_configure_non_existent, + muc_service_subscriptions]}. + +muc_service_presence_error(Config) -> + Service = muc_jid(Config), + ServiceResource = jid:replace_resource(Service, randoms: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). + +muc_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, randoms: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). + +muc_service_unknown_ns_iq_error(Config) -> + Service = muc_jid(Config), + ServiceResource = jid:replace_resource(Service, randoms: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). + +muc_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). + +muc_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). + +muc_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_RSM, + ?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). + +muc_service_disco_info_node_error(Config) -> + MUC = muc_jid(Config), + Node = randoms: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). + +muc_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 = muc_join_new(Config, Room) + end, Rooms), + Items = muc_disco_items(Config), + Rooms = [J || #disco_item{jid = J} <- Items], + lists:foreach( + fun(Room) -> + ok = muc_leave(Config, Room) + end, Rooms), + [] = muc_disco_items(Config), + disconnect(Config). + +muc_service_vcard(Config) -> + MUC = muc_jid(Config), + ct:comment("Retreiving vCard from ~s", [jid:to_string(MUC)]), + #iq{type = result, sub_els = [#vcard_temp{}]} = + send_recv(Config, #iq{type = get, to = MUC, sub_els = [#vcard_temp{}]}), + disconnect(Config). + +muc_service_unique(Config) -> + MUC = muc_jid(Config), + ct:comment("Requesting muc unique from ~s", [jid:to_string(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). + +muc_configure_non_existent(Config) -> + [_|_] = muc_get_config(Config), + disconnect(Config). + +muc_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). + +muc_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 = muc_join_new(Config, Room), + [104] = muc_set_config(Config, [{allow_subscription, true}], Room), + [] = muc_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(JIDs), + lists:foreach( + fun(Room) -> + ok = muc_unsubscribe(Config, Room), + ok = muc_leave(Config, Room) + end, Rooms), + disconnect(Config). + +%%%=================================================================== +%%% Master-slave tests +%%%=================================================================== +master_slave_cases() -> + {muc_master_slave, [sequence], + [master_slave_test(muc_register), + master_slave_test(muc_groupchat_msg), + master_slave_test(muc_private_msg), + master_slave_test(muc_set_subject), + master_slave_test(muc_history), + master_slave_test(muc_invite), + master_slave_test(muc_invite_members_only), + master_slave_test(muc_invite_password_protected), + master_slave_test(muc_voice_request), + master_slave_test(muc_change_role), + master_slave_test(muc_kick), + master_slave_test(muc_change_affiliation), + master_slave_test(muc_destroy), + master_slave_test(muc_vcard), + master_slave_test(muc_nick_change), + master_slave_test(muc_config_title_desc), + master_slave_test(muc_config_public_list), + master_slave_test(muc_config_password), + master_slave_test(muc_config_whois), + master_slave_test(muc_config_members_only), + master_slave_test(muc_config_moderated), + master_slave_test(muc_config_private_messages), + master_slave_test(muc_config_query), + master_slave_test(muc_config_allow_invites), + master_slave_test(muc_config_visitor_status), + master_slave_test(muc_config_allow_voice_requests), + master_slave_test(muc_config_voice_request_interval), + master_slave_test(muc_config_visitor_nickchange), + master_slave_test(muc_join_conflict)]}. + +muc_join_conflict_master(Config) -> + ok = muc_join_new(Config), + put_event(Config, join), + ct:comment("Waiting for 'leave' command from the slave"), + leave = get_event(Config), + ok = muc_leave(Config), + disconnect(Config). + +muc_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'} = muc_join(NewConfig), + put_event(Config, leave), + disconnect(NewConfig). + +muc_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 = muc_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 = muc_leave(Config), + disconnect(Config). + +muc_groupchat_msg_slave(Config) -> + Room = muc_room_jid(Config), + PeerNick = ?config(master_nick, Config), + PeerNickJID = jid:replace_resource(Room, PeerNick), + {[], _, _} = muc_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 = muc_leave(Config), + disconnect(Config). + +muc_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 = muc_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 = muc_leave(Config), + disconnect(Config). + +muc_private_msg_slave(Config) -> + Room = muc_room_jid(Config), + PeerNick = ?config(master_nick, Config), + PeerNickJID = jid:replace_resource(Room, PeerNick), + {[], _, _} = muc_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 = muc_leave(Config), + disconnect(Config). + +muc_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 = muc_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] = muc_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 = muc_leave(Config), + disconnect(Config). + +muc_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>>), + {[], _, _} = muc_slave_join(Config), + ct:comment("Receiving 1st subject set by the master"), + #message{type = groupchat, from = PeerNickJID, + subject = Subject1} = recv_message(Config), + ok = muc_leave(Config), + ct:comment("Waiting for 'join' command from the master"), + join = get_event(Config), + {[], SubjMsg2, _} = muc_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] = muc_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 = muc_leave(Config), + disconnect(Config). + +muc_history_master(Config) -> + Room = muc_room_jid(Config), + ServerHost = ?config(server_host, Config), + MyNick = ?config(nick, Config), + MyNickJID = jid:replace_resource(Room, MyNick), + Size = gen_mod:get_module_opt(ServerHost, mod_muc, history_size, + fun(I) when is_integer(I), I>=0 -> I end, + 20), + ok = muc_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)), + wait_for_slave(Config), + wait_for_slave(Config), + flush(Config), + ok = muc_leave(Config), + disconnect(Config). + +muc_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 = gen_mod:get_module_opt(ServerHost, mod_muc, history_size, + fun(I) when is_integer(I), I>=0 -> I end, + 20), + {History, _, _} = muc_slave_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 = muc_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"), + {[], _, _} = muc_join(Config, #muc{history = #muc_history{maxchars = 0}}), + ok = muc_leave(Config), + ct:comment("Receiving only 10 last stanzas"), + {History10, _, _} = muc_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 = muc_leave(Config), + #delay{stamp = TS} = xmpp:get_subtag(hd(History), #delay{}), + ct:comment("Receiving all history without the very first element"), + {HistoryWithoutFirst, _, _} = muc_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 = muc_leave(Config), + wait_for_master(Config), + disconnect(Config). + +muc_invite_master(Config) -> + Room = muc_room_jid(Config), + PeerJID = ?config(peer, Config), + ok = muc_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 = muc_leave(Config), + disconnect(Config). + +muc_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). + +muc_invite_members_only_master(Config) -> + Room = muc_room_jid(Config), + PeerJID = ?config(slave, Config), + ok = muc_join_new(Config), + %% Setting the room to members-only + [_|_] = muc_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 = muc_leave(Config), + disconnect(Config). + +muc_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). + +muc_invite_password_protected_master(Config) -> + Room = muc_room_jid(Config), + PeerJID = ?config(slave, Config), + Password = randoms:get_string(), + ok = muc_join_new(Config), + [104] = muc_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 = muc_leave(Config), + disconnect(Config). + +muc_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). + +muc_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 = muc_join_new(Config), + [104] = muc_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}, + {nick, 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 = muc_leave(Config), + disconnect(Config). + +muc_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), + {[], _, _} = muc_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 = muc_leave(Config), + disconnect(Config). + +muc_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 = muc_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 muc_get_role(Config, Role) of + [#muc_item{jid = MyJID, affiliation = owner, + role = moderator, nick = MyNick}] when Role == moderator -> + ok; + [] -> + ok + end, + Reason = randoms:get_string(), + put_event(Config, {Role, Reason}), + ok = muc_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}|_] = muc_get_role(Config, Role) + end, [visitor, participant, moderator]), + put_event(Config, disconnect), + wait_for_slave(Config), + flush(Config), + ok = muc_leave(Config), + disconnect(Config). + +muc_change_role_slave(Config) -> + wait_for_master(Config), + {[], _, _} = muc_join(Config), + muc_change_role_slave(Config, get_event(Config)). + +muc_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), + muc_change_role_slave(Config, get_event(Config)); +muc_change_role_slave(Config, disconnect) -> + ok = muc_leave(Config), + wait_for_master(Config), + disconnect(Config). + +muc_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 = muc_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 muc_get_affiliation(Config, Aff) of + [#muc_item{jid = MyBareJID, + affiliation = owner}] when Aff == owner -> + ok; + [] -> + ok + end, + Reason = randoms:get_string(), + put_event(Config, {Aff, Role, Status, Reason}), + ok = muc_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 = muc_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, participant, available}, + {admin, moderator, available}, {owner, moderator, available}, + {outcast, none, unavailable}]), + ok = muc_leave(Config), + disconnect(Config). + +muc_change_affiliation_slave(Config) -> + wait_for_master(Config), + {[], _, _} = muc_join(Config), + muc_change_affiliation_slave(Config, get_event(Config)). + +muc_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]), + #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 -> + muc_change_affiliation_slave(Config, get_event(Config)) + end. + +muc_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 = muc_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}|_] = muc_get_role(Config, participant), + ct:comment("Kicking slave"), + ok = muc_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), + [] = muc_get_role(Config, participant), + ct:comment("Checking if the code is '307' (kicked)"), + true = lists:member(307, Codes), + ok = muc_leave(Config), + disconnect(Config). + +muc_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), + {[], _, _} = muc_join(Config), + ct:comment("Receiving role change to 'none'"), + #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). + +muc_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 = muc_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 = muc_destroy(Config, Reason), + ct:comment("Receiving destruction presence"), + #muc_user{items = [#muc_item{role = none, + affiliation = none}], + destroy = #muc_destroy{jid = AltRoom, + reason = Reason}} = + recv_muc_presence(Config, MyNickJID, unavailable), + disconnect(Config). + +muc_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), + {[], _, _} = muc_join(Config), + #stanza_error{reason = 'forbidden'} = muc_destroy(Config, Reason), + wait_for_master(Config), + ct:comment("Receiving destruction presence"), + #muc_user{items = [#muc_item{role = none, + affiliation = none}], + destroy = #muc_destroy{jid = AltRoom, + reason = Reason}} = + recv_muc_presence(Config, MyNickJID, unavailable), + disconnect(Config). + +muc_vcard_master(Config) -> + Room = muc_room_jid(Config), + PeerNick = ?config(slave_nick, Config), + PeerNickJID = jid:replace_resource(Room, PeerNick), + FN = randoms:get_string(), + VCard = #vcard_temp{fn = FN}, + ok = muc_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'} = muc_get_vcard(Config), + ok = muc_set_vcard(Config, VCard), + VCard = muc_get_vcard(Config), + put_event(Config, VCard), + recv_muc_presence(Config, PeerNickJID, unavailable), + leave = get_event(Config), + ok = muc_leave(Config), + disconnect(Config). + +muc_vcard_slave(Config) -> + wait_for_master(Config), + {[], _, _} = muc_join(Config), + VCard = get_event(Config), + VCard = muc_get_vcard(Config), + #stanza_error{reason = 'forbidden'} = muc_set_vcard(Config, VCard), + ok = muc_leave(Config), + VCard = muc_get_vcard(Config), + put_event(Config, leave), + disconnect(Config). + +muc_nick_change_master(Config) -> + NewNick = randoms:get_string(), + PeerJID = ?config(peer, Config), + PeerNickJID = peer_muc_jid(Config), + ok = muc_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 = muc_leave(Config), + disconnect(Config). + +muc_nick_change_slave(Config) -> + MyJID = my_jid(Config), + MyNickJID = my_muc_jid(Config), + {[], _, _} = muc_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 = muc_leave(NewConfig), + disconnect(NewConfig). + +muc_config_title_desc_master(Config) -> + Title = randoms:get_string(), + Desc = randoms:get_string(), + Room = muc_room_jid(Config), + PeerNick = ?config(slave_nick, Config), + PeerNickJID = jid:replace_resource(Room, PeerNick), + ok = muc_master_join(Config), + [104] = muc_set_config(Config, [{roomname, Title}, {roomdesc, Desc}]), + RoomCfg = muc_get_config(Config), + Title = proplists:get_value(roomname, RoomCfg), + Desc = proplists:get_value(roomdesc, RoomCfg), + recv_muc_presence(Config, PeerNickJID, unavailable), + ok = muc_leave(Config), + disconnect(Config). + +muc_config_title_desc_slave(Config) -> + {[], _, _} = muc_slave_join(Config), + [104] = muc_recv_config_change_message(Config), + ok = muc_leave(Config), + disconnect(Config). + +muc_config_public_list_master(Config) -> + Room = muc_room_jid(Config), + PeerNick = ?config(slave_nick, Config), + PeerNickJID = jid:replace_resource(Room, PeerNick), + ok = muc_join_new(Config), + wait_for_slave(Config), + recv_muc_presence(Config, PeerNickJID, available), + lists:member(<<"muc_public">>, get_features(Config, Room)), + [104] = muc_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 = muc_leave(Config), + disconnect(Config). + +muc_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}] = muc_disco_items(Config), + [#disco_item{jid = PeerNickJID, + name = PeerNick}] = muc_disco_room_items(Config), + {[], _, _} = muc_join(Config), + [104] = muc_recv_config_change_message(Config), + ok = muc_leave(Config), + [] = muc_disco_items(Config), + [] = muc_disco_room_items(Config), + wait_for_master(Config), + disconnect(Config). + +muc_config_password_master(Config) -> + Password = randoms:get_string(), + Room = muc_room_jid(Config), + PeerNick = ?config(slave_nick, Config), + PeerNickJID = jid:replace_resource(Room, PeerNick), + ok = muc_join_new(Config), + lists:member(<<"muc_unsecured">>, get_features(Config, Room)), + [104] = muc_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 = muc_leave(Config), + disconnect(Config). + +muc_config_password_slave(Config) -> + Password = get_event(Config), + #stanza_error{reason = 'not-authorized'} = muc_join(Config), + #stanza_error{reason = 'not-authorized'} = + muc_join(Config, #muc{password = randoms:get_string()}), + {[], _, _} = muc_join(Config, #muc{password = Password}), + ok = muc_leave(Config), + disconnect(Config). + +muc_config_whois_master(Config) -> + Room = muc_room_jid(Config), + PeerNickJID = peer_muc_jid(Config), + MyNickJID = my_muc_jid(Config), + ok = muc_master_join(Config), + lists:member(<<"muc_semianonymous">>, get_features(Config, Room)), + [172] = muc_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] = muc_set_config(Config, [{whois, moderators}]), + recv_muc_presence(Config, PeerNickJID, unavailable), + ok = muc_leave(Config), + disconnect(Config). + +muc_config_whois_slave(Config) -> + PeerJID = ?config(peer, Config), + PeerNickJID = peer_muc_jid(Config), + {[], _, _} = muc_slave_join(Config), + ct:comment("Checking if the room becomes non-anonymous (code '172')"), + [172] = muc_recv_config_change_message(Config), + ct:comment("Re-joining in order to check status codes"), + ok = muc_leave(Config), + {[], _, Codes} = muc_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] = muc_recv_config_change_message(Config), + ok = muc_leave(Config), + disconnect(Config). + +muc_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 = muc_master_join(Config), + lists:member(<<"muc_open">>, get_features(Config, Room)), + [104] = muc_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 = muc_set_affiliation(Config, member, randoms: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 = muc_set_affiliation(Config, none, randoms: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 = muc_leave(Config), + disconnect(Config). + +muc_config_members_only_slave(Config) -> + MyJID = my_jid(Config), + MyNickJID = my_muc_jid(Config), + {[], _, _} = muc_slave_join(Config), + [104] = muc_recv_config_change_message(Config), + ct:comment("Getting kicked because the room has become members-only"), + #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'} = muc_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), + {[], _, _} = muc_join(Config, participant, member), + #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). + +muc_config_moderated_master(Config) -> + Room = muc_room_jid(Config), + PeerNickJID = peer_muc_jid(Config), + ok = muc_master_join(Config), + lists:member(<<"muc_moderated">>, get_features(Config, Room)), + ok = muc_set_role(Config, visitor, randoms:get_string()), + #muc_user{items = [#muc_item{role = visitor}]} = + recv_muc_presence(Config, PeerNickJID, available), + set_unmoderated = get_event(Config), + [104] = muc_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 = muc_leave(Config), + disconnect(Config). + +muc_config_moderated_slave(Config) -> + Room = muc_room_jid(Config), + MyNickJID = my_muc_jid(Config), + {[], _, _} = muc_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] = muc_recv_config_change_message(Config), + send(Config, #message{to = Room, type = groupchat}), + #message{from = MyNickJID, type = groupchat} = recv_message(Config), + ok = muc_leave(Config), + disconnect(Config). + +muc_config_private_messages_master(Config) -> + PeerNickJID = peer_muc_jid(Config), + ok = muc_master_join(Config), + ct:comment("Waiting for a private message from the slave"), + #message{from = PeerNickJID, type = chat} = recv_message(Config), + ok = muc_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] = muc_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] = muc_set_config(Config, [{allow_private_messages_from_visitors, nobody}]), + wait_for_slave(Config), + [104] = muc_set_config(Config, [{allow_private_messages_from_visitors, anyone}, + {allow_private_messages, false}]), + 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 = muc_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 = muc_leave(Config), + disconnect(Config). + +muc_config_private_messages_slave(Config) -> + MyNickJID = my_muc_jid(Config), + PeerNickJID = peer_muc_jid(Config), + {[], _, _} = muc_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] = muc_recv_config_change_message(Config), + ct:comment("Sending a private message"), + send(Config, #message{to = PeerNickJID, type = chat}), + [104] = muc_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] = muc_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 = muc_leave(Config), + disconnect(Config). + +muc_config_query_master(Config) -> + PeerNickJID = peer_muc_jid(Config), + ok = muc_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] = muc_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 = muc_leave(Config), + disconnect(Config). + +muc_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), + {[], _, _} = muc_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] = muc_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 = muc_leave(Config), + disconnect(Config). + +muc_config_allow_invites_master(Config) -> + Room = muc_room_jid(Config), + PeerJID = ?config(peer, Config), + PeerNickJID = peer_muc_jid(Config), + ok = muc_master_join(Config), + [104] = muc_set_config(Config, [{allowinvites, true}]), + ct:comment("Receiving an invitation from the slave"), + #message{from = Room, type = normal} = recv_message(Config), + [104] = muc_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 = muc_leave(Config), + disconnect(Config). + +muc_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}]}]}, + {[], _, _} = muc_slave_join(Config), + [104] = muc_recv_config_change_message(Config), + ct:comment("Sending an invitation"), + send(Config, InviteMsg), + [104] = muc_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 = muc_leave(Config), + disconnect(Config). + +muc_config_visitor_status_master(Config) -> + PeerNickJID = peer_muc_jid(Config), + Status = xmpp:mk_text(randoms:get_string()), + ok = muc_join_new(Config), + [104] = muc_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] = muc_set_config(Config, [{allow_visitor_status, false}]), + ct:comment("Receiving status change with <status/> stripped"), + #presence{from = PeerNickJID, status = []} = recv_presence(Config), + ct:comment("Waiting for the slave to leave"), + recv_muc_presence(Config, PeerNickJID, unavailable), + ok = muc_leave(Config), + disconnect(Config). + +muc_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), + {[], _, _} = muc_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] = muc_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 = muc_leave(Config), + disconnect(Config). + +muc_config_allow_voice_requests_master(Config) -> + PeerNickJID = peer_muc_jid(Config), + ok = muc_join_new(Config), + [104] = muc_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] = muc_set_config(Config, [{allow_voice_requests, false}]), + ct:comment("Waiting for the slave to leave"), + recv_muc_presence(Config, PeerNickJID, unavailable), + ok = muc_leave(Config), + disconnect(Config). + +muc_config_allow_voice_requests_slave(Config) -> + Room = muc_room_jid(Config), + ct:comment("Waiting for 'join' command from the master"), + join = get_event(Config), + {[], _, _} = muc_join(Config, visitor), + [104] = muc_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 = muc_leave(Config), + disconnect(Config). + +muc_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 = muc_join_new(Config), + [104] = muc_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] = muc_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}, + {nick, 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 = muc_leave(Config), + disconnect(Config). + +muc_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), + {[], _, _} = muc_join(Config, visitor), + [104] = muc_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("Receving 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 = muc_leave(Config), + disconnect(Config). + +muc_config_visitor_nickchange_master(Config) -> + PeerNickJID = peer_muc_jid(Config), + ok = muc_join_new(Config), + [104] = muc_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] = muc_set_config(Config, [{allow_visitor_nickchange, false}]), + ct:comment("Waiting for the slave to leave"), + recv_muc_presence(Config, PeerNickJID, unavailable), + ok = muc_leave(Config), + disconnect(Config). + +muc_config_visitor_nickchange_slave(Config) -> + NewNick = randoms: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), + {[], _, _} = muc_join(Config, visitor), + [104] = muc_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 = muc_leave(Config), + disconnect(Config). + +muc_register_master(Config) -> + MUC = muc_jid(Config), + %% Register nick "master1" + muc_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" + muc_register_nick(Config, MUC, <<"">>, <<"master2">>), + %% Now register nick "master" + muc_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 + muc_register_nick(Config, MUC, <<"master">>, <<"">>), + disconnect(Config). + +muc_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 +%%%=================================================================== +master_slave_test(T) -> + {T, [parallel], [list_to_atom(atom_to_list(T) ++ "_master"), + list_to_atom(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{}). + +muc_join_new(Config) -> + muc_join_new(Config, muc_room_jid(Config)). + +muc_join_new(Config, Room) -> + MyJID = my_jid(Config), + MyNick = ?config(nick, Config), + MyNickJID = jid:replace_resource(Room, MyNick), + ct:comment("Joining new room"), + send(Config, #presence{to = MyNickJID, sub_els = [#muc{}]}), + %% 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}]} = + xmpp:get_subtag(?recv1(#presence{from = MyNickJID}), #muc_user{}), + 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] = muc_set_config(Config, [{persistentroom, true}], Room), + ok; + false -> + ok + end. + +muc_recv_history_and_subject(Config) -> + ct:comment("Receiving room history and/or subject"), + muc_recv_history_and_subject(Config, []). + +muc_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} -> + muc_recv_history_and_subject(Config, [Msg|History]); + false when Subj /= [], Body == [], Thread == undefined -> + {lists:reverse(History), Msg} + end. + +muc_join(Config) -> + muc_join(Config, participant, none, #muc{}). + +muc_join(Config, Role) when is_atom(Role) -> + muc_join(Config, Role, none, #muc{}); +muc_join(Config, #muc{} = SubEl) -> + muc_join(Config, participant, none, SubEl). + +muc_join(Config, Role, Aff) when is_atom(Role), is_atom(Aff) -> + muc_join(Config, Role, Aff, #muc{}); +muc_join(Config, Role, #muc{} = SubEl) when is_atom(Role) -> + muc_join(Config, Role, none, SubEl). + +muc_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{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} = muc_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} = muc_recv_history_and_subject(Config), + {empty, History, Subj, Codes} + end. + +muc_leave(Config) -> + muc_leave(Config, muc_room_jid(Config)). + +muc_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] = muc_set_config(Config, [{persistentroom, false}], Room); + true -> + ok + end, + ct:comment("Leaving the room"), + send(Config, #presence{to = MyNickJID, type = unavailable}), + #muc_user{ + status_codes = Codes, + items = [#muc_item{role = none, jid = MyJID}]} = + xmpp:get_subtag(?recv1(#presence{from = MyNickJID, + type = unavailable}), #muc_user{}), + ct:comment("Checking if code '110' (self-presence) is set"), + true = lists:member(110, Codes), + ok. + +muc_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. + +muc_set_config(Config, RoomConfig) -> + muc_set_config(Config, RoomConfig, muc_room_jid(Config)). + +muc_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 = []} -> + #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. + +muc_create_persistent(Config) -> + [_|_] = muc_get_config(Config), + [] = muc_set_config(Config, [{persistentroom, true}], false), + ok. + +muc_destroy(Config) -> + muc_destroy(Config, <<>>). + +muc_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. + +muc_disco_items(Config) -> + MUC = muc_jid(Config), + ct:comment("Performing disco#items request to ~s", [jid:to_string(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). + +muc_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. + +muc_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. + +muc_master_join(Config) -> + Room = muc_room_jid(Config), + PeerJID = ?config(slave, Config), + PeerNick = ?config(slave_nick, Config), + PeerNickJID = jid:replace_resource(Room, PeerNick), + ok = muc_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. + +muc_slave_join(Config) -> + wait_for_master(Config), + muc_join(Config). + +muc_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. + +muc_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. + +muc_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. + +muc_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. + +muc_set_vcard(Config, VCard) -> + Room = muc_room_jid(Config), + ct:comment("Setting vCard for ~s", [jid:to_string(Room)]), + case send_recv(Config, #iq{type = set, to = Room, + sub_els = [VCard]}) of + #iq{type = result, sub_els = []} -> + ok; + #iq{type = error} = Err -> + xmpp:get_subtag(Err, #stanza_error{}) + end. + +muc_get_vcard(Config) -> + Room = muc_room_jid(Config), + ct:comment("Retreiving vCard from ~s", [jid:to_string(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. + +muc_recv_config_change_message(Config) -> + ct:comment("Receiving configuration change notification message"), + Room = muc_room_jid(Config), + #message{type = groupchat, from = Room} = Msg = recv_message(Config), + #muc_user{status_codes = Codes} = xmpp:get_subtag(Msg, #muc_user{}), + lists:sort(Codes). + +muc_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)). + +muc_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. + +muc_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. diff --git a/test/suite.erl b/test/suite.erl index ed1cbd83d..7a823844b 100644 --- a/test/suite.erl +++ b/test/suite.erl @@ -70,10 +70,12 @@ init_config(Config) -> {s2s_port, ct:get_config(s2s_port, 5269)}, {server, ?COMMON_VHOST}, {user, <<"test_single!#$%^*()`~+-;_=[]{}|\\">>}, + {nick, <<"nick!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>}, {master_nick, <<"master_nick!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>}, {slave_nick, <<"slave_nick!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>}, {room_subject, <<"hello, world!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>}, {certfile, CertFile}, + {persistent_room, true}, {anonymous, false}, {type, client}, {xmlns, ?NS_CLIENT}, @@ -210,6 +212,7 @@ process_stream_features(Config) -> end, set_opt(mechs, Mechs, Config), Fs). disconnect(Config) -> + ct:comment("Disconnecting"), Socket = ?config(socket, Config), try ok = send_text(Config, ?STREAM_TRAILER) @@ -435,22 +438,50 @@ match_failure(Received, Matches) -> recv(Config) -> receive {'$gen_event', {xmlstreamelement, El}} -> - NS = case ?config(type, Config) of - client -> ?NS_CLIENT; - server -> ?NS_SERVER; - component -> ?NS_COMPONENT - end, - decode(El, NS, []); + decode_stream_element(Config, El); {'$gen_event', {xmlstreamstart, Name, Attrs}} -> decode(#xmlel{name = Name, attrs = Attrs}, <<>>, []); {'$gen_event', Event} -> Event end. +recv_iq(Config) -> + receive + {'$gen_event', {xmlstreamelement, #xmlel{name = <<"iq">>} = El}} -> + decode_stream_element(Config, El) + end. + +recv_presence(Config) -> + receive + {'$gen_event', {xmlstreamelement, #xmlel{name = <<"presence">>} = El}} -> + decode_stream_element(Config, El) + end. + +recv_message(Config) -> + receive + {'$gen_event', {xmlstreamelement, #xmlel{name = <<"message">>} = El}} -> + decode_stream_element(Config, El) + end. + +decode_stream_element(Config, El) -> + NS = case ?config(type, Config) of + client -> ?NS_CLIENT; + server -> ?NS_SERVER; + component -> ?NS_COMPONENT + end, + decode(El, NS, []). + +format_element(El) -> + case erlang:function_exported(ct, log, 5) of + true -> ejabberd_web_admin:pretty_print_xml(El); + false -> io_lib:format(" ~s~n", El) + end. + decode(El, NS, Opts) -> try Pkt = xmpp:decode(El, NS, Opts), - ct:pal("recv: ~p ->~n~s", [El, xmpp:pp(Pkt)]), + ct:pal("RECV:~n~s~n~s", + [format_element(El), xmpp:pp(Pkt)]), Pkt catch _:{xmpp_codec, Why} -> ct:fail("recv failed: ~p->~n~s", @@ -475,7 +506,8 @@ send(State, Pkt) -> {undefined, Pkt} end, El = xmpp:encode(NewPkt), - ct:pal("sent: ~p <-~n~s", [El, xmpp:pp(NewPkt)]), + ct:pal("SENT:~n~s~n~s", + [format_element(El), xmpp:pp(NewPkt)]), Data = case NewPkt of #stream_start{} -> fxml:element_to_header(El); _ -> fxml:element_to_binary(El) @@ -483,9 +515,15 @@ send(State, Pkt) -> ok = send_text(State, Data), NewID. -send_recv(State, IQ) -> +send_recv(State, #message{} = Msg) -> + ID = send(State, Msg), + #message{id = ID} = recv_message(State); +send_recv(State, #presence{} = Pres) -> + ID = send(State, Pres), + #presence{id = ID} = recv_presence(State); +send_recv(State, #iq{} = IQ) -> ID = send(State, IQ), - #iq{id = ID} = recv(State). + #iq{id = ID} = recv_iq(State). sasl_new(<<"PLAIN">>, User, Server, Password) -> {<<User/binary, $@, Server/binary, 0, User/binary, 0, Password/binary>>, @@ -590,6 +628,20 @@ muc_room_jid(Config) -> Server = ?config(server, Config), jid:make(<<"test">>, <<"conference.", Server/binary>>, <<>>). +my_muc_jid(Config) -> + Nick = ?config(nick, Config), + RoomJID = muc_room_jid(Config), + jid:replace_resource(RoomJID, Nick). + +peer_muc_jid(Config) -> + PeerNick = ?config(peer_nick, Config), + RoomJID = muc_room_jid(Config), + jid:replace_resource(RoomJID, PeerNick). + +alt_room_jid(Config) -> + Server = ?config(server, Config), + jid:make(<<"alt">>, <<"conference.", Server/binary>>, <<>>). + mix_jid(Config) -> Server = ?config(server, Config), jid:make(<<>>, <<"mix.", Server/binary>>, <<>>). @@ -610,6 +662,7 @@ get_features(Config) -> get_features(Config, server_jid(Config)). get_features(Config, To) -> + ct:comment("Getting features of ~s", [jid:to_string(To)]), #iq{type = result, sub_els = [#disco_info{features = Features}]} = send_recv(Config, #iq{type = get, sub_els = [#disco_info{}], to = To}), Features. @@ -707,3 +760,10 @@ get_event(Config) -> {event, Event, Relay} -> Event end. + +flush(Config) -> + flush(Config, []). + +flush(Config, Msgs) -> + receive Msg -> flush(Config, [Msg|Msgs]) + after 1000 -> lists:reverse(Msgs) end. |