]> granicus.if.org Git - ejabberd/commitdiff
Make common tests working again
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>
Fri, 18 Nov 2016 10:38:08 +0000 (13:38 +0300)
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>
Fri, 18 Nov 2016 10:38:08 +0000 (13:38 +0300)
41 files changed:
include/mam_query.hrl [new file with mode: 0644]
specs/mam_query.cfg [new file with mode: 0644]
specs/mam_query.xdata [new file with mode: 0644]
src/ejabberd_local.erl
src/flex_offline.erl
src/mam_query.erl [new file with mode: 0644]
src/mod_carboncopy.erl
src/mod_mam.erl
src/mod_mam_mnesia.erl
src/mod_mam_sql.erl
src/mod_offline.erl
src/mod_offline_riak.erl
src/mod_offline_sql.erl
src/mod_roster.erl
src/muc_register.erl
src/muc_request.erl
src/muc_roomconfig.erl
src/muc_roominfo.erl
src/pubsub_get_pending.erl
src/pubsub_node_config.erl
src/pubsub_publish_options.erl
src/pubsub_subscribe_authorization.erl
src/pubsub_subscribe_options.erl
src/xmpp_util.erl
test/announce_tests.erl [new file with mode: 0644]
test/carbons_tests.erl [new file with mode: 0644]
test/csi_tests.erl [new file with mode: 0644]
test/ejabberd_SUITE.erl
test/example_tests.erl [new file with mode: 0644]
test/mam_tests.erl [new file with mode: 0644]
test/mix_tests.erl [new file with mode: 0644]
test/mod_legacy.erl
test/muc_tests.erl
test/privacy_tests.erl
test/proxy65_tests.erl [new file with mode: 0644]
test/pubsub_tests.erl [new file with mode: 0644]
test/replaced_tests.erl [new file with mode: 0644]
test/roster_tests.erl
test/sm_tests.erl [new file with mode: 0644]
test/suite.erl
test/vcard_tests.erl [new file with mode: 0644]

diff --git a/include/mam_query.hrl b/include/mam_query.hrl
new file mode 100644 (file)
index 0000000..4ec48c0
--- /dev/null
@@ -0,0 +1,13 @@
+%% Created automatically by xdata generator (xdata_codec.erl)
+%% Source: mam_query.xdata
+%% Form type: urn:xmpp:mam:1
+%% Document: XEP-0313
+
+
+-type property() :: {'with', jid:jid()} |
+                    {'start', erlang:timestamp()} |
+                    {'end', erlang:timestamp()} |
+                    {'withtext', binary()}.
+-type result() :: [property()].
+
+-type form() :: [property() | xdata_field()].
diff --git a/specs/mam_query.cfg b/specs/mam_query.cfg
new file mode 100644 (file)
index 0000000..a8921a5
--- /dev/null
@@ -0,0 +1,9 @@
+[{decode, [{<<"start">>, {xmpp_util, decode_timestamp, []}},
+          {<<"end">>, {xmpp_util, decode_timestamp, []}}]},
+ {specs, [{<<"start">>, "erlang:timestamp()"},
+         {<<"end">>, "erlang:timestamp()"}]}].
+
+%% Local Variables:
+%% mode: erlang
+%% End:
+%% vim: set filetype=erlang tabstop=8:
diff --git a/specs/mam_query.xdata b/specs/mam_query.xdata
new file mode 100644 (file)
index 0000000..58fad41
--- /dev/null
@@ -0,0 +1,23 @@
+<form_type>
+  <name>urn:xmpp:mam:1</name>
+  <doc>XEP-0313</doc>
+  <desc>Form to query message archives</desc>
+  <field var='with'
+         type='jid-single'
+         label='User JID'/>
+  <field var='start'
+        type='text-single'
+        label='Search from the date'/>
+  <field var='end'
+        type='text-single'
+        label='Search until the date'/>
+  <field var='withtext'
+        type='text-single'
+        label='Search the text'/>
+</form_type>
+
+<!--
+Local Variables:
+mode: xml
+End:
+-->
index d7849396bd9245d83b3563f33a80c37b40d1b620..74d86945d095947c6f154baf77b3c45f3cd5dd75 100644 (file)
@@ -177,15 +177,19 @@ unregister_iq_handler(Host, XMLNS) ->
 refresh_iq_handlers() ->
     ejabberd_local ! refresh_iq_handlers.
 
--spec bounce_resource_packet(jid(), jid(), stanza()) -> ok.
-bounce_resource_packet(_From, _To, #presence{}) ->
+-spec bounce_resource_packet(jid(), jid(), stanza()) -> stop.
+bounce_resource_packet(_From, #jid{lresource = <<"">>}, #presence{}) ->
+    ok;
+bounce_resource_packet(_From, #jid{lresource = <<"">>},
+                      #message{type = headline}) ->
     ok;
 bounce_resource_packet(From, To, Packet) ->
     Lang = xmpp:get_lang(Packet),
     Txt = <<"No available resource found">>,
     Err = xmpp:make_error(Packet,
                          xmpp:err_item_not_found(Txt, Lang)),
-    ejabberd_router:route(To, From, Err).
+    ejabberd_router:route(To, From, Err),
+    stop.
 
 %%====================================================================
 %% gen_server callbacks
@@ -283,7 +287,7 @@ do_route(From, To, Packet) ->
            ejabberd_sm:route(From, To, Packet);
        is_record(Packet, iq), To#jid.lresource == <<"">> ->
            process_iq(From, To, Packet);
-       Type == result; Type == error; Type == headline ->
+       Type == result; Type == error ->
            ok;
        true ->
            ejabberd_hooks:run(local_send_to_resource_hook,
index 090ab3ddf3e3e961dbf6410be04b388144eebc6c..acc57342e867b5c746a5ea798e8488026d53c83a 100644 (file)
@@ -12,7 +12,7 @@
 
 -include("flex_offline.hrl").
 
--export_type([{property, 0}, {result, 0}, {form, 0}]).
+-export_type([property/0, result/0, form/0]).
 
 dec_int(Val, Min, Max) ->
     case list_to_integer(binary_to_list(Val)) of
diff --git a/src/mam_query.erl b/src/mam_query.erl
new file mode 100644 (file)
index 0000000..cb5bfe1
--- /dev/null
@@ -0,0 +1,220 @@
+%% Created automatically by xdata generator (xdata_codec.erl)
+%% Source: mam_query.xdata
+%% Form type: urn:xmpp:mam:1
+%% Document: XEP-0313
+
+-module(mam_query).
+
+-export([decode/1, decode/2, encode/1, encode/2,
+        format_error/1]).
+
+-include("xmpp_codec.hrl").
+
+-include("mam_query.hrl").
+
+-export_type([property/0, result/0, form/0]).
+
+enc_jid(J) -> jid:to_string(J).
+
+dec_jid(Val) ->
+    case jid:from_string(Val) of
+      error -> erlang:error(badarg);
+      J -> J
+    end.
+
+format_error({form_type_mismatch, Type}) ->
+    <<"FORM_TYPE doesn't match '", Type/binary, "'">>;
+format_error({bad_var_value, Var, Type}) ->
+    <<"Bad value of field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({missing_value, Var, Type}) ->
+    <<"Missing value of field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({too_many_values, Var, Type}) ->
+    <<"Too many values for field '", Var/binary,
+      "' of type '", Type/binary, "'">>;
+format_error({unknown_var, Var, Type}) ->
+    <<"Unknown field '", Var/binary, "' of type '",
+      Type/binary, "'">>;
+format_error({missing_required_var, Var, Type}) ->
+    <<"Missing required field '", Var/binary, "' of type '",
+      Type/binary, "'">>.
+
+decode(Fs) -> decode(Fs, []).
+
+decode(Fs, Acc) ->
+    case lists:keyfind(<<"FORM_TYPE">>, #xdata_field.var,
+                      Fs)
+       of
+      false -> decode(Fs, Acc, []);
+      #xdata_field{values = [<<"urn:xmpp:mam:1">>]} ->
+         decode(Fs, Acc, []);
+      _ ->
+         erlang:error({?MODULE,
+                       {form_type_mismatch, <<"urn:xmpp:mam:1">>}})
+    end.
+
+encode(Cfg) -> encode(Cfg, fun (Text) -> Text end).
+
+encode(List, Translate) when is_list(List) ->
+    Fs = [case Opt of
+           {with, Val} -> [encode_with(Val, Translate)];
+           {with, _, _} -> erlang:error({badarg, Opt});
+           {start, Val} -> [encode_start(Val, Translate)];
+           {start, _, _} -> erlang:error({badarg, Opt});
+           {'end', Val} -> [encode_end(Val, Translate)];
+           {'end', _, _} -> erlang:error({badarg, Opt});
+           {withtext, Val} -> [encode_withtext(Val, Translate)];
+           {withtext, _, _} -> erlang:error({badarg, Opt});
+           #xdata_field{} -> [Opt];
+           _ -> []
+         end
+         || Opt <- List],
+    FormType = #xdata_field{var = <<"FORM_TYPE">>,
+                           type = hidden, values = [<<"urn:xmpp:mam:1">>]},
+    [FormType | lists:flatten(Fs)].
+
+decode([#xdata_field{var = <<"with">>, values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try dec_jid(Value) of
+      Result ->
+         decode(Fs, [{with, Result} | Acc],
+                lists:delete(<<"with">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"with">>, <<"urn:xmpp:mam:1">>}})
+    end;
+decode([#xdata_field{var = <<"with">>, values = []} = F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"with">>, values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"with">>} | _], _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"with">>, <<"urn:xmpp:mam:1">>}});
+decode([#xdata_field{var = <<"start">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try xmpp_util:decode_timestamp(Value) of
+      Result ->
+         decode(Fs, [{start, Result} | Acc],
+                lists:delete(<<"start">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"start">>, <<"urn:xmpp:mam:1">>}})
+    end;
+decode([#xdata_field{var = <<"start">>, values = []} = F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"start">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"start">>} | _], _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"start">>, <<"urn:xmpp:mam:1">>}});
+decode([#xdata_field{var = <<"end">>, values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try xmpp_util:decode_timestamp(Value) of
+      Result ->
+         decode(Fs, [{'end', Result} | Acc],
+                lists:delete(<<"end">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"end">>, <<"urn:xmpp:mam:1">>}})
+    end;
+decode([#xdata_field{var = <<"end">>, values = []} = F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"end">>, values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"end">>} | _], _, _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"end">>, <<"urn:xmpp:mam:1">>}});
+decode([#xdata_field{var = <<"withtext">>,
+                    values = [Value]}
+       | Fs],
+       Acc, Required) ->
+    try Value of
+      Result ->
+         decode(Fs, [{withtext, Result} | Acc],
+                lists:delete(<<"withtext">>, Required))
+    catch
+      _:_ ->
+         erlang:error({?MODULE,
+                       {bad_var_value, <<"withtext">>, <<"urn:xmpp:mam:1">>}})
+    end;
+decode([#xdata_field{var = <<"withtext">>,
+                    values = []} =
+           F
+       | Fs],
+       Acc, Required) ->
+    decode([F#xdata_field{var = <<"withtext">>,
+                         values = [<<>>]}
+           | Fs],
+          Acc, Required);
+decode([#xdata_field{var = <<"withtext">>} | _], _,
+       _) ->
+    erlang:error({?MODULE,
+                 {too_many_values, <<"withtext">>,
+                  <<"urn:xmpp:mam:1">>}});
+decode([#xdata_field{var = Var} | Fs], Acc, Required) ->
+    if Var /= <<"FORM_TYPE">> ->
+          erlang:error({?MODULE,
+                        {unknown_var, Var, <<"urn:xmpp:mam:1">>}});
+       true -> decode(Fs, Acc, Required)
+    end;
+decode([], _, [Var | _]) ->
+    erlang:error({?MODULE,
+                 {missing_required_var, Var, <<"urn:xmpp:mam:1">>}});
+decode([], Acc, []) -> Acc.
+
+encode_with(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [enc_jid(Value)]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"with">>, values = Values,
+                required = false, type = 'jid-single', options = Opts,
+                desc = <<>>, label = Translate(<<"User JID">>)}.
+
+encode_start(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"start">>, values = Values,
+                required = false, type = 'text-single', options = Opts,
+                desc = <<>>,
+                label = Translate(<<"Search from the date">>)}.
+
+encode_end(Value, Translate) ->
+    Values = case Value of
+              undefined -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"end">>, values = Values,
+                required = false, type = 'text-single', options = Opts,
+                desc = <<>>,
+                label = Translate(<<"Search until the date">>)}.
+
+encode_withtext(Value, Translate) ->
+    Values = case Value of
+              <<>> -> [];
+              Value -> [Value]
+            end,
+    Opts = [],
+    #xdata_field{var = <<"withtext">>, values = Values,
+                required = false, type = 'text-single', options = Opts,
+                desc = <<>>, label = Translate(<<"Search the text">>)}.
index 7d8ca533218f81f9379cda3f29ba322a0712e4e6..1c8ca1fdcd40ef38cfee8137182c00026d996df9 100644 (file)
@@ -228,8 +228,8 @@ complete_packet(_From, Msg, _Direction) ->
 -spec is_chat_message(stanza()) -> boolean().
 is_chat_message(#message{type = chat}) ->
     true;
-is_chat_message(#message{type = normal, body = Body}) ->
-    xmpp:get_text(Body) /= <<"">>;
+is_chat_message(#message{type = normal, body = [_|_]}) ->
+    true;
 is_chat_message(_) ->
     false.
 
index f9ef104bd17b42501d70cd0596ef03cf498e4327..61754ae592634b3bbe104f9534cf0d98af619bbe 100644 (file)
 -callback delete_old_messages(binary() | global,
                              erlang:timestamp(),
                              all | chat | groupchat) -> any().
--callback extended_fields() -> [xdata_field()].
+-callback extended_fields() -> [mam_query:property() | #xdata_field{}].
 -callback store(xmlel(), binary(), {binary(), binary()}, chat | groupchat,
                jid(), binary(), recv | send) -> {ok, binary()} | any().
 -callback write_prefs(binary(), binary(), #archive_prefs{}, binary()) -> ok | any().
 -callback get_prefs(binary(), binary()) -> {ok, #archive_prefs{}} | error.
--callback select(binary(), jid(), jid(), mam_query(), chat | groupchat) ->
+-callback select(binary(), jid(), jid(), mam_query:result(),
+                #rsm_set{} | undefined, chat | groupchat) ->
     {[{binary(), non_neg_integer(), xmlel()}], boolean(), non_neg_integer()}.
 
 %%%===================================================================
@@ -259,8 +260,9 @@ muc_filter_message(Pkt, #state{config = Config} = MUCState,
     end.
 
 set_stanza_id(Pkt, JID, ID) ->
-    Archived = #mam_archived{by = JID, id = ID},
-    StanzaID = #stanza_id{by = JID, id = ID},
+    BareJID = jid:remove_resource(JID),
+    Archived = #mam_archived{by = BareJID, id = ID},
+    StanzaID = #stanza_id{by = BareJID, id = ID},
     NewEls = [Archived, StanzaID|xmpp:get_els(Pkt)],
     xmpp:set_els(Pkt, NewEls).
 
@@ -308,43 +310,24 @@ muc_process_iq(#iq{type = get,
 muc_process_iq(IQ, _MUCState) ->
     IQ.
 
-parse_query(#mam_query{xdata = #xdata{fields = Fs}} = Query, Lang) ->
-    try
-       lists:foldl(
-         fun(#xdata_field{var = <<"start">>, values = [Data|_]}, Q) ->
-                 try xmpp_util:decode_timestamp(Data) of
-                     {_, _, _} = TS -> Q#mam_query{start = TS}
-                 catch _:{bad_timestamp, _} -> throw({error, <<"start">>})
-                 end;
-            (#xdata_field{var = <<"end">>, values = [Data|_]}, Q) ->
-                 try xmpp_util:decode_timestamp(Data) of
-                     {_, _, _} = TS -> Q#mam_query{start = TS}
-                 catch _:{bad_timestamp, _} -> throw({error, <<"end">>})
-                 end;
-            (#xdata_field{var = <<"with">>, values = [Data|_]}, Q) ->
-                 case jid:from_string(Data) of
-                     error -> throw({error, <<"with">>});
-                     J -> Q#mam_query{with = J}
-                 end;
-            (#xdata_field{var = <<"withtext">>, values = [Data|_]}, Q) ->
-                 case Data of
-                     <<"">> -> throw({error, <<"withtext">>});
-                     _ -> Q#mam_query{withtext = Data}
-             end;
-            (#xdata_field{var = <<"FORM_TYPE">>, values = [NS|_]}, Q) ->
-                 case Query#mam_query.xmlns of
-                     NS -> Q;
-                     _ -> throw({error, <<"FORM_TYPE">>})
-                 end;
-            (#xdata_field{}, Acc) ->
-                 Acc
-         end, Query, Fs)
-    catch throw:{error, Var} ->
-           Txt = io_lib:format("Incorrect value of field '~s'", [Var]),
-           {error, xmpp:err_bad_request(iolist_to_binary(Txt), Lang)}
+parse_query(#mam_query{xmlns = ?NS_MAM_TMP,
+                      start = Start, 'end' = End,
+                      with = With, withtext = Text}, _Lang) ->
+    {ok, [{start, Start}, {'end', End},
+         {with, With}, {withtext, Text}]};
+parse_query(#mam_query{xdata = #xdata{}} = Query, Lang) ->
+    X = xmpp_util:set_xdata_field(
+         #xdata_field{var = <<"FORM_TYPE">>,
+                      type = hidden, values = [?NS_MAM_1]},
+         Query#mam_query.xdata),
+    try        mam_query:decode(X#xdata.fields) of
+       Form -> {ok, Form}
+    catch _:{mam_query, Why} ->
+           Txt = mam_query:format_error(Why),
+           {error, xmpp:err_bad_request(Txt, Lang)}
     end;
-parse_query(Query, _Lang) ->
-    Query.
+parse_query(#mam_query{}, _Lang) ->
+    {ok, []}.
 
 disco_sm_features(empty, From, To, Node, Lang) ->
     disco_sm_features({result, []}, From, To, Node, Lang);
@@ -402,17 +385,16 @@ delete_old_messages(_TypeBin, _Days) ->
 %%%===================================================================
 
 process_iq(LServer, #iq{sub_els = [#mam_query{xmlns = NS}]} = IQ) ->
-    CommonFields = [#xdata_field{type = hidden,
-                                var = <<"FORM_TYPE">>,
-                                values = [NS]},
-                   #xdata_field{type = 'jid-single', var = <<"with">>},
-                   #xdata_field{type = 'text-single', var = <<"start">>},
-                   #xdata_field{type = 'text-single', var = <<"end">>}],
     Mod = gen_mod:db_mod(LServer, ?MODULE),
+    CommonFields = [{with, undefined},
+                   {start, undefined},
+                   {'end', undefined}],
     ExtendedFields = Mod:extended_fields(),
-    Fields = CommonFields ++ ExtendedFields,
-    Form = #xdata{type = form, fields = Fields},
-    xmpp:make_iq_result(IQ, #mam_query{xmlns = NS, xdata = Form}).
+    Fields = mam_query:encode(CommonFields ++ ExtendedFields),
+    X = xmpp_util:set_xdata_field(
+         #xdata_field{var = <<"FORM_TYPE">>, type = hidden, values = [NS]},
+         #xdata{type = form, fields = Fields}),
+    xmpp:make_iq_result(IQ, #mam_query{xmlns = NS, xdata = X}).
 
 % Preference setting (both v0.2 & v0.3)
 process_iq(#iq{type = set, lang = Lang,
@@ -457,15 +439,17 @@ process_iq(LServer, #iq{from = #jid{luser = LUser}, lang = Lang,
        {groupchat, _Role, _MUCState} ->
            ok
     end,
-    case parse_query(SubEl, Lang) of
+    case SubEl of
        #mam_query{rsm = #rsm_set{index = I}} when is_integer(I) ->
            xmpp:make_error(IQ, xmpp:err_feature_not_implemented());
-       #mam_query{rsm = RSM, xmlns = NS} = Query ->
-           NewRSM = limit_max(RSM, NS),
-           NewQuery = Query#mam_query{rsm = NewRSM},
-           select_and_send(LServer, NewQuery, IQ, MsgType);
-       {error, Err} ->
-           xmpp:make_error(IQ, Err)
+       #mam_query{rsm = RSM, xmlns = NS} ->
+           case parse_query(SubEl, Lang) of
+               {ok, Query} ->
+                   NewRSM = limit_max(RSM, NS),
+                   select_and_send(LServer, Query, NewRSM, IQ, MsgType);
+               {error, Err} ->
+                   xmpp:make_error(IQ, Err)
+           end
     end.
 
 should_archive(#message{type = error}, _LServer) ->
@@ -493,57 +477,57 @@ should_archive(_, _LServer) ->
 
 -spec strip_my_archived_tag(stanza(), binary()) -> stanza().
 strip_my_archived_tag(Pkt, LServer) ->
-    NewPkt = xmpp:decode_els(
-              Pkt, ?NS_CLIENT,
+    Els = xmpp:get_els(Pkt),
+    NewEls = lists:filter(
               fun(El) ->
-                      case xmpp:get_name(El) of
-                          <<"archived">> ->
-                              xmpp:get_ns(El) == ?NS_MAM_TMP;
-                          <<"stanza-id">> ->
-                              xmpp:get_ns(El) == ?NS_SID_0;
-                          _ ->
-                              false
+                      Name = xmpp:get_name(El),
+                      NS = xmpp:get_ns(El),
+                      if (Name == <<"archived">> andalso NS == ?NS_MAM_TMP);
+                         (Name == <<"stanza-id">> andalso NS == ?NS_SID_0) ->
+                              try xmpp:decode(El) of
+                                  #mam_archived{by = By} ->
+                                      By#jid.lserver /= LServer;
+                                  #stanza_id{by = By} ->
+                                      By#jid.lserver /= LServer
+                              catch _:{xmpp_codec, _} ->
+                                      false
+                              end;
+                         true ->
+                              true
                       end
-              end),
-    NewEls = lists:filter(
-              fun(#mam_archived{by = By}) ->
-                      By#jid.lserver /= LServer;
-                 (#stanza_id{by = By}) ->
-                      By#jid.lserver /= LServer;
-                 (_) ->
-                      true
-              end, xmpp:get_els(NewPkt)),
-    xmpp:set_els(NewPkt, NewEls).
+              end, Els),
+    xmpp:set_els(Pkt, NewEls).
 
+-spec strip_x_jid_tags(stanza()) -> stanza().
 strip_x_jid_tags(Pkt) ->
-    NewPkt = xmpp:decode_els(
-              Pkt, ?NS_CLIENT,
+    Els = xmpp:get_els(Pkt),
+    NewEls = lists:filter(
               fun(El) ->
                       case xmpp:get_name(El) of
                           <<"x">> ->
-                              case xmpp:get_ns(El) of
-                                  ?NS_MUC_USER -> true;
-                                  ?NS_MUC_ADMIN -> true;
-                                  ?NS_MUC_OWNER -> true;
-                                  _ -> false
-                              end;
+                              NS = xmpp:get_ns(El),
+                              Items = if NS == ?NS_MUC_USER;
+                                         NS == ?NS_MUC_ADMIN;
+                                         NS == ?NS_MUC_OWNER ->
+                                              try xmpp:decode(El) of
+                                                  #muc_user{items = Is} -> Is;
+                                                  #muc_admin{items = Is} -> Is;
+                                                  #muc_owner{items = Is} -> Is
+                                              catch _:{xmpp_codec, _} ->
+                                                      []
+                                              end;
+                                         true ->
+                                              []
+                                      end,
+                              not lists:any(
+                                    fun(#muc_item{jid = JID}) ->
+                                            JID /= undefined
+                                    end, Items);
                           _ ->
-                              false
+                              true
                       end
-              end),
-    NewEls = lists:filter(
-              fun(El) ->
-                      Items = case El of
-                                  #muc_user{items = Is} -> Is;
-                                  #muc_admin{items = Is} -> Is;
-                                  #muc_owner{items = Is} -> Is;
-                                  _ -> []
-                              end,
-                      not lists:any(fun(#muc_item{jid = JID}) ->
-                                            JID /= undefined
-                                    end, Items)
-              end, xmpp:get_els(NewPkt)),
-    xmpp:set_els(NewPkt, NewEls).
+              end, Els),
+    xmpp:set_els(Pkt, NewEls).
 
 should_archive_peer(C2SState,
                    #archive_prefs{default = Default,
@@ -625,7 +609,7 @@ has_no_store_hint(Message) ->
 -spec is_resent(message(), binary()) -> boolean().
 is_resent(Pkt, LServer) ->
     case xmpp:get_subtag(Pkt, #stanza_id{}) of
-       #stanza_id{by = #jid{luser = <<>>, lserver = LServer}} ->
+       #stanza_id{by = #jid{lserver = LServer}} ->
            true;
        _ ->
            false
@@ -741,21 +725,22 @@ maybe_activate_mam(LUser, LServer) ->
            ok
     end.
 
-select_and_send(LServer, Query, #iq{from = From, to = To} = IQ, MsgType) ->
+select_and_send(LServer, Query, RSM, #iq{from = From, to = To} = IQ, MsgType) ->
     {Msgs, IsComplete, Count} =
        case MsgType of
            chat ->
-               select(LServer, From, From, Query, MsgType);
+               select(LServer, From, From, Query, RSM, MsgType);
            {groupchat, _Role, _MUCState} ->
-               select(LServer, From, To, Query, MsgType)
+               select(LServer, From, To, Query, RSM, MsgType)
        end,
     SortedMsgs = lists:keysort(2, Msgs),
     send(SortedMsgs, Count, IsComplete, IQ).
 
-select(_LServer, JidRequestor, JidArchive,
-       #mam_query{start = Start, 'end' = End, rsm = RSM},
+select(_LServer, JidRequestor, JidArchive, Query, RSM,
        {groupchat, _Role, #state{config = #config{mam = false},
                                 history = History}} = MsgType) ->
+    Start = proplists:get_value(start, Query),
+    End = proplists:get_value('end', Query),
     #lqueue{len = L, queue = Q} = History,
     Msgs =
        lists:flatmap(
@@ -786,9 +771,9 @@ select(_LServer, JidRequestor, JidArchive,
        _ ->
            {Msgs, true, L}
     end;
-select(LServer, JidRequestor, JidArchive, Query, MsgType) ->
+select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType) ->
     Mod = gen_mod:db_mod(LServer, ?MODULE),
-    Mod:select(LServer, JidRequestor, JidArchive, Query, MsgType).
+    Mod:select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType).
 
 msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, nick = Nick, peer = Peer},
          MsgType, JidRequestor, #jid{lserver = LServer} = JidArchive) ->
index e913d5a4534c1284a0bc1a0d542ece022aabf96f..8b9c6676c66efe4c9aef13560feb6dcc4cd43439 100644 (file)
@@ -12,7 +12,7 @@
 
 %% API
 -export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
-        extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/5]).
+        extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/6]).
 
 -include_lib("stdlib/include/ms_transform.hrl").
 -include("xmpp.hrl").
@@ -132,8 +132,10 @@ get_prefs(LUser, LServer) ->
 
 select(_LServer, JidRequestor,
        #jid{luser = LUser, lserver = LServer} = JidArchive,
-       #mam_query{start = Start, 'end' = End,
-                 with = With, rsm = RSM}, MsgType) ->
+       Query, RSM, MsgType) ->
+    Start = proplists:get_value(start, Query),
+    End = proplists:get_value('end', Query),
+    With = proplists:get_value(with, Query),
     LWith = if With /= undefined -> jid:tolower(With);
               true -> undefined
            end,
index 1491f70f209c5a6d49ade4d2113f6b06c657b6c8..c500745a307c6758a7073f75bb6fd31966d3979f 100644 (file)
@@ -14,7 +14,7 @@
 
 %% API
 -export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
-        extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/5]).
+        extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/6]).
 
 -include_lib("stdlib/include/ms_transform.hrl").
 -include("xmpp.hrl").
@@ -51,7 +51,7 @@ delete_old_messages(ServerHost, TimeStamp, Type) ->
     ok.
 
 extended_fields() ->
-    [#xdata_field{type = 'text-single', var = <<"withtext">>}].
+    [{withtext, <<"">>}].
 
 store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir) ->
     TSinteger = p1_time_compat:system_time(micro_seconds),
@@ -124,12 +124,12 @@ get_prefs(LUser, LServer) ->
     end.
 
 select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
-       MAMQuery, MsgType) ->
+       MAMQuery, RSM, MsgType) ->
     User = case MsgType of
               chat -> LUser;
               {groupchat, _Role, _MUCState} -> jid:to_string(JidArchive)
           end,
-    {Query, CountQuery} = make_sql_query(User, LServer, MAMQuery),
+    {Query, CountQuery} = make_sql_query(User, LServer, MAMQuery, RSM),
     % TODO from XEP-0313 v0.2: "To conserve resources, a server MAY place a
     % reasonable limit on how many stanzas may be pushed to a client in one
     % request. If a query returns a number of stanzas greater than this limit
@@ -139,7 +139,7 @@ select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
     case {ejabberd_sql:sql_query(LServer, Query),
          ejabberd_sql:sql_query(LServer, CountQuery)} of
        {{selected, _, Res}, {selected, _, [[Count]]}} ->
-           {Max, Direction, _} = get_max_direction_id(MAMQuery#mam_query.rsm),
+           {Max, Direction, _} = get_max_direction_id(RSM),
            {Res1, IsComplete} =
                if Max >= 0 andalso Max /= undefined andalso length(Res) > Max ->
                        if Direction == before ->
@@ -194,9 +194,11 @@ usec_to_now(Int) ->
     Sec = Secs rem 1000000,
     {MSec, Sec, USec}.
 
-make_sql_query(User, LServer,
-              #mam_query{start = Start, 'end' = End, with = With,
-                         withtext = WithText, rsm = RSM}) ->
+make_sql_query(User, LServer, MAMQuery, RSM) ->
+    Start = proplists:get_value(start, MAMQuery),
+    End = proplists:get_value('end', MAMQuery),
+    With = proplists:get_value(with, MAMQuery),
+    WithText = proplists:get_value(withtext, MAMQuery),
     {Max, Direction, ID} = get_max_direction_id(RSM),
     ODBCType = ejabberd_config:get_option(
                 {sql_type, LServer},
index 2f6d52c36f370ff0e552f7b096e2837c67a5ebbf..d007bf3c6e7cf97c317784d234ad56d7f7ddc21d 100644 (file)
@@ -99,7 +99,8 @@
 -callback remove_expired_messages(binary()) -> {atomic, any()}.
 -callback remove_old_messages(non_neg_integer(), binary()) -> {atomic, any()}.
 -callback remove_user(binary(), binary()) -> {atomic, any()}.
--callback read_message_headers(binary(), binary()) -> any().
+-callback read_message_headers(binary(), binary()) ->
+    [{non_neg_integer(), jid(), jid(), undefined | erlang:timestamp(), xmlel()}].
 -callback read_message(binary(), binary(), non_neg_integer()) ->
     {ok, #offline_msg{}} | error.
 -callback remove_message(binary(), binary(), non_neg_integer()) -> ok | {error, any()}.
index 241a8d65020a1f8b5e6cef1e9f0cbe154116ec6a..24d565383f614a7970ba4173d2d4a8b7cf030861 100644 (file)
@@ -88,7 +88,7 @@ read_message_headers(LUser, LServer) ->
                     fun(#offline_msg{from = From, to = To, packet = Pkt,
                                      timestamp = TS}) ->
                             Seq = now_to_integer(TS),
-                            {Seq, From, To, Pkt}
+                            {Seq, From, To, TS, Pkt}
                     end, Rs),
            lists:keysort(1, Hdrs);
        _Err ->
index 2b7a40bff8bc8a156788d05beacd1bde8888b72a..025aa56f5e653948fce9931980585fbb9a9db0d7 100644 (file)
@@ -103,8 +103,9 @@ read_message_headers(LUser, LServer) ->
                      case xml_to_offline_msg(XML) of
                          {ok, #offline_msg{from = From,
                                            to = To,
+                                           timestamp = TS,
                                            packet = El}} ->
-                             [{Seq, From, To, El}];
+                             [{Seq, From, To, TS, El}];
                          _ ->
                              []
                      end
index c344213f38b21007ce09c12b8da7e40471187669..2da09d317ef5a862c3a55112d7e990cb067dd1be 100644 (file)
@@ -330,7 +330,7 @@ set_roster(#roster{us = {LUser, LServer}, jid = LJID} = Item) ->
     transaction(
       LServer,
       fun() ->
-             roster_subscribe_t(LUser, LServer, LJID, Item)
+             update_roster_t(LUser, LServer, LJID, Item)
       end).
 
 del_roster(LUser, LServer, LJID) ->
index cddce2b98c22b242a6f8c6ddf6e2b3d4d96a88c4..c2b951dfc161f48b8e46d59a70e69ac140c75feb 100644 (file)
@@ -12,7 +12,7 @@
 
 -include("muc_register.hrl").
 
--export_type([{property, 0}, {result, 0}, {form, 0}]).
+-export_type([property/0, result/0, form/0]).
 
 dec_bool(<<"1">>) -> true;
 dec_bool(<<"0">>) -> false;
index 4c7becd2e57ec7098550bb710641f5c7e49adf10..2d79ba0a56a7f516bfbe788e7fd403f77db0a432 100644 (file)
@@ -12,7 +12,7 @@
 
 -include("muc_request.hrl").
 
--export_type([{property, 0}, {result, 0}, {form, 0}]).
+-export_type([property/0, result/0, form/0]).
 
 dec_enum(Val, Enums) ->
     AtomVal = erlang:binary_to_existing_atom(Val, utf8),
index 73ceb649ef7be4296ede5a1e6c5fae6ba2918fed..7d18bab666d6e88ed4be79b31069218e601612c6 100644 (file)
@@ -12,7 +12,7 @@
 
 -include("muc_roomconfig.hrl").
 
--export_type([{property, 0}, {result, 0}, {form, 0}]).
+-export_type([property/0, result/0, form/0]).
 
 dec_int(Val, Min, Max) ->
     case list_to_integer(binary_to_list(Val)) of
index 809dcef5b81928766b0bb5f9c38b37150f209537..bd5cb011bc9a86a680adf162250f5b535d95304e 100644 (file)
@@ -12,7 +12,7 @@
 
 -include("muc_roominfo.hrl").
 
--export_type([{property, 0}, {result, 0}, {form, 0}]).
+-export_type([property/0, result/0, form/0]).
 
 dec_int(Val, Min, Max) ->
     case list_to_integer(binary_to_list(Val)) of
index 1a7de6a2d6c92646cccfd3b5e0780af1a25b1aaf..c1f2ba3ad5259538b792075972d8ebcfd96bedb7 100644 (file)
@@ -12,7 +12,7 @@
 
 -include("pubsub_get_pending.hrl").
 
--export_type([{property, 0}, {result, 0}, {form, 0}]).
+-export_type([property/0, result/0, form/0]).
 
 format_error({form_type_mismatch, Type}) ->
     <<"FORM_TYPE doesn't match '", Type/binary, "'">>;
index 47ed10b491306283c3d77454db7c49602e2b933d..e831d6a8301a7274243cdc9a811b26d84219d75f 100644 (file)
@@ -12,7 +12,7 @@
 
 -include("pubsub_node_config.hrl").
 
--export_type([{property, 0}, {result, 0}, {form, 0}]).
+-export_type([property/0, result/0, form/0]).
 
 dec_int(Val, Min, Max) ->
     case list_to_integer(binary_to_list(Val)) of
index 8d022907180d084d1b16c2546081d05db04bac4e..6e96946fd3587e7900957fd9d77f283726df341a 100644 (file)
@@ -12,7 +12,7 @@
 
 -include("pubsub_publish_options.hrl").
 
--export_type([{property, 0}, {result, 0}, {form, 0}]).
+-export_type([property/0, result/0, form/0]).
 
 dec_enum(Val, Enums) ->
     AtomVal = erlang:binary_to_existing_atom(Val, utf8),
index e019ed6b95a0c8f2e013f65e1f8e6ba36a2b829a..46538da8de284d821478aa707cef5eb151519970 100644 (file)
@@ -12,7 +12,7 @@
 
 -include("pubsub_subscribe_authorization.hrl").
 
--export_type([{property, 0}, {result, 0}, {form, 0}]).
+-export_type([property/0, result/0, form/0]).
 
 dec_bool(<<"1">>) -> true;
 dec_bool(<<"0">>) -> false;
index 446a84a00145c0d7091cd68cc9b49b7c7cd103c5..02c046995ad3f47fc7980e694dd491c20220b70d 100644 (file)
@@ -12,7 +12,7 @@
 
 -include("pubsub_subscribe_options.hrl").
 
--export_type([{property, 0}, {result, 0}, {form, 0}]).
+-export_type([property/0, result/0, form/0]).
 
 dec_enum(Val, Enums) ->
     AtomVal = erlang:binary_to_existing_atom(Val, utf8),
index 57440b50ea102cc6834967934c0a68fc6cef8be9..22b8ea59747d69f50175078d185013fd38220640 100644 (file)
@@ -11,7 +11,8 @@
 %% API
 -export([add_delay_info/3, add_delay_info/4, unwrap_carbon/1,
         is_standalone_chat_state/1, get_xdata_values/2,
-        has_xdata_var/2, make_adhoc_response/1, make_adhoc_response/2,
+        set_xdata_field/2, has_xdata_var/2,
+        make_adhoc_response/1, make_adhoc_response/2,
         decode_timestamp/1, encode_timestamp/1]).
 
 -include("xmpp.hrl").
@@ -78,6 +79,12 @@ get_xdata_values(Var, #xdata{fields = Fields}) ->
        false -> []
     end.
 
+-spec set_xdata_field(xdata_field(), xdata()) -> xdata().
+set_xdata_field(Field, #xdata{fields = Fields} = X) ->
+    NewFields = lists:keystore(Field#xdata_field.var, #xdata_field.var,
+                              Fields, Field),
+    X#xdata{fields = NewFields}.
+
 -spec has_xdata_var(binary(), xdata()) -> boolean().
 has_xdata_var(Var, #xdata{fields = Fields}) ->
     lists:keymember(Var, #xdata_field.var, Fields).
diff --git a/test/announce_tests.erl b/test/announce_tests.erl
new file mode 100644 (file)
index 0000000..3eea529
--- /dev/null
@@ -0,0 +1,61 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 16 Nov 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(announce_tests).
+
+%% API
+-compile(export_all).
+-import(suite, [server_jid/1, send_recv/2, recv_message/1, disconnect/1,
+               send/2, wait_for_master/1, wait_for_slave/1]).
+
+-include("suite.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+%%%===================================================================
+%%% Single user tests
+%%%===================================================================
+single_cases() ->
+    {announce_single, [sequence], []}.
+
+%%%===================================================================
+%%% Master-slave tests
+%%%===================================================================
+master_slave_cases() ->
+    {announce_master_slave, [sequence],
+     [master_slave_test(set_motd)]}.
+
+set_motd_master(Config) ->
+    ServerJID = server_jid(Config),
+    MotdJID = jid:replace_resource(ServerJID, <<"announce/motd">>),
+    Body = xmpp:mk_text(<<"motd">>),
+    #presence{} = send_recv(Config, #presence{}),
+    wait_for_slave(Config),
+    send(Config, #message{to = MotdJID, body = Body}),
+    #message{from = ServerJID, body = Body} = recv_message(Config),
+    disconnect(Config).
+
+set_motd_slave(Config) ->
+    ServerJID = server_jid(Config),
+    Body = xmpp:mk_text(<<"motd">>),
+    #presence{} = send_recv(Config, #presence{}),
+    wait_for_master(Config),
+    #message{from = ServerJID, body = Body} = recv_message(Config),
+    disconnect(Config).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+single_test(T) ->
+    list_to_atom("announce_" ++ atom_to_list(T)).
+
+master_slave_test(T) ->
+    {list_to_atom("announce_" ++ atom_to_list(T)), [parallel],
+     [list_to_atom("announce_" ++ atom_to_list(T) ++ "_master"),
+      list_to_atom("announce_" ++ atom_to_list(T) ++ "_slave")]}.
diff --git a/test/carbons_tests.erl b/test/carbons_tests.erl
new file mode 100644 (file)
index 0000000..2780dab
--- /dev/null
@@ -0,0 +1,202 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 16 Nov 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(carbons_tests).
+
+%% API
+-compile(export_all).
+-import(suite, [is_feature_advertised/2, disconnect/1, send_recv/2,
+               recv_presence/1, send/2, get_event/1, recv_message/1,
+               my_jid/1, wait_for_slave/1, wait_for_master/1,
+               put_event/2]).
+
+-include("suite.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+%%%===================================================================
+%%% Single user tests
+%%%===================================================================
+single_cases() ->
+    {carbons_single, [sequence],
+     [single_test(feature_enabled),
+      single_test(unsupported_iq)]}.
+
+feature_enabled(Config) ->
+    true = is_feature_advertised(Config, ?NS_CARBONS_2),
+    disconnect(Config).
+
+unsupported_iq(Config) ->
+    lists:foreach(
+      fun({Type, SubEl}) ->
+             #iq{type = error} =
+                 send_recv(Config, #iq{type = Type, sub_els = [SubEl]})
+      end, [{Type, SubEl} ||
+              Type <- [get, set],
+              SubEl <- [#carbons_sent{forwarded = #forwarded{}},
+                        #carbons_received{forwarded = #forwarded{}},
+                        #carbons_private{}]] ++
+         [{get, SubEl} || SubEl <- [#carbons_enable{}, #carbons_disable{}]]),
+    disconnect(Config).
+
+%%%===================================================================
+%%% Master-slave tests
+%%%===================================================================
+master_slave_cases() ->
+    {carbons_master_slave, [sequence],
+     [master_slave_test(send_recv),
+      master_slave_test(enable_disable)]}.
+
+send_recv_master(Config) ->
+    Peer = ?config(peer, Config),
+    prepare_master(Config),
+    ct:comment("Waiting for the peer to be ready"),
+    ready = get_event(Config),
+    send_messages(Config),
+    ct:comment("Waiting for the peer to disconnect"),
+    #presence{from = Peer, type = unavailable} = recv_presence(Config),
+    disconnect(Config).
+
+send_recv_slave(Config) ->
+    prepare_slave(Config),
+    ok = enable(Config),
+    put_event(Config, ready),
+    recv_carbons(Config),
+    disconnect(Config).
+
+enable_disable_master(Config) ->
+    prepare_master(Config),
+    ct:comment("Waiting for the peer to be ready"),
+    ready = get_event(Config),
+    send_messages(Config),
+    disconnect(Config).
+
+enable_disable_slave(Config) ->
+    Peer = ?config(peer, Config),
+    prepare_slave(Config),
+    ok = enable(Config),
+    ok = disable(Config),
+    put_event(Config, ready),
+    ct:comment("Waiting for the peer to disconnect"),
+    #presence{from = Peer, type = unavailable} = recv_presence(Config),
+    disconnect(Config).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+single_test(T) ->
+    list_to_atom("carbons_" ++ atom_to_list(T)).
+
+master_slave_test(T) ->
+    {list_to_atom("carbons_" ++ atom_to_list(T)), [parallel],
+     [list_to_atom("carbons_" ++ atom_to_list(T) ++ "_master"),
+      list_to_atom("carbons_" ++ atom_to_list(T) ++ "_slave")]}.
+
+prepare_master(Config) ->
+    MyJID = my_jid(Config),
+    Peer = ?config(peer, Config),
+    #presence{from = MyJID} = send_recv(Config, #presence{priority = 10}),
+    wait_for_slave(Config),
+    ct:comment("Receiving initial presence from the peer"),
+    #presence{from = Peer} = recv_presence(Config),
+    Config.
+
+prepare_slave(Config) ->
+    Peer = ?config(peer, Config),
+    MyJID = my_jid(Config),
+    ok = enable(Config),
+    wait_for_master(Config),
+    #presence{from = MyJID} = send_recv(Config, #presence{priority = 5}),
+    ct:comment("Receiving initial presence from the peer"),
+    #presence{from = Peer} = recv_presence(Config),
+    Config.
+
+send_messages(Config) ->
+    Server = ?config(server, Config),
+    MyJID = my_jid(Config),
+    JID = jid:make(randoms:get_string(), Server),
+    lists:foreach(
+      fun({send, #message{type = Type} = Msg}) ->
+             I = send(Config, Msg#message{to = JID}),
+             if Type /= error ->
+                     #message{id = I, type = error} = recv_message(Config);
+                true ->
+                     ok
+             end;
+        ({recv, #message{} = Msg}) ->
+             ejabberd_router:route(
+               JID, MyJID, Msg#message{from = JID, to = MyJID}),
+             ct:comment("Receiving message ~s", [xmpp:pp(Msg)]),
+             #message{} = recv_message(Config)
+      end, message_iterator(Config)).
+
+recv_carbons(Config) ->
+    Peer = ?config(peer, Config),
+    BarePeer = jid:remove_resource(Peer),
+    MyJID = my_jid(Config),
+    lists:foreach(
+      fun({_, #message{sub_els = [#hint{type = 'no-copy'}]}}) ->
+             ok;
+        ({_, #message{sub_els = [#carbons_private{}]}}) ->
+             ok;
+        ({_, #message{sub_els = [#carbons_sent{}]}}) ->
+             ok;
+        ({_, #message{sub_els = [#carbons_received{}]}}) ->
+             ok;
+        ({_, #message{type = T}}) when T /= normal, T /= chat ->
+             ok;
+        ({Dir, #message{type = T, body = Body} = M})
+           when (T == chat) or (T == normal andalso Body /= []) ->
+             ct:comment("Receiving carbon ~s", [xmpp:pp(M)]),
+             #message{from = BarePeer, to = MyJID} = CarbonMsg =
+                 recv_message(Config),
+             case Dir of
+                 send ->
+                     #carbons_sent{forwarded = #forwarded{xml_els = [El]}} =
+                         xmpp:get_subtag(CarbonMsg, #carbons_sent{}),
+                     #message{body = Body} = xmpp:decode(El);
+                 recv ->
+                     #carbons_received{forwarded = #forwarded{xml_els = [El]}}=
+                         xmpp:get_subtag(CarbonMsg, #carbons_received{}),
+                     #message{body = Body} = xmpp:decode(El)
+             end;
+        (_) ->
+             false
+      end, message_iterator(Config)).
+
+enable(Config) ->
+    case send_recv(
+          Config, #iq{type = set,
+                      sub_els = [#carbons_enable{}]}) of
+       #iq{type = result, sub_els = []} ->
+           ok;
+       #iq{type = error} = Err ->
+           xmpp:get_error(Err)
+    end.
+
+disable(Config) ->
+    case send_recv(
+          Config, #iq{type = set,
+                      sub_els = [#carbons_disable{}]}) of
+       #iq{type = result, sub_els = []} ->
+           ok;
+       #iq{type = error} = Err ->
+           xmpp:get_error(Err)
+    end.
+
+message_iterator(_Config) ->
+    [{Dir, #message{type = Type, body = Body, sub_els = Els}}
+     || Dir <- [send, recv],
+       Type <- [error, chat, normal, groupchat, headline],
+       Body <- [[], xmpp:mk_text(<<"body">>)],
+       Els <- [[],
+               [#hint{type = 'no-copy'}],
+               [#carbons_private{}],
+               [#carbons_sent{forwarded = #forwarded{}}],
+               [#carbons_received{forwarded = #forwarded{}}]]].
diff --git a/test/csi_tests.erl b/test/csi_tests.erl
new file mode 100644 (file)
index 0000000..9a96b8a
--- /dev/null
@@ -0,0 +1,147 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 16 Nov 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(csi_tests).
+
+%% API
+-compile(export_all).
+-import(suite, [disconnect/1, wait_for_slave/1, wait_for_master/1,
+               send/2, send_recv/2, recv_presence/1, recv_message/1,
+               server_jid/1]).
+
+-include("suite.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+%%%===================================================================
+%%% Single user tests
+%%%===================================================================
+single_cases() ->
+    {csi_single, [sequence],
+     [single_test(feature_enabled)]}.
+
+feature_enabled(Config) ->
+    true = ?config(csi, Config),
+    disconnect(Config).
+
+%%%===================================================================
+%%% Master-slave tests
+%%%===================================================================
+master_slave_cases() ->
+    {csi_master_slave, [sequence],
+     [master_slave_test(all)]}.
+
+all_master(Config) ->
+    Peer = ?config(peer, Config),
+    Presence = #presence{to = Peer},
+    ChatState = #message{to = Peer, thread = <<"1">>,
+                        sub_els = [#chatstate{type = active}]},
+    Message = ChatState#message{body = [#text{data = <<"body">>}]},
+    PepPayload = xmpp:encode(#presence{}),
+    PepOne = #message{
+               to = Peer,
+               sub_els =
+                   [#ps_event{
+                       items =
+                           #ps_items{
+                              node = <<"foo-1">>,
+                              items =
+                                  [#ps_item{
+                                      id = <<"pep-1">>,
+                                      xml_els = [PepPayload]}]}}]},
+    PepTwo = #message{
+               to = Peer,
+               sub_els =
+                   [#ps_event{
+                       items =
+                           #ps_items{
+                              node = <<"foo-2">>,
+                              items =
+                                  [#ps_item{
+                                      id = <<"pep-2">>,
+                                      xml_els = [PepPayload]}]}}]},
+    %% Wait for the slave to become inactive.
+    wait_for_slave(Config),
+    %% Should be queued (but see below):
+    send(Config, Presence),
+    %% Should replace the previous presence in the queue:
+    send(Config, Presence#presence{type = unavailable}),
+    %% The following two PEP stanzas should be queued (but see below):
+    send(Config, PepOne),
+    send(Config, PepTwo),
+    %% The following two PEP stanzas should replace the previous two:
+    send(Config, PepOne),
+    send(Config, PepTwo),
+    %% Should be queued (but see below):
+    send(Config, ChatState),
+    %% Should replace the previous chat state in the queue:
+    send(Config, ChatState#message{sub_els = [#chatstate{type = composing}]}),
+    %% Should be sent immediately, together with the queued stanzas:
+    send(Config, Message),
+    %% Wait for the slave to become active.
+    wait_for_slave(Config),
+    %% Should be delivered, as the client is active again:
+    send(Config, ChatState),
+    disconnect(Config).
+
+all_slave(Config) ->
+    Peer = ?config(peer, Config),
+    change_client_state(Config, inactive),
+    wait_for_master(Config),
+    #presence{from = Peer, type = unavailable, sub_els = [#delay{}]} =
+       recv_presence(Config),
+    #message{
+       from = Peer,
+       sub_els =
+          [#ps_event{
+              items =
+                  #ps_items{
+                     node = <<"foo-1">>,
+                     items =
+                         [#ps_item{
+                             id = <<"pep-1">>}]}},
+           #delay{}]} = recv_message(Config),
+    #message{
+       from = Peer,
+       sub_els =
+          [#ps_event{
+              items =
+                  #ps_items{
+                     node = <<"foo-2">>,
+                     items =
+                         [#ps_item{
+                             id = <<"pep-2">>}]}},
+           #delay{}]} = recv_message(Config),
+    #message{from = Peer, thread = <<"1">>,
+            sub_els = [#chatstate{type = composing},
+                       #delay{}]} = recv_message(Config),
+    #message{from = Peer, thread = <<"1">>,
+            body = [#text{data = <<"body">>}],
+            sub_els = [#chatstate{type = active}]} = recv_message(Config),
+    change_client_state(Config, active),
+    wait_for_master(Config),
+    #message{from = Peer, thread = <<"1">>,
+            sub_els = [#chatstate{type = active}]} = recv_message(Config),
+    disconnect(Config).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+single_test(T) ->
+    list_to_atom("csi_" ++ atom_to_list(T)).
+
+master_slave_test(T) ->
+    {list_to_atom("csi_" ++ atom_to_list(T)), [parallel],
+     [list_to_atom("csi_" ++ atom_to_list(T) ++ "_master"),
+      list_to_atom("csi_" ++ atom_to_list(T) ++ "_slave")]}.
+
+change_client_state(Config, NewState) ->
+    send(Config, #csi{type = NewState}),
+    send_recv(Config, #iq{type = get, to = server_jid(Config),
+                         sub_els = [#ping{}]}).
index 121719cdfb862d1c06efc093b3ea5f7cc336fcbc..46711ad4956f70c0ff9e7ed21e761f0eb4990285 100644 (file)
@@ -173,8 +173,13 @@ end_per_group(ldap, _Config) ->
     ok;
 end_per_group(extauth, _Config) ->
     ok;
-end_per_group(riak, _Config) ->
-    ok;
+end_per_group(riak, Config) ->
+    case ejabberd_riak:is_connected() of
+       true ->
+           clear_riak_tables(Config);
+       false ->
+           Config
+    end;
 end_per_group(component, _Config) ->
     ok;
 end_per_group(s2s, _Config) ->
@@ -216,6 +221,8 @@ init_per_testcase(TestCase, OrigConfig) ->
     IsCarbons = lists:prefix("carbons_", Test),
     IsReplaced = lists:prefix("replaced_", Test),
     User = if IsReplaced -> <<"test_single!#$%^*()`~+-;_=[]{}|\\">>;
+             IsCarbons and not (IsMaster or IsSlave) ->
+                  <<"test_single!#$%^*()`~+-;_=[]{}|\\">>;
              IsMaster or IsCarbons -> <<"test_master!#$%^*()`~+-;_=[]{}|\\">>;
               IsSlave -> <<"test_slave!#$%^*()`~+-;_=[]{}|\\">>;
               true -> <<"test_single!#$%^*()`~+-;_=[]{}|\\">>
@@ -344,46 +351,12 @@ no_db_tests() ->
        s2s_optional,
        s2s_required,
        s2s_required_trusted]},
-     {sm, [sequence],
-       [sm,
-       sm_resume,
-       sm_resume_failed]},
+     sm_tests:single_cases(),
      muc_tests:single_cases(),
      muc_tests:master_slave_cases(),
-     {test_proxy65, [parallel],
-      [proxy65_master, proxy65_slave]},
-     {replaced, [parallel],
-      [replaced_master, replaced_slave]}].
-
-pubsub_single_tests() ->
-    {pubsub_single, [sequence],
-     [test_pubsub_features,
-      test_pubsub_create,
-      test_pubsub_configure,
-      test_pubsub_delete,
-      test_pubsub_get_affiliations,
-      test_pubsub_get_subscriptions,
-      test_pubsub_create_instant,
-      test_pubsub_default,
-      test_pubsub_create_configure,
-      test_pubsub_publish,
-      test_pubsub_auto_create,
-      test_pubsub_get_items,
-      test_pubsub_delete_item,
-      test_pubsub_purge,
-      test_pubsub_subscribe,
-      test_pubsub_unsubscribe]}.
-
-pubsub_multiple_tests() ->
-    {pubsub_multiple, [sequence],
-     [{pubsub_publish, [parallel],
-       [pubsub_publish_master, pubsub_publish_slave]},
-      {pubsub_subscriptions, [parallel],
-       [pubsub_subscriptions_master, pubsub_subscriptions_slave]},
-      {pubsub_affiliations, [parallel],
-       [pubsub_affiliations_master, pubsub_affiliations_slave]},
-      {pubsub_authorize, [parallel],
-       [pubsub_authorize_master, pubsub_authorize_slave]}]}.
+     proxy65_tests:single_cases(),
+     proxy65_tests:master_slave_cases(),
+     replaced_tests:master_slave_cases()].
 
 db_tests(riak) ->
     %% No support for mod_pubsub
@@ -397,18 +370,16 @@ db_tests(riak) ->
        roster_tests:single_cases(),
        private,
        privacy_tests:single_cases(),
-       vcard,
+       vcard_tests:single_cases(),
        muc_tests:single_cases(),
-       offline_tests:master_slave_cases(),
+       offline_tests:single_cases(),
        test_unregister]},
      muc_tests:master_slave_cases(),
      privacy_tests:master_slave_cases(),
      roster_tests:master_slave_cases(),
      offline_tests:master_slave_cases(),
-     {test_announce, [sequence],
-      [announce_master, announce_slave]},
-     {test_vcard_xupdate, [parallel],
-      [vcard_xupdate_master, vcard_xupdate_slave]}];
+     vcard_tests:master_slave_cases(),
+     announce_tests:master_slave_cases()];
 db_tests(DB) when DB == mnesia; DB == redis ->
     [{single_user, [sequence],
       [test_register,
@@ -420,32 +391,26 @@ db_tests(DB) when DB == mnesia; DB == redis ->
        roster_tests:single_cases(),
        private,
        privacy_tests:single_cases(),
-       vcard,
-       pubsub_single_tests(),
+       vcard_tests:single_cases(),
+       pubsub_tests:single_cases(),
        muc_tests:single_cases(),
        offline_tests:single_cases(),
+       mam_tests:single_cases(),
+       mix_tests:single_cases(),
+       carbons_tests:single_cases(),
+       csi_tests:single_cases(),
        test_unregister]},
      muc_tests:master_slave_cases(),
      privacy_tests:master_slave_cases(),
-     pubsub_multiple_tests(),
+     pubsub_tests:master_slave_cases(),
      roster_tests:master_slave_cases(),
      offline_tests:master_slave_cases(),
-     {test_mix, [parallel],
-      [mix_master, mix_slave]},
-     {test_old_mam, [parallel],
-      [mam_old_master, mam_old_slave]},
-     {test_new_mam, [parallel],
-      [mam_new_master, mam_new_slave]},
-     {test_carbons, [parallel],
-      [carbons_master, carbons_slave]},
-     {test_client_state, [parallel],
-      [client_state_master, client_state_slave]},
-     {test_muc_mam, [parallel],
-      [muc_mam_master, muc_mam_slave]},
-     {test_announce, [sequence],
-      [announce_master, announce_slave]},
-     {test_vcard_xupdate, [parallel],
-      [vcard_xupdate_master, vcard_xupdate_slave]}];
+     mam_tests:master_slave_cases(),
+     mix_tests:master_slave_cases(),
+     vcard_tests:master_slave_cases(),
+     announce_tests:master_slave_cases(),
+     carbons_tests:master_slave_cases(),
+     csi_tests:master_slave_cases()];
 db_tests(_) ->
     %% No support for carboncopy
     [{single_user, [sequence],
@@ -458,28 +423,22 @@ db_tests(_) ->
        roster_tests:single_cases(),
        private,
        privacy_tests:single_cases(),
-       vcard,
-       pubsub_single_tests(),
+       vcard_tests:single_cases(),
+       pubsub_tests:single_cases(),
        muc_tests:single_cases(),
        offline_tests:single_cases(),
+       mam_tests:single_cases(),
+       mix_tests:single_cases(),
        test_unregister]},
      muc_tests:master_slave_cases(),
      privacy_tests:master_slave_cases(),
-     pubsub_multiple_tests(),
+     pubsub_tests:master_slave_cases(),
      roster_tests:master_slave_cases(),
      offline_tests:master_slave_cases(),
-     {test_mix, [parallel],
-      [mix_master, mix_slave]},
-     {test_old_mam, [parallel],
-      [mam_old_master, mam_old_slave]},
-     {test_new_mam, [parallel],
-      [mam_new_master, mam_new_slave]},
-     {test_muc_mam, [parallel],
-      [muc_mam_master, muc_mam_slave]},
-     {test_announce, [sequence],
-      [announce_master, announce_slave]},
-     {test_vcard_xupdate, [parallel],
-      [vcard_xupdate_master, vcard_xupdate_slave]}].
+     mam_tests:master_slave_cases(),
+     mix_tests:master_slave_cases(),
+     vcard_tests:master_slave_cases(),
+     announce_tests:master_slave_cases()].
 
 ldap_tests() ->
     [{ldap_tests, [sequence],
@@ -839,27 +798,6 @@ test_bind(Config) ->
 test_open_session(Config) ->
     disconnect(open_session(Config, true)).
 
-roster_feature_enabled(Config) ->
-    roster_tests:feature_enabled(Config).
-roster_iq_set_many_items(Config) ->
-    roster_tests:iq_set_many_items(Config).
-roster_iq_set_duplicated_groups(Config) ->
-    roster_tests:iq_set_duplicated_groups(Config).
-roster_iq_set_ask(Config) ->
-    roster_tests:iq_set_ask(Config).
-roster_iq_get_item(Config) ->
-    roster_tests:iq_get_item(Config).
-roster_iq_unexpected_element(Config) ->
-    roster_tests:iq_unexpected_element(Config).
-roster_set_item(Config) ->
-    roster_tests:set_item(Config).
-roster_version(Config) ->
-    roster_tests:version(Config).
-roster_subscribe_master(Config) ->
-    roster_tests:subscribe_master(Config).
-roster_subscribe_slave(Config) ->
-    roster_tests:subscribe_slave(Config).
-
 codec_failure(Config) ->
     JID = my_jid(Config),
     #iq{type = error} =
@@ -969,74 +907,6 @@ disco(Config) ->
       end, Items),
     disconnect(Config).
 
-%% replaced_master(Config0) ->
-%%     Config = bind(Config0),
-%%     wait_for_slave(Config),
-%%     ?recv1(#stream_error{reason = conflict}),
-%%     ?recv1({xmlstreamend, <<"stream:stream">>}),
-%%     close_socket(Config).
-
-%% replaced_slave(Config0) ->
-%%     wait_for_master(Config0),
-%%     Config = bind(Config0),
-%%     disconnect(Config).
-
-replaced_master(Config) ->
-    disconnect(Config).
-
-replaced_slave(Config) ->
-    disconnect(Config).
-
-sm(Config) ->
-    Server = ?config(server, Config),
-    ServerJID = jid:make(<<"">>, Server, <<"">>),
-    %% Send messages of type 'headline' so the server discards them silently
-    Msg = #message{to = ServerJID, type = headline,
-                  body = [#text{data = <<"body">>}]},
-    true = ?config(sm, Config),
-    %% Enable the session management with resumption enabled
-    send(Config, #sm_enable{resume = true, xmlns = ?NS_STREAM_MGMT_3}),
-    #sm_enabled{id = ID, resume = true} = recv(Config),
-    %% Initial request; 'h' should be 0.
-    send(Config, #sm_r{xmlns = ?NS_STREAM_MGMT_3}),
-    ?recv1(#sm_a{h = 0}),
-    %% sending two messages and requesting again; 'h' should be 3.
-    send(Config, Msg),
-    send(Config, Msg),
-    send(Config, Msg),
-    send(Config, #sm_r{xmlns = ?NS_STREAM_MGMT_3}),
-    ?recv1(#sm_a{h = 3}),
-    close_socket(Config),
-    {save_config, set_opt(sm_previd, ID, Config)}.
-
-sm_resume(Config) ->
-    {sm, SMConfig} = ?config(saved_config, Config),
-    ID = ?config(sm_previd, SMConfig),
-    Server = ?config(server, Config),
-    ServerJID = jid:make(<<"">>, Server, <<"">>),
-    MyJID = my_jid(Config),
-    Txt = #text{data = <<"body">>},
-    Msg = #message{from = ServerJID, to = MyJID, body = [Txt]},
-    %% Route message. The message should be queued by the C2S process.
-    ejabberd_router:route(ServerJID, MyJID, Msg),
-    send(Config, #sm_resume{previd = ID, h = 0, xmlns = ?NS_STREAM_MGMT_3}),
-    ?recv1(#sm_resumed{previd = ID, h = 3}),
-    #message{from = ServerJID, to = MyJID, body = [Txt]} = recv_message(Config),
-    ?recv1(#sm_r{}),
-    send(Config, #sm_a{h = 1, xmlns = ?NS_STREAM_MGMT_3}),
-    %% Send another stanza to increment the server's 'h' for sm_resume_failed.
-    send(Config, #presence{to = ServerJID}),
-    close_socket(Config),
-    {save_config, set_opt(sm_previd, ID, Config)}.
-
-sm_resume_failed(Config) ->
-    {sm_resume, SMConfig} = ?config(saved_config, Config),
-    ID = ?config(sm_previd, SMConfig),
-    ct:sleep(5000), % Wait for session to time out.
-    send(Config, #sm_resume{previd = ID, h = 1, xmlns = ?NS_STREAM_MGMT_3}),
-    ?recv1(#sm_failed{reason = 'item-not-found', h = 4}),
-    disconnect(Config).
-
 private(Config) ->
     Conference = #bookmark_conference{name = <<"Some name">>,
                                       autojoin = true,
@@ -1071,137 +941,6 @@ last(Config) ->
                               to = server_jid(Config)}),
     disconnect(Config).
 
-privacy_feature_enabled(Config) ->
-    privacy_tests:feature_enabled(Config).
-privacy_set_get_list(Config) ->
-    privacy_tests:set_get_list(Config).
-privacy_get_list_non_existent(Config) ->
-    privacy_tests:get_list_non_existent(Config).
-privacy_set_default(Config) ->
-    privacy_tests:set_default(Config).
-privacy_del_default(Config) ->
-    privacy_tests:del_default(Config).
-privacy_set_default_non_existent(Config) ->
-    privacy_tests:set_default_non_existent(Config).
-privacy_set_active(Config) ->
-    privacy_tests:set_active(Config).
-privacy_del_active(Config) ->
-    privacy_tests:del_active(Config).
-privacy_set_active_non_existent(Config) ->
-    privacy_tests:set_active_non_existent(Config).
-privacy_remove_list(Config) ->
-    privacy_tests:remove_list(Config).
-privacy_remove_active_list(Config) ->
-    privacy_tests:remove_active_list(Config).
-privacy_remove_default_list(Config) ->
-    privacy_tests:remove_default_list(Config).
-privacy_remove_list_non_existent(Config) ->
-    privacy_tests:remove_list_non_existent(Config).
-privacy_allow_local_server(Config) ->
-    privacy_tests:allow_local_server(Config).
-privacy_malformed_iq_query(Config) ->
-    privacy_tests:malformed_iq_query(Config).
-privacy_malformed_get(Config) ->
-    privacy_tests:malformed_get(Config).
-privacy_malformed_set(Config) ->
-    privacy_tests:malformed_set(Config).
-privacy_malformed_type_value(Config) ->
-    privacy_tests:malformed_type_value(Config).
-privacy_set_get_block(Config) ->
-    privacy_tests:set_get_block(Config).
-
-privacy_deny_bare_jid_master(Config) ->
-    privacy_tests:deny_bare_jid_master(Config).
-privacy_deny_bare_jid_slave(Config) ->
-    privacy_tests:deny_bare_jid_slave(Config).
-privacy_deny_full_jid_master(Config) ->
-    privacy_tests:deny_full_jid_master(Config).
-privacy_deny_full_jid_slave(Config) ->
-    privacy_tests:deny_full_jid_slave(Config).
-privacy_deny_server_jid_master(Config) ->
-    privacy_tests:deny_server_jid_master(Config).
-privacy_deny_server_jid_slave(Config) ->
-    privacy_tests:deny_server_jid_slave(Config).
-privacy_deny_group_master(Config) ->
-    privacy_tests:deny_group_master(Config).
-privacy_deny_group_slave(Config) ->
-    privacy_tests:deny_group_slave(Config).
-privacy_deny_sub_both_master(Config) ->
-    privacy_tests:deny_sub_both_master(Config).
-privacy_deny_sub_both_slave(Config) ->
-    privacy_tests:deny_sub_both_slave(Config).
-privacy_deny_sub_from_master(Config) ->
-    privacy_tests:deny_sub_from_master(Config).
-privacy_deny_sub_from_slave(Config) ->
-    privacy_tests:deny_sub_from_slave(Config).
-privacy_deny_sub_to_master(Config) ->
-    privacy_tests:deny_sub_to_master(Config).
-privacy_deny_sub_to_slave(Config) ->
-    privacy_tests:deny_sub_to_slave(Config).
-privacy_deny_sub_none_master(Config) ->
-    privacy_tests:deny_sub_none_master(Config).
-privacy_deny_sub_none_slave(Config) ->
-    privacy_tests:deny_sub_none_slave(Config).
-privacy_deny_all_master(Config) ->
-    privacy_tests:deny_all_master(Config).
-privacy_deny_all_slave(Config) ->
-    privacy_tests:deny_all_slave(Config).
-privacy_deny_offline_master(Config) ->
-    privacy_tests:deny_offline_master(Config).
-privacy_deny_offline_slave(Config) ->
-    privacy_tests:deny_offline_slave(Config).
-privacy_block_master(Config) ->
-    privacy_tests:block_master(Config).
-privacy_block_slave(Config) ->
-    privacy_tests:block_slave(Config).
-privacy_unblock_master(Config) ->
-    privacy_tests:unblock_master(Config).
-privacy_unblock_slave(Config) ->
-    privacy_tests:unblock_slave(Config).
-privacy_unblock_all_master(Config) ->
-    privacy_tests:unblock_all_master(Config).
-privacy_unblock_all_slave(Config) ->
-    privacy_tests:unblock_all_slave(Config).
-
-vcard(Config) ->
-    true = is_feature_advertised(Config, ?NS_VCARD),
-    VCard =
-        #vcard_temp{fn = <<"Peter Saint-Andre">>,
-               n = #vcard_name{family = <<"Saint-Andre">>,
-                               given = <<"Peter">>},
-               nickname = <<"stpeter">>,
-               bday = <<"1966-08-06">>,
-               adr = [#vcard_adr{work = true,
-                                 extadd = <<"Suite 600">>,
-                                 street = <<"1899 Wynkoop Street">>,
-                                 locality = <<"Denver">>,
-                                 region = <<"CO">>,
-                                 pcode = <<"80202">>,
-                                 ctry = <<"USA">>},
-                      #vcard_adr{home = true,
-                                 locality = <<"Denver">>,
-                                 region = <<"CO">>,
-                                 pcode = <<"80209">>,
-                                 ctry = <<"USA">>}],
-               tel = [#vcard_tel{work = true,voice = true,
-                                 number = <<"303-308-3282">>},
-                      #vcard_tel{home = true,voice = true,
-                                 number = <<"303-555-1212">>}],
-               email = [#vcard_email{internet = true,pref = true,
-                                     userid = <<"stpeter@jabber.org">>}],
-               jabberid = <<"stpeter@jabber.org">>,
-               title = <<"Executive Director">>,role = <<"Patron Saint">>,
-               org = #vcard_org{name = <<"XMPP Standards Foundation">>},
-               url = <<"http://www.xmpp.org/xsf/people/stpeter.shtml">>,
-               desc = <<"More information about me is located on my "
-                        "personal website: http://www.saint-andre.com/">>},
-    #iq{type = result, sub_els = []} =
-        send_recv(Config, #iq{type = set, sub_els = [VCard]}),
-    %% TODO: check if VCard == VCard1.
-    #iq{type = result, sub_els = [_VCard1]} =
-        send_recv(Config, #iq{type = get, sub_els = [#vcard_temp{}]}),
-    disconnect(Config).
-
 vcard_get(Config) ->
     true = is_feature_advertised(Config, ?NS_VCARD),
     %% TODO: check if VCard corresponds to LDIF data from ejabberd.ldif
@@ -1216,40 +955,6 @@ ldap_shared_roster_get(Config) ->
         send_recv(Config, #iq{type = get, sub_els = [#roster_query{}]}),
     disconnect(Config).
 
-vcard_xupdate_master(Config) ->
-    Img = <<137, "PNG\r\n", 26, $\n>>,
-    ImgHash = p1_sha:sha(Img),
-    MyJID = my_jid(Config),
-    Peer = ?config(slave, Config),
-    wait_for_slave(Config),
-    #presence{from = MyJID, type = available} = send_recv(Config, #presence{}),
-    #presence{from = Peer, type = available} = recv_presence(Config),
-    VCard = #vcard_temp{photo = #vcard_photo{type = <<"image/png">>, binval = Img}},
-    #iq{type = result, sub_els = []} =
-       send_recv(Config, #iq{type = set, sub_els = [VCard]}),
-    #presence{from = MyJID, type = available,
-             sub_els = [#vcard_xupdate{hash = ImgHash}]} = recv_presence(Config),
-    #iq{type = result, sub_els = []} =
-       send_recv(Config, #iq{type = set, sub_els = [#vcard_temp{}]}),
-    ?recv2(#presence{from = MyJID, type = available,
-                    sub_els = [#vcard_xupdate{hash = undefined}]},
-          #presence{from = Peer, type = unavailable}),
-    disconnect(Config).
-
-vcard_xupdate_slave(Config) ->
-    Img = <<137, "PNG\r\n", 26, $\n>>,
-    ImgHash = p1_sha:sha(Img),
-    MyJID = my_jid(Config),
-    Peer = ?config(master, Config),
-    #presence{from = MyJID, type = available} = send_recv(Config, #presence{}),
-    wait_for_master(Config),
-    #presence{from = Peer, type = available} = recv_presence(Config),
-    #presence{from = Peer, type = available,
-             sub_els = [#vcard_xupdate{hash = ImgHash}]} = recv_presence(Config),
-    #presence{from = Peer, type = available,
-             sub_els = [#vcard_xupdate{hash = undefined}]} = recv_presence(Config),
-    disconnect(Config).
-
 stats(Config) ->
     #iq{type = result, sub_els = [#stats{list = Stats}]} =
         send_recv(Config, #iq{type = get, sub_els = [#stats{}],
@@ -1263,1637 +968,9 @@ stats(Config) ->
       end, Stats),
     disconnect(Config).
 
-test_pubsub_features(Config) ->
-    PJID = pubsub_jid(Config),
-    AllFeatures = sets:from_list(get_features(Config, PJID)),
-    NeededFeatures = sets:from_list(
-                      [?NS_PUBSUB,
-                       ?PUBSUB("access-open"),
-                       ?PUBSUB("access-authorize"),
-                       ?PUBSUB("create-nodes"),
-                       ?PUBSUB("instant-nodes"),
-                       ?PUBSUB("config-node"),
-                       ?PUBSUB("retrieve-default"),
-                       ?PUBSUB("create-and-configure"),
-                       ?PUBSUB("publish"),
-                       ?PUBSUB("auto-create"),
-                       ?PUBSUB("retrieve-items"),
-                       ?PUBSUB("delete-items"),
-                       ?PUBSUB("subscribe"),
-                       ?PUBSUB("retrieve-affiliations"),
-                       ?PUBSUB("modify-affiliations"),
-                       ?PUBSUB("retrieve-subscriptions"),
-                       ?PUBSUB("manage-subscriptions"),
-                       ?PUBSUB("purge-nodes"),
-                       ?PUBSUB("delete-nodes")]),
-    true = sets:is_subset(NeededFeatures, AllFeatures),
-    disconnect(Config).
-
-test_pubsub_create(Config) ->
-    Node = ?config(pubsub_node, Config),
-    Node = create_node(Config, Node),
-    disconnect(Config).
-
-test_pubsub_create_instant(Config) ->
-    Node = create_node(Config, <<>>),
-    delete_node(Config, Node),
-    disconnect(Config).
-
-test_pubsub_configure(Config) ->
-    Node = ?config(pubsub_node, Config),
-    NodeTitle = ?config(pubsub_node_title, Config),
-    NodeConfig = get_node_config(Config, Node),
-    MyNodeConfig = set_opts(NodeConfig,
-                           [{title, NodeTitle}]),
-    set_node_config(Config, Node, MyNodeConfig),
-    NewNodeConfig = get_node_config(Config, Node),
-    NodeTitle = proplists:get_value(title, NewNodeConfig),
-    disconnect(Config).
-
-test_pubsub_default(Config) ->
-    get_default_node_config(Config),
-    disconnect(Config).
-
-test_pubsub_create_configure(Config) ->
-    NodeTitle = ?config(pubsub_node_title, Config),
-    DefaultNodeConfig = get_default_node_config(Config),
-    CustomNodeConfig = set_opts(DefaultNodeConfig,
-                               [{title, NodeTitle}]),
-    Node = create_node(Config, <<>>, CustomNodeConfig),
-    NodeConfig = get_node_config(Config, Node),
-    NodeTitle = proplists:get_value(title, NodeConfig),
-    delete_node(Config, Node),
-    disconnect(Config).
-
-test_pubsub_publish(Config) ->
-    Node = create_node(Config, <<>>),
-    publish_item(Config, Node),
-    delete_node(Config, Node),
-    disconnect(Config).
-
-test_pubsub_auto_create(Config) ->
-    Node = randoms:get_string(),
-    publish_item(Config, Node),
-    delete_node(Config, Node),
-    disconnect(Config).
-
-test_pubsub_get_items(Config) ->
-    Node = create_node(Config, <<>>),
-    ItemsIn = [publish_item(Config, Node) || _ <- lists:seq(1, 5)],
-    ItemsOut = get_items(Config, Node),
-    true = [I || #ps_item{id = I} <- lists:sort(ItemsIn)]
-       == [I || #ps_item{id = I} <- lists:sort(ItemsOut)],
-    delete_node(Config, Node),
-    disconnect(Config).
-
-test_pubsub_delete_item(Config) ->
-    Node = create_node(Config, <<>>),
-    #ps_item{id = I} = publish_item(Config, Node),
-    [#ps_item{id = I}] = get_items(Config, Node),
-    delete_item(Config, Node, I),
-    [] = get_items(Config, Node),
-    delete_node(Config, Node),
-    disconnect(Config).
-
-test_pubsub_subscribe(Config) ->
-    Node = create_node(Config, <<>>),
-    #ps_subscription{type = subscribed} = subscribe_node(Config, Node),
-    [#ps_subscription{node = Node}] = get_subscriptions(Config),
-    delete_node(Config, Node),
-    disconnect(Config).
-
-test_pubsub_unsubscribe(Config) ->
-    Node = create_node(Config, <<>>),
-    subscribe_node(Config, Node),
-    [#ps_subscription{node = Node}] = get_subscriptions(Config),
-    unsubscribe_node(Config, Node),
-    [] = get_subscriptions(Config),
-    delete_node(Config, Node),
-    disconnect(Config).
-
-test_pubsub_get_affiliations(Config) ->
-    Nodes = lists:sort([create_node(Config, <<>>) || _ <- lists:seq(1, 5)]),
-    Affs = get_affiliations(Config),
-    Nodes = lists:sort([Node || #ps_affiliation{node = Node,
-                                               type = owner} <- Affs]),
-    [delete_node(Config, Node) || Node <- Nodes],
-    disconnect(Config).
-
-test_pubsub_get_subscriptions(Config) ->
-    Nodes = lists:sort([create_node(Config, <<>>) || _ <- lists:seq(1, 5)]),
-    [subscribe_node(Config, Node) || Node <- Nodes],
-    Subs = get_subscriptions(Config),
-    Nodes = lists:sort([Node || #ps_subscription{node = Node} <- Subs]),
-    [delete_node(Config, Node) || Node <- Nodes],
-    disconnect(Config).
-
-test_pubsub_purge(Config) ->
-    Node = create_node(Config, <<>>),
-    ItemsIn = [publish_item(Config, Node) || _ <- lists:seq(1, 5)],
-    ItemsOut = get_items(Config, Node),
-    true = [I || #ps_item{id = I} <- lists:sort(ItemsIn)]
-       == [I || #ps_item{id = I} <- lists:sort(ItemsOut)],
-    purge_node(Config, Node),
-    [] = get_items(Config, Node),
-    delete_node(Config, Node),
-    disconnect(Config).
-
-test_pubsub_delete(Config) ->
-    Node = ?config(pubsub_node, Config),
-    delete_node(Config, Node),
-    disconnect(Config).
-
-pubsub_publish_master(Config) ->
-    Node = create_node(Config, <<>>),
-    put_event(Config, Node),
-    wait_for_slave(Config),
-    #ps_item{id = ID} = publish_item(Config, Node),
-    #ps_item{id = ID} = get_event(Config),
-    delete_node(Config, Node),
-    disconnect(Config).
-
-pubsub_publish_slave(Config) ->
-    Node = get_event(Config),
-    subscribe_node(Config, Node),
-    wait_for_master(Config),
-    #message{
-       sub_els =
-          [#ps_event{
-              items = #ps_items{node = Node,
-                                items = [Item]}}]} = recv_message(Config),
-    put_event(Config, Item),
-    disconnect(Config).
-
-pubsub_subscriptions_master(Config) ->
-    Peer = ?config(slave, Config),
-    Node = ?config(pubsub_node, Config),
-    Node = create_node(Config, Node),
-    [] = get_subscriptions(Config, Node),
-    wait_for_slave(Config),
-    lists:foreach(
-      fun(Type) ->
-             ok = set_subscriptions(Config, Node, [{Peer, Type}]),
-             #ps_item{} = publish_item(Config, Node),
-             case get_subscriptions(Config, Node) of
-                 [] when Type == none; Type == pending ->
-                     ok;
-                 [#ps_subscription{jid = Peer, type = Type}] ->
-                     ok
-             end
-      end, [subscribed, unconfigured, pending, none]),
-    delete_node(Config, Node),
-    disconnect(Config).
-
-pubsub_subscriptions_slave(Config) ->
-    wait_for_master(Config),
-    MyJID = my_jid(Config),
-    Node = ?config(pubsub_node, Config),
-    lists:foreach(
-      fun(subscribed = Type) ->
-             ?recv2(#message{
-                       sub_els =
-                           [#ps_event{
-                               subscription = #ps_subscription{
-                                                 node = Node,
-                                                 jid = MyJID,
-                                                 type = Type}}]},
-                    #message{sub_els = [#ps_event{}]});
-        (Type) ->
-             #message{
-                sub_els =
-                    [#ps_event{
-                        subscription = #ps_subscription{
-                                          node = Node,
-                                          jid = MyJID,
-                                          type = Type}}]} =
-                 recv_message(Config)
-      end, [subscribed, unconfigured, pending, none]),
-    disconnect(Config).
-
-pubsub_affiliations_master(Config) ->
-    Peer = ?config(slave, Config),
-    BarePeer = jid:remove_resource(Peer),
-    lists:foreach(
-      fun(Aff) ->
-             Node = <<(atom_to_binary(Aff, utf8))/binary,
-                      $-, (randoms:get_string())/binary>>,
-             create_node(Config, Node, default_node_config(Config)),
-             #ps_item{id = I} = publish_item(Config, Node),
-             ok = set_affiliations(Config, Node, [{Peer, Aff}]),
-             Affs = get_affiliations(Config, Node),
-             case lists:keyfind(BarePeer, #ps_affiliation.jid, Affs) of
-                 false when Aff == none ->
-                     ok;
-                 #ps_affiliation{type = Aff} ->
-                     ok
-             end,
-             put_event(Config, {Aff, Node, I}),
-             wait_for_slave(Config),
-             delete_node(Config, Node)
-      end, [outcast, none, member, publish_only, publisher, owner]),
-    put_event(Config, disconnect),
-    disconnect(Config).
-
-pubsub_affiliations_slave(Config) ->
-    pubsub_affiliations_slave(Config, get_event(Config)).
-
-pubsub_affiliations_slave(Config, {outcast, Node, ItemID}) ->
-    #stanza_error{reason = 'forbidden'} = subscribe_node(Config, Node),
-    #stanza_error{} = unsubscribe_node(Config, Node),
-    #stanza_error{reason = 'forbidden'} = get_items(Config, Node),
-    #stanza_error{reason = 'forbidden'} = publish_item(Config, Node),
-    #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID),
-    #stanza_error{reason = 'forbidden'} = purge_node(Config, Node),
-    #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node),
-    #stanza_error{reason = 'forbidden'} =
-       set_node_config(Config, Node, default_node_config(Config)),
-    #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node),
-    #stanza_error{reason = 'forbidden'} =
-       set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]),
-    #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node),
-    #stanza_error{reason = 'forbidden'} =
-       set_affiliations(Config, Node, [{?config(master, Config), outcast},
-                                       {my_jid(Config), owner}]),
-    #stanza_error{reason = 'forbidden'} = delete_node(Config, Node),
-    wait_for_master(Config),
-    pubsub_affiliations_slave(Config, get_event(Config));
-pubsub_affiliations_slave(Config, {none, Node, ItemID}) ->
-    #ps_subscription{type = subscribed} = subscribe_node(Config, Node),
-    ok = unsubscribe_node(Config, Node),
-    %% This violates the affiliation char from section 4.1
-    [_|_] = get_items(Config, Node),
-    #stanza_error{reason = 'forbidden'} = publish_item(Config, Node),
-    #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID),
-    #stanza_error{reason = 'forbidden'} = purge_node(Config, Node),
-    #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node),
-    #stanza_error{reason = 'forbidden'} =
-       set_node_config(Config, Node, default_node_config(Config)),
-    #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node),
-    #stanza_error{reason = 'forbidden'} =
-       set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]),
-    #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node),
-    #stanza_error{reason = 'forbidden'} =
-       set_affiliations(Config, Node, [{?config(master, Config), outcast},
-                                       {my_jid(Config), owner}]),
-    #stanza_error{reason = 'forbidden'} = delete_node(Config, Node),
-    wait_for_master(Config),
-    pubsub_affiliations_slave(Config, get_event(Config));
-pubsub_affiliations_slave(Config, {member, Node, ItemID}) ->
-    #ps_subscription{type = subscribed} = subscribe_node(Config, Node),
-    ok = unsubscribe_node(Config, Node),
-    [_|_] = get_items(Config, Node),
-    #stanza_error{reason = 'forbidden'} = publish_item(Config, Node),
-    #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID),
-    #stanza_error{reason = 'forbidden'} = purge_node(Config, Node),
-    #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node),
-    #stanza_error{reason = 'forbidden'} =
-       set_node_config(Config, Node, default_node_config(Config)),
-    #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node),
-    #stanza_error{reason = 'forbidden'} =
-       set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]),
-    #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node),
-    #stanza_error{reason = 'forbidden'} =
-       set_affiliations(Config, Node, [{?config(master, Config), outcast},
-                                       {my_jid(Config), owner}]),
-    #stanza_error{reason = 'forbidden'} = delete_node(Config, Node),
-    wait_for_master(Config),
-    pubsub_affiliations_slave(Config, get_event(Config));
-pubsub_affiliations_slave(Config, {publish_only, Node, ItemID}) ->
-    #stanza_error{reason = 'forbidden'} = subscribe_node(Config, Node),
-    #stanza_error{} = unsubscribe_node(Config, Node),
-    #stanza_error{reason = 'forbidden'} = get_items(Config, Node),
-    #ps_item{id = MyItemID} = publish_item(Config, Node),
-    %% BUG: This should be fixed
-    %% ?match(ok, delete_item(Config, Node, MyItemID)),
-    #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID),
-    #stanza_error{reason = 'forbidden'} = purge_node(Config, Node),
-    #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node),
-    #stanza_error{reason = 'forbidden'} =
-       set_node_config(Config, Node, default_node_config(Config)),
-    #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node),
-    #stanza_error{reason = 'forbidden'} =
-       set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]),
-    #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node),
-    #stanza_error{reason = 'forbidden'} =
-       set_affiliations(Config, Node, [{?config(master, Config), outcast},
-                                       {my_jid(Config), owner}]),
-    #stanza_error{reason = 'forbidden'} = delete_node(Config, Node),
-    wait_for_master(Config),
-    pubsub_affiliations_slave(Config, get_event(Config));
-pubsub_affiliations_slave(Config, {publisher, Node, ItemID}) ->
-    #ps_subscription{type = subscribed} = subscribe_node(Config, Node),
-    ok = unsubscribe_node(Config, Node),
-    [_|_] = get_items(Config, Node),
-    #ps_item{id = MyItemID} = publish_item(Config, Node),
-    ok = delete_item(Config, Node, MyItemID),
-    %% BUG: this should be fixed
-    %% #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID),
-    #stanza_error{reason = 'forbidden'} = purge_node(Config, Node),
-    #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node),
-    #stanza_error{reason = 'forbidden'} =
-       set_node_config(Config, Node, default_node_config(Config)),
-    #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node),
-    #stanza_error{reason = 'forbidden'} =
-       set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]),
-    #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node),
-    #stanza_error{reason = 'forbidden'} =
-       set_affiliations(Config, Node, [{?config(master, Config), outcast},
-                                       {my_jid(Config), owner}]),
-    #stanza_error{reason = 'forbidden'} = delete_node(Config, Node),
-    wait_for_master(Config),
-    pubsub_affiliations_slave(Config, get_event(Config));
-pubsub_affiliations_slave(Config, {owner, Node, ItemID}) ->
-    MyJID = my_jid(Config),
-    Peer = ?config(master, Config),
-    #ps_subscription{type = subscribed} = subscribe_node(Config, Node),
-    ok = unsubscribe_node(Config, Node),
-    [_|_] = get_items(Config, Node),
-    #ps_item{id = MyItemID} = publish_item(Config, Node),
-    ok = delete_item(Config, Node, MyItemID),
-    ok = delete_item(Config, Node, ItemID),
-    ok = purge_node(Config, Node),
-    [_|_] = get_node_config(Config, Node),
-    ok = set_node_config(Config, Node, default_node_config(Config)),
-    ok = set_subscriptions(Config, Node, []),
-    [] = get_subscriptions(Config, Node),
-    ok = set_affiliations(Config, Node, [{Peer, outcast}, {MyJID, owner}]),
-    [_, _] = get_affiliations(Config, Node),
-    ok = delete_node(Config, Node),
-    wait_for_master(Config),
-    pubsub_affiliations_slave(Config, get_event(Config));
-pubsub_affiliations_slave(Config, disconnect) ->
-    disconnect(Config).
-
-pubsub_authorize_master(Config) ->
-    send(Config, #presence{}),
-    #presence{} = recv_presence(Config),
-    Peer = ?config(slave, Config),
-    PJID = pubsub_jid(Config),
-    NodeConfig = set_opts(default_node_config(Config),
-                         [{access_model, authorize}]),
-    Node = ?config(pubsub_node, Config),
-    Node = create_node(Config, Node, NodeConfig),
-    wait_for_slave(Config),
-    #message{sub_els = [#xdata{fields = F1}]} = recv_message(Config),
-    C1 = pubsub_subscribe_authorization:decode(F1),
-    Node = proplists:get_value(node, C1),
-    Peer = proplists:get_value(subscriber_jid, C1),
-    %% Deny it at first
-    Deny = #xdata{type = submit,
-                 fields = pubsub_subscribe_authorization:encode(
-                            [{node, Node},
-                             {subscriber_jid, Peer},
-                             {allow, false}])},
-    send(Config, #message{to = PJID, sub_els = [Deny]}),
-    %% We should not have any subscriptions
-    [] = get_subscriptions(Config, Node),
-    wait_for_slave(Config),
-    #message{sub_els = [#xdata{fields = F2}]} = recv_message(Config),
-    C2 = pubsub_subscribe_authorization:decode(F2),
-    Node = proplists:get_value(node, C2),
-    Peer = proplists:get_value(subscriber_jid, C2),
-    %% Now we accept is as the peer is very insisting ;)
-    Approve = #xdata{type = submit,
-                    fields = pubsub_subscribe_authorization:encode(
-                               [{node, Node},
-                                {subscriber_jid, Peer},
-                                {allow, true}])},
-    send(Config, #message{to = PJID, sub_els = [Approve]}),
-    wait_for_slave(Config),
-    delete_node(Config, Node),
-    disconnect(Config).
-
-pubsub_authorize_slave(Config) ->
-    Node = ?config(pubsub_node, Config),
-    MyJID = my_jid(Config),
-    wait_for_master(Config),
-    #ps_subscription{type = pending} = subscribe_node(Config, Node),
-    %% We're denied at first
-    #message{
-       sub_els =
-          [#ps_event{
-              subscription = #ps_subscription{type = none,
-                                              jid = MyJID}}]} =
-       recv_message(Config),
-    wait_for_master(Config),
-    #ps_subscription{type = pending} = subscribe_node(Config, Node),
-    %% Now much better!
-    #message{
-       sub_els =
-          [#ps_event{
-              subscription = #ps_subscription{type = subscribed,
-                                              jid = MyJID}}]} =
-       recv_message(Config),
-    wait_for_master(Config),
-    disconnect(Config).
-
-create_node(Config, Node) ->
-    create_node(Config, Node, undefined).
-
-create_node(Config, Node, Options) ->
-    PJID = pubsub_jid(Config),
-    NodeConfig = if is_list(Options) ->
-                        #xdata{type = submit,
-                               fields = pubsub_node_config:encode(Options)};
-                   true ->
-                        undefined
-                end,
-    case send_recv(Config,
-                  #iq{type = set, to = PJID,
-                      sub_els = [#pubsub{create = Node,
-                                         configure = {<<>>, NodeConfig}}]}) of
-       #iq{type = result, sub_els = [#pubsub{create = NewNode}]} ->
-           NewNode;
-       #iq{type = error} = IQ ->
-           xmpp:get_subtag(IQ, #stanza_error{})
-    end.
-
-delete_node(Config, Node) ->
-    PJID = pubsub_jid(Config),
-    case send_recv(Config,
-                  #iq{type = set, to = PJID,
-                      sub_els = [#pubsub_owner{delete = {Node, <<>>}}]}) of
-       #iq{type = result, sub_els = []} ->
-           ok;
-       #iq{type = error} = IQ ->
-           xmpp:get_subtag(IQ, #stanza_error{})
-    end.
-
-purge_node(Config, Node) ->
-    PJID = pubsub_jid(Config),
-    case send_recv(Config,
-                  #iq{type = set, to = PJID,
-                      sub_els = [#pubsub_owner{purge = Node}]}) of
-       #iq{type = result, sub_els = []} ->
-           ok;
-       #iq{type = error} = IQ ->
-           xmpp:get_subtag(IQ, #stanza_error{})
-    end.
-
-get_default_node_config(Config) ->
-    PJID = pubsub_jid(Config),
-    case send_recv(Config,
-                  #iq{type = get, to = PJID,
-                      sub_els = [#pubsub_owner{default = {<<>>, undefined}}]}) of
-       #iq{type = result,
-           sub_els = [#pubsub_owner{default = {<<>>, NodeConfig}}]} ->
-           pubsub_node_config:decode(NodeConfig#xdata.fields);
-       #iq{type = error} = IQ ->
-           xmpp:get_subtag(IQ, #stanza_error{})
-    end.
-
-get_node_config(Config, Node) ->
-    PJID = pubsub_jid(Config),
-    case send_recv(Config,
-                  #iq{type = get, to = PJID,
-                      sub_els = [#pubsub_owner{configure = {Node, undefined}}]}) of
-       #iq{type = result,
-           sub_els = [#pubsub_owner{configure = {Node, NodeConfig}}]} ->
-           pubsub_node_config:decode(NodeConfig#xdata.fields);
-       #iq{type = error} = IQ ->
-           xmpp:get_subtag(IQ, #stanza_error{})
-    end.
-
-set_node_config(Config, Node, Options) ->
-    PJID = pubsub_jid(Config),
-    NodeConfig = #xdata{type = submit,
-                       fields = pubsub_node_config:encode(Options)},
-    case send_recv(Config,
-                  #iq{type = set, to = PJID,
-                      sub_els = [#pubsub_owner{configure =
-                                                   {Node, NodeConfig}}]}) of
-       #iq{type = result, sub_els = []} ->
-           ok;
-       #iq{type = error} = IQ ->
-           xmpp:get_subtag(IQ, #stanza_error{})
-    end.
-
-publish_item(Config, Node) ->
-    PJID = pubsub_jid(Config),
-    ItemID = randoms:get_string(),
-    Item = #ps_item{id = ItemID, xml_els = [xmpp:encode(#presence{id = ItemID})]},
-    case send_recv(Config,
-                  #iq{type = set, to = PJID,
-                      sub_els = [#pubsub{publish = #ps_publish{
-                                                      node = Node,
-                                                      items = [Item]}}]}) of
-       #iq{type = result,
-           sub_els = [#pubsub{publish = #ps_publish{
-                                           node = Node,
-                                           items = [#ps_item{id = ItemID}]}}]} ->
-           Item;
-       #iq{type = error} = IQ ->
-           xmpp:get_subtag(IQ, #stanza_error{})
-    end.
-
-get_items(Config, Node) ->
-    PJID = pubsub_jid(Config),
-    case send_recv(Config,
-                  #iq{type = get, to = PJID,
-                      sub_els = [#pubsub{items = #ps_items{node = Node}}]}) of
-       #iq{type = result,
-           sub_els = [#pubsub{items = #ps_items{node = Node, items = Items}}]} ->
-           Items;
-       #iq{type = error} = IQ ->
-           xmpp:get_subtag(IQ, #stanza_error{})
-    end.
-
-delete_item(Config, Node, I) ->
-    PJID = pubsub_jid(Config),
-    case send_recv(Config,
-                  #iq{type = set, to = PJID,
-                      sub_els = [#pubsub{retract =
-                                             #ps_retract{
-                                                node = Node,
-                                                items = [#ps_item{id = I}]}}]}) of
-       #iq{type = result, sub_els = []} ->
-           ok;
-       #iq{type = error} = IQ ->
-           xmpp:get_subtag(IQ, #stanza_error{})
-    end.
-
-subscribe_node(Config, Node) ->
-    PJID = pubsub_jid(Config),
-    MyJID = my_jid(Config),
-    case send_recv(Config,
-                  #iq{type = set, to = PJID,
-                      sub_els = [#pubsub{subscribe = #ps_subscribe{
-                                                        node = Node,
-                                                        jid = MyJID}}]}) of
-       #iq{type = result,
-           sub_els = [#pubsub{
-                         subscription = #ps_subscription{
-                                           node = Node,
-                                           jid = MyJID} = Sub}]} ->
-           Sub;
-       #iq{type = error} = IQ ->
-           xmpp:get_subtag(IQ, #stanza_error{})
-    end.
-
-unsubscribe_node(Config, Node) ->
-    PJID = pubsub_jid(Config),
-    MyJID = my_jid(Config),
-    case send_recv(Config,
-                  #iq{type = set, to = PJID,
-                      sub_els = [#pubsub{
-                                    unsubscribe = #ps_unsubscribe{
-                                                     node = Node,
-                                                     jid = MyJID}}]}) of
-       #iq{type = result, sub_els = []} ->
-           ok;
-       #iq{type = error} = IQ ->
-           xmpp:get_subtag(IQ, #stanza_error{})
-    end.
-
-get_affiliations(Config) ->
-    PJID = pubsub_jid(Config),
-    case send_recv(Config,
-                  #iq{type = get, to = PJID,
-                      sub_els = [#pubsub{affiliations = {<<>>, []}}]}) of
-       #iq{type = result,
-           sub_els = [#pubsub{affiliations = {<<>>, Affs}}]} ->
-           Affs;
-       #iq{type = error} = IQ ->
-           xmpp:get_subtag(IQ, #stanza_error{})
-    end.
-
-get_affiliations(Config, Node) ->
-    PJID = pubsub_jid(Config),
-    case send_recv(Config,
-                  #iq{type = get, to = PJID,
-                      sub_els = [#pubsub_owner{affiliations = {Node, []}}]}) of
-       #iq{type = result,
-           sub_els = [#pubsub_owner{affiliations = {Node, Affs}}]} ->
-           Affs;
-       #iq{type = error} = IQ ->
-           xmpp:get_subtag(IQ, #stanza_error{})
-    end.
-
-set_affiliations(Config, Node, JTs) ->
-    PJID = pubsub_jid(Config),
-    Affs = [#ps_affiliation{jid = J, type = T} || {J, T} <- JTs],
-    case send_recv(Config,
-                  #iq{type = set, to = PJID,
-                      sub_els = [#pubsub_owner{affiliations =
-                                                   {Node, Affs}}]}) of
-       #iq{type = result, sub_els = []} ->
-           ok;
-       #iq{type = error} = IQ ->
-           xmpp:get_subtag(IQ, #stanza_error{})
-    end.
-
-get_subscriptions(Config) ->
-    PJID = pubsub_jid(Config),
-    case send_recv(Config,
-                  #iq{type = get, to = PJID,
-                      sub_els = [#pubsub{subscriptions = {<<>>, []}}]}) of
-       #iq{type = result, sub_els = [#pubsub{subscriptions = {<<>>, Subs}}]} ->
-           Subs;
-       #iq{type = error} = IQ ->
-           xmpp:get_subtag(IQ, #stanza_error{})
-    end.
-
-get_subscriptions(Config, Node) ->
-    PJID = pubsub_jid(Config),
-    case send_recv(Config,
-                  #iq{type = get, to = PJID,
-                      sub_els = [#pubsub_owner{subscriptions = {Node, []}}]}) of
-       #iq{type = result,
-           sub_els = [#pubsub_owner{subscriptions = {Node, Subs}}]} ->
-           Subs;
-       #iq{type = error} = IQ ->
-           xmpp:get_subtag(IQ, #stanza_error{})
-    end.
-
-set_subscriptions(Config, Node, JTs) ->
-    PJID = pubsub_jid(Config),
-    Subs = [#ps_subscription{jid = J, type = T} || {J, T} <- JTs],
-    case send_recv(Config,
-                  #iq{type = set, to = PJID,
-                      sub_els = [#pubsub_owner{subscriptions =
-                                                   {Node, Subs}}]}) of
-       #iq{type = result, sub_els = []} ->
-           ok;
-       #iq{type = error} = IQ ->
-           xmpp:get_subtag(IQ, #stanza_error{})
-    end.
-
-default_node_config(Config) ->
-    [{title, ?config(pubsub_node_title, Config)},
-     {notify_delete, false},
-     {send_last_published_item, never}].
-
-mix_master(Config) ->
-    MIX = mix_jid(Config),
-    Room = mix_room_jid(Config),
-    MyJID = my_jid(Config),
-    MyBareJID = jid:remove_resource(MyJID),
-    true = is_feature_advertised(Config, ?NS_MIX_0, MIX),
-    #iq{type = result,
-       sub_els =
-           [#disco_info{
-               identities = [#identity{category = <<"conference">>,
-                                       type = <<"text">>}],
-               xdata = [#xdata{type = result, fields = XFields}]}]} =
-       send_recv(Config, #iq{type = get, to = MIX, sub_els = [#disco_info{}]}),
-    true = lists:any(
-            fun(#xdata_field{var = <<"FORM_TYPE">>,
-                             values = [?NS_MIX_SERVICEINFO_0]}) -> true;
-               (_) -> false
-            end, XFields),
-    %% Joining
-    Nodes = [?NS_MIX_NODES_MESSAGES, ?NS_MIX_NODES_PRESENCE,
-            ?NS_MIX_NODES_PARTICIPANTS, ?NS_MIX_NODES_SUBJECT,
-            ?NS_MIX_NODES_CONFIG],
-    #iq{type = result,
-       sub_els = [#mix_join{subscribe = Nodes, jid = MyBareJID}]} =
-       send_recv(Config, #iq{type = set, to = Room,
-                             sub_els = [#mix_join{subscribe = Nodes}]}),
-    #message{from = Room,
-            sub_els =
-                [#ps_event{
-                    items = #ps_items{
-                               node = ?NS_MIX_NODES_PARTICIPANTS,
-                               items = [#ps_item{
-                                           id = ParticipantID,
-                                           xml_els = [PXML]}]}}]} =
-       recv_message(Config),
-    #mix_participant{jid = MyBareJID} = xmpp:decode(PXML),
-    %% Coming online
-    PresenceID = randoms:get_string(),
-    Presence = xmpp:encode(#presence{}),
-    #iq{type = result,
-       sub_els =
-           [#pubsub{
-               publish = #ps_publish{
-                            node = ?NS_MIX_NODES_PRESENCE,
-                            items = [#ps_item{id = PresenceID}]}}]} =
-       send_recv(
-         Config,
-         #iq{type = set, to = Room,
-             sub_els =
-                 [#pubsub{
-                     publish = #ps_publish{
-                                  node = ?NS_MIX_NODES_PRESENCE,
-                                  items = [#ps_item{
-                                              id = PresenceID,
-                                              xml_els = [Presence]}]}}]}),
-    #message{from = Room,
-            sub_els =
-                [#ps_event{
-                    items = #ps_items{
-                               node = ?NS_MIX_NODES_PRESENCE,
-                               items = [#ps_item{
-                                           id = PresenceID,
-                                           xml_els = [Presence]}]}}]} =
-       recv_message(Config),
-    %% Coming offline
-    send(Config, #presence{type = unavailable, to = Room}),
-    %% Receiving presence retract event
-    #message{from = Room,
-            sub_els = [#ps_event{
-                          items = #ps_items{
-                                     node = ?NS_MIX_NODES_PRESENCE,
-                                     retract = PresenceID}}]} =
-       recv_message(Config),
-    %% Leaving
-    #iq{type = result, sub_els = []} =
-       send_recv(Config, #iq{type = set, to = Room, sub_els = [#mix_leave{}]}),
-    #message{from = Room,
-            sub_els =
-                [#ps_event{
-                    items = #ps_items{
-                               node = ?NS_MIX_NODES_PARTICIPANTS,
-                               retract = ParticipantID}}]} =
-       recv_message(Config),
-    put_event(Config, disconnect),
-    disconnect(Config).
-
-mix_slave(Config) ->
-    disconnect = get_event(Config),
-    disconnect(Config).
-
-proxy65_master(Config) ->
-    Proxy = proxy_jid(Config),
-    MyJID = my_jid(Config),
-    Peer = ?config(slave, Config),
-    wait_for_slave(Config),
-    send(Config, #presence{}),
-    #presence{from = MyJID, type = available} = recv_presence(Config),
-    true = is_feature_advertised(Config, ?NS_BYTESTREAMS, Proxy),
-    #iq{type = result, sub_els = [#bytestreams{hosts = [StreamHost]}]} =
-        send_recv(
-          Config,
-          #iq{type = get, sub_els = [#bytestreams{}], to = Proxy}),
-    SID = randoms:get_string(),
-    Data = crypto:rand_bytes(1024),
-    put_event(Config, {StreamHost, SID, Data}),
-    Socks5 = socks5_connect(StreamHost, {SID, MyJID, Peer}),
-    wait_for_slave(Config),
-    #iq{type = result, sub_els = []} =
-        send_recv(Config,
-                  #iq{type = set, to = Proxy,
-                      sub_els = [#bytestreams{activate = Peer, sid = SID}]}),
-    socks5_send(Socks5, Data),
-    %%?recv1(#presence{type = unavailable, from = Peer}),
-    disconnect(Config).
-
-proxy65_slave(Config) ->
-    MyJID = my_jid(Config),
-    Peer = ?config(master, Config),
-    send(Config, #presence{}),
-    #presence{from = MyJID, type = available} = recv_presence(Config),
-    wait_for_master(Config),
-    {StreamHost, SID, Data} = get_event(Config),
-    Socks5 = socks5_connect(StreamHost, {SID, Peer, MyJID}),
-    wait_for_master(Config),
-    socks5_recv(Socks5, Data),
-    disconnect(Config).
-
-send_messages_to_room(Config, Range) ->
-    MyNick = ?config(master_nick, Config),
-    Room = muc_room_jid(Config),
-    MyNickJID = jid:replace_resource(Room, MyNick),
-    lists:foreach(
-      fun(N) ->
-              Text = #text{data = integer_to_binary(N)},
-             #message{from = MyNickJID, id = I,
-                      type = groupchat,
-                      body = [Text]} =
-                 send_recv(Config, #message{to = Room, body = [Text],
-                                            type = groupchat})
-      end, Range).
-
-retrieve_messages_from_room_via_mam(Config, Range) ->
-    MyNick = ?config(master_nick, Config),
-    Room = muc_room_jid(Config),
-    MyNickJID = jid:replace_resource(Room, MyNick),
-    MyJID = my_jid(Config),
-    QID = randoms:get_string(),
-    Count = length(Range),
-    I = send(Config, #iq{type = set, to = Room,
-                        sub_els = [#mam_query{xmlns = ?NS_MAM_1, id = QID}]}),
-    lists:foreach(
-      fun(N) ->
-             Text = #text{data = integer_to_binary(N)},
-             #message{
-                to = MyJID, from = Room,
-                sub_els =
-                    [#mam_result{
-                        xmlns = ?NS_MAM_1,
-                        queryid = QID,
-                        sub_els =
-                            [#forwarded{
-                                delay = #delay{},
-                                sub_els = [#message{
-                                              from = MyNickJID,
-                                              type = groupchat,
-                                              body = [Text]}]}]}]} =
-                 recv_message(Config)
-      end, Range),
-    #iq{from = Room, id = I, type = result,
-       sub_els = [#mam_fin{xmlns = ?NS_MAM_1,
-                           id = QID,
-                           rsm = #rsm_set{count = Count},
-                           complete = true}]} = recv_iq(Config).
-
-muc_mam_master(Config) ->
-    MyNick = ?config(master_nick, Config),
-    Room = muc_room_jid(Config),
-    MyNickJID = jid:replace_resource(Room, MyNick),
-    %% Joining
-    ok = muc_tests:muc_join_new(Config),
-    %% MAM feature should not be advertised at this point,
-    %% because MAM is not enabled so far
-    false = is_feature_advertised(Config, ?NS_MAM_1, Room),
-    %% Fill in some history
-    send_messages_to_room(Config, lists:seq(1, 21)),
-    %% We now should be able to retrieve those via MAM, even though
-    %% MAM is disabled. However, only last 20 messages should be received.
-    retrieve_messages_from_room_via_mam(Config, lists:seq(2, 21)),
-    %% Now enable MAM for the conference
-    %% Retrieve config first
-    #iq{type = result, sub_els = [#muc_owner{config = #xdata{} = RoomCfg}]} =
-        send_recv(Config, #iq{type = get, sub_els = [#muc_owner{}],
-                              to = Room}),
-    %% Find the MAM field in the config and enable it
-    NewFields = lists:flatmap(
-                 fun(#xdata_field{var = <<"mam">> = Var}) ->
-                         [#xdata_field{var = Var, values = [<<"1">>]}];
-                    (_) ->
-                         []
-                 end, RoomCfg#xdata.fields),
-    NewRoomCfg = #xdata{type = submit, fields = NewFields},
-    #iq{type = result, sub_els = []} =
-       send_recv(Config, #iq{type = set, to = Room,
-                             sub_els = [#muc_owner{config = NewRoomCfg}]}),
-    #message{from = Room, type = groupchat,
-            sub_els = [#muc_user{status_codes = [104]}]} = recv_message(Config),
-    %% Check if MAM has been enabled
-    true = is_feature_advertised(Config, ?NS_MAM_1, Room),
-    %% We now sending some messages again
-    send_messages_to_room(Config, lists:seq(1, 5)),
-    %% And retrieve them via MAM again.
-    retrieve_messages_from_room_via_mam(Config, lists:seq(1, 5)),
-    put_event(Config, disconnect),
-    disconnect(Config).
-
-muc_mam_slave(Config) ->
-    disconnect = get_event(Config),
-    disconnect(Config).
-
-%% 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_tests:muc_register_master(Config).
-muc_register_slave(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).
-
-offline_feature_enabled(Config) ->
-    offline_tests:feature_enabled(Config).
-offline_check_identity(Config) ->
-    offline_tests:check_identity(Config).
-offline_send_non_existent(Config) ->
-    offline_tests:send_non_existent(Config).
-offline_view_non_existent(Config) ->
-    offline_tests:view_non_existent(Config).
-offline_remove_non_existent(Config) ->
-    offline_tests:remove_non_existent(Config).
-offline_view_non_integer(Config) ->
-    offline_tests:view_non_integer(Config).
-offline_remove_non_integer(Config) ->
-    offline_tests:remove_non_integer(Config).
-offline_malformed_iq(Config) ->
-    offline_tests:malformed_iq(Config).
-offline_wrong_user(Config) ->
-    offline_tests:wrong_user(Config).
-offline_unsupported_iq(Config) ->
-    offline_tests:unsupported_iq(Config).
-offline_flex_master(Config) ->
-    offline_tests:flex_master(Config).
-offline_flex_slave(Config) ->
-    offline_tests:flex_slave(Config).
-offline_send_all_master(Config) ->
-    offline_tests:send_all_master(Config).
-offline_send_all_slave(Config) ->
-    offline_tests:send_all_slave(Config).
-
-announce_master(Config) ->
-    MyJID = my_jid(Config),
-    ServerJID = server_jid(Config),
-    MotdJID = jid:replace_resource(ServerJID, <<"announce/motd">>),
-    MotdText = #text{data = <<"motd">>},
-    #presence{from = MyJID} = send_recv(Config, #presence{}),
-    %% Set message of the day
-    send(Config, #message{to = MotdJID, body = [MotdText]}),
-    %% Receive this message back
-    #message{from = ServerJID, body = [MotdText]} = recv_message(Config),
-    disconnect(Config).
-
-announce_slave(Config) ->
-    MyJID = my_jid(Config),
-    ServerJID = server_jid(Config),
-    MotdDelJID = jid:replace_resource(ServerJID, <<"announce/motd/delete">>),
-    MotdText = #text{data = <<"motd">>},
-    #presence{from = MyJID} = send_recv(Config, #presence{}),
-    #message{from = ServerJID, body = [MotdText]} = recv_message(Config),
-    %% Delete message of the day
-    send(Config, #message{to = MotdDelJID}),
-    disconnect(Config).
-
-carbons_master(Config) ->
-    MyJID = my_jid(Config),
-    MyBareJID = jid:remove_resource(MyJID),
-    Peer = ?config(slave, Config),
-    Txt = #text{data = <<"body">>},
-    true = is_feature_advertised(Config, ?NS_CARBONS_2),
-    #presence{from = MyJID} = send_recv(Config, #presence{priority = 10}),
-    wait_for_slave(Config),
-    #presence{from = Peer} = recv_presence(Config),
-    %% Enable carbons
-    #iq{type = result, sub_els = []} =
-       send_recv(Config,
-                 #iq{type = set,
-                     sub_els = [#carbons_enable{}]}),
-    %% Send a message to bare and full JID
-    send(Config, #message{to = MyBareJID, type = chat, body = [Txt]}),
-    send(Config, #message{to = MyJID, type = chat, body = [Txt]}),
-    send(Config, #message{to = MyBareJID, type = chat, body = [Txt],
-                         sub_els = [#carbons_private{}]}),
-    send(Config, #message{to = MyJID, type = chat, body = [Txt],
-                         sub_els = [#carbons_private{}]}),
-    %% Receive the messages back
-    ?recv4(#message{from = MyJID, to = MyBareJID, type = chat,
-                   body = [Txt], sub_els = []},
-          #message{from = MyJID, to = MyJID, type = chat,
-                   body = [Txt], sub_els = []},
-          #message{from = MyJID, to = MyBareJID, type = chat,
-                   body = [Txt], sub_els = [#carbons_private{}]},
-          #message{from = MyJID, to = MyJID, type = chat,
-                   body = [Txt], sub_els = [#carbons_private{}]}),
-    %% Disable carbons
-    #iq{type = result, sub_els = []} =
-       send_recv(Config,
-                 #iq{type = set,
-                     sub_els = [#carbons_disable{}]}),
-    wait_for_slave(Config),
-    %% Repeat the same and leave
-    send(Config, #message{to = MyBareJID, type = chat, body = [Txt]}),
-    send(Config, #message{to = MyJID, type = chat, body = [Txt]}),
-    send(Config, #message{to = MyBareJID, type = chat, body = [Txt],
-                         sub_els = [#carbons_private{}]}),
-    send(Config, #message{to = MyJID, type = chat, body = [Txt],
-                         sub_els = [#carbons_private{}]}),
-    ?recv4(#message{from = MyJID, to = MyBareJID, type = chat,
-                   body = [Txt], sub_els = []},
-          #message{from = MyJID, to = MyJID, type = chat,
-                   body = [Txt], sub_els = []},
-          #message{from = MyJID, to = MyBareJID, type = chat,
-                   body = [Txt], sub_els = [#carbons_private{}]},
-          #message{from = MyJID, to = MyJID, type = chat,
-                   body = [Txt], sub_els = [#carbons_private{}]}),
-    disconnect(Config).
-
-carbons_slave(Config) ->
-    MyJID = my_jid(Config),
-    MyBareJID = jid:remove_resource(MyJID),
-    Peer = ?config(master, Config),
-    Txt = #text{data = <<"body">>},
-    wait_for_master(Config),
-    #presence{from = MyJID} = send_recv(Config, #presence{priority = 5}),
-    #presence{from = Peer} = recv_presence(Config),
-    %% Enable carbons
-    #iq{type = result, sub_els = []} =
-       send_recv(Config,
-                 #iq{type = set,
-                     sub_els = [#carbons_enable{}]}),
-    %% Receive messages sent by the peer
-    ?recv4(
-       #message{from = MyBareJID, to = MyJID, type = chat,
-               sub_els =
-                   [#carbons_sent{
-                       forwarded = #forwarded{
-                                      sub_els =
-                                          [#message{from = Peer,
-                                                    to = MyBareJID,
-                                                    type = chat,
-                                                    body = [Txt]}]}}]},
-       #message{from = MyBareJID, to = MyJID, type = chat,
-               sub_els =
-                   [#carbons_sent{
-                       forwarded = #forwarded{
-                                      sub_els =
-                                          [#message{from = Peer,
-                                                    to = Peer,
-                                                    type = chat,
-                                                    body = [Txt]}]}}]},
-       #message{from = MyBareJID, to = MyJID, type = chat,
-               sub_els =
-                   [#carbons_received{
-                       forwarded = #forwarded{
-                                      sub_els =
-                                          [#message{from = Peer,
-                                                    to = MyBareJID,
-                                                    type = chat,
-                                                    body = [Txt]}]}}]},
-       #message{from = MyBareJID, to = MyJID, type = chat,
-               sub_els =
-                   [#carbons_received{
-                       forwarded = #forwarded{
-                                      sub_els =
-                                          [#message{from = Peer,
-                                                    to = Peer,
-                                                    type = chat,
-                                                    body = [Txt]}]}}]}),
-    %% Disable carbons
-    #iq{type = result, sub_els = []} =
-       send_recv(Config,
-                 #iq{type = set,
-                     sub_els = [#carbons_disable{}]}),
-    wait_for_master(Config),
-    %% Now we should receive nothing but presence unavailable from the peer
-    #presence{from = Peer, type = unavailable} = recv_presence(Config),
-    disconnect(Config).
-
-mam_old_master(Config) ->
-    mam_master(Config, ?NS_MAM_TMP).
-
-mam_new_master(Config) ->
-    mam_master(Config, ?NS_MAM_0).
-
-mam_master(Config, NS) ->
-    true = is_feature_advertised(Config, NS),
-    MyJID = my_jid(Config),
-    BareMyJID = jid:remove_resource(MyJID),
-    Peer = ?config(slave, Config),
-    #presence{} = send_recv(Config, #presence{}),
-    wait_for_slave(Config),
-    #presence{from = Peer} = recv_presence(Config),
-    #iq{type = result, sub_els = [#mam_prefs{xmlns = NS, default = roster}]} =
-        send_recv(Config,
-                  #iq{type = set,
-                      sub_els = [#mam_prefs{xmlns = NS,
-                                           default = roster,
-                                            never = [MyJID]}]}),
-    if NS == ?NS_MAM_TMP ->
-           %% NOTE: The server should strip fake archived tags,
-           %% i.e. the sub_els received should be [].
-           FakeArchived = #mam_archived{id = randoms:get_string(),
-                                        by = server_jid(Config)},
-           #message{body = [#text{data = <<"a">>}], sub_els = []} =
-               send_recv(Config, #message{to = MyJID,
-                                          sub_els = [FakeArchived],
-                                          body = [#text{data = <<"a">>}]}),
-           #message{body = [#text{data = <<"b">>}], sub_els = []} =
-               send_recv(Config, #message{to = BareMyJID,
-                                          sub_els = [FakeArchived],
-                                          body = [#text{data = <<"b">>}]});
-       true ->
-           ok
-    end,
-    wait_for_slave(Config),
-    lists:foreach(
-      fun(N) ->
-              Text = #text{data = integer_to_binary(N)},
-              send(Config, #message{to = Peer, body = [Text]})
-      end, lists:seq(1, 5)),
-    #presence{type = unavailable, from = Peer} = recv_presence(Config),
-    mam_query_all(Config, NS),
-    mam_query_with(Config, Peer, NS),
-    %% mam_query_with(Config, jid:remove_resource(Peer)),
-    mam_query_rsm(Config, NS),
-    #iq{type = result, sub_els = [#mam_prefs{xmlns = NS, default = never}]} =
-        send_recv(Config, #iq{type = set,
-                              sub_els = [#mam_prefs{xmlns = NS,
-                                                   default = never}]}),
-    disconnect(Config).
-
-mam_old_slave(Config) ->
-    mam_slave(Config, ?NS_MAM_TMP).
-
-mam_new_slave(Config) ->
-    mam_slave(Config, ?NS_MAM_0).
-
-mam_slave(Config, NS) ->
-    Peer = ?config(master, Config),
-    MyJID = my_jid(Config),
-    ServerJID = server_jid(Config),
-    wait_for_master(Config),
-    #presence{from = MyJID} = send_recv(Config, #presence{}),
-    #presence{from = Peer} = recv_presence(Config),
-    #iq{type = result, sub_els = [#mam_prefs{xmlns = NS, default = always}]} =
-        send_recv(Config,
-                  #iq{type = set,
-                      sub_els = [#mam_prefs{xmlns = NS, default = always}]}),
-    wait_for_master(Config),
-    lists:foreach(
-      fun(N) ->
-              Text = #text{data = integer_to_binary(N)},
-             Msg = #message{from = Peer, body = [Text]} = recv_message(Config),
-             #mam_archived{by = ServerJID} =
-                 xmpp:get_subtag(Msg, #mam_archived{}),
-             #stanza_id{by = ServerJID} =
-                 xmpp:get_subtag(Msg, #stanza_id{})
-      end, lists:seq(1, 5)),
-    #iq{type = result, sub_els = [#mam_prefs{xmlns = NS, default = never}]} =
-        send_recv(Config, #iq{type = set,
-                              sub_els = [#mam_prefs{xmlns = NS, default = never}]}),
-    disconnect(Config).
-
-mam_query_all(Config, NS) ->
-    QID = randoms:get_string(),
-    MyJID = my_jid(Config),
-    Peer = ?config(slave, Config),
-    Type = case NS of
-              ?NS_MAM_TMP -> get;
-              _ -> set
-          end,
-    I = send(Config, #iq{type = Type, sub_els = [#mam_query{xmlns = NS, id = QID}]}),
-    maybe_recv_iq_result(Config, NS, I),
-    Iter = if NS == ?NS_MAM_TMP -> lists:seq(1, 5);
-             true -> lists:seq(1, 5) ++ lists:seq(1, 5)
-          end,
-    lists:foreach(
-      fun(N) ->
-              Text = #text{data = integer_to_binary(N)},
-              #message{to = MyJID,
-                       sub_els =
-                           [#mam_result{
-                               queryid = QID,
-                               sub_els =
-                                   [#forwarded{
-                                       delay = #delay{},
-                                       sub_els =
-                                           [#message{
-                                               from = MyJID, to = Peer,
-                                               body = [Text]}]}]}]} =
-                 recv_message(Config)
-      end, Iter),
-    if NS == ?NS_MAM_TMP ->
-           #iq{type = result, id = I,
-               sub_els = [#mam_query{xmlns = NS, id = QID}]} = recv_iq(Config);
-       true ->
-           #message{sub_els = [#mam_fin{complete = true, id = QID}]} =
-               recv_message(Config)
-    end.
-
-mam_query_with(Config, JID, NS) ->
-    MyJID = my_jid(Config),
-    Peer = ?config(slave, Config),
-    {Query, Type} = if NS == ?NS_MAM_TMP ->
-                   {#mam_query{xmlns = NS, with = JID}, get};
-              true ->
-                   Fs = [#xdata_field{var = <<"jid">>,
-                                      values = [jid:to_string(JID)]}],
-                   {#mam_query{xmlns = NS,
-                              xdata = #xdata{type = submit, fields = Fs}}, set}
-           end,
-    I = send(Config, #iq{type = Type, sub_els = [Query]}),
-    Iter = if NS == ?NS_MAM_TMP -> lists:seq(1, 5);
-             true -> lists:seq(1, 5) ++ lists:seq(1, 5)
-          end,
-    maybe_recv_iq_result(Config, NS, I),
-    lists:foreach(
-      fun(N) ->
-              Text = #text{data = integer_to_binary(N)},
-              #message{to = MyJID,
-                       sub_els =
-                           [#mam_result{
-                               sub_els =
-                                   [#forwarded{
-                                       delay = #delay{},
-                                       sub_els =
-                                           [#message{
-                                               from = MyJID, to = Peer,
-                                               body = [Text]}]}]}]} =
-                 recv_message(Config)
-      end, Iter),
-    if NS == ?NS_MAM_TMP ->
-           #iq{type = result, id = I,
-               sub_els = [#mam_query{xmlns = NS}]} = recv_iq(Config);
-       true ->
-           #message{sub_els = [#mam_fin{complete = true}]} =
-               recv_message(Config)
-    end.
-
-maybe_recv_iq_result(Config, ?NS_MAM_0, I1) ->
-    #iq{type = result, id = I1} = recv_iq(Config);
-maybe_recv_iq_result(_, _, _) ->
-    ok.
-
-mam_query_rsm(Config, NS) ->
-    MyJID = my_jid(Config),
-    Peer = ?config(slave, Config),
-    Type = case NS of
-              ?NS_MAM_TMP -> get;
-              _ -> set
-          end,
-    %% Get the first 3 items out of 5
-    I1 = send(Config,
-              #iq{type = Type,
-                  sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{max = 3}}]}),
-    maybe_recv_iq_result(Config, NS, I1),
-    lists:foreach(
-      fun(N) ->
-              Text = #text{data = integer_to_binary(N)},
-              #message{to = MyJID,
-                       sub_els =
-                           [#mam_result{
-                              xmlns = NS,
-                               sub_els =
-                                   [#forwarded{
-                                       delay = #delay{},
-                                       sub_els =
-                                           [#message{
-                                               from = MyJID, to = Peer,
-                                               body = [Text]}]}]}]} =
-                 recv_message(Config)
-      end, lists:seq(1, 3)),
-    if NS == ?NS_MAM_TMP ->
-           #iq{type = result, id = I1,
-               sub_els = [#mam_query{xmlns = NS,
-                                     rsm = #rsm_set{last = Last,
-                                                    count = 5}}]} =
-               recv_iq(Config);
-       true ->
-           #message{sub_els = [#mam_fin{
-                                  complete = false,
-                                  rsm = #rsm_set{last = Last,
-                                                 count = 10}}]} =
-               recv_message(Config)
-    end,
-    %% Get the next items starting from the `Last`.
-    %% Limit the response to 2 items.
-    I2 = send(Config,
-              #iq{type = Type,
-                  sub_els = [#mam_query{xmlns = NS,
-                                       rsm = #rsm_set{max = 2,
-                                                       'after' = Last}}]}),
-    maybe_recv_iq_result(Config, NS, I2),
-    lists:foreach(
-      fun(N) ->
-              Text = #text{data = integer_to_binary(N)},
-              #message{to = MyJID,
-                       sub_els =
-                           [#mam_result{
-                              xmlns = NS,
-                               sub_els =
-                                   [#forwarded{
-                                       delay = #delay{},
-                                       sub_els =
-                                           [#message{
-                                               from = MyJID, to = Peer,
-                                               body = [Text]}]}]}]} =
-                 recv_message(Config)
-      end, lists:seq(4, 5)),
-    if NS == ?NS_MAM_TMP ->
-           #iq{type = result, id = I2,
-               sub_els = [#mam_query{
-                             xmlns = NS,
-                             rsm = #rsm_set{
-                                      count = 5,
-                                      first = #rsm_first{data = First}}}]} =
-               recv_iq(Config);
-       true ->
-           #message{
-              sub_els = [#mam_fin{
-                            complete = false,
-                            rsm = #rsm_set{
-                                     count = 10,
-                                     first = #rsm_first{data = First}}}]} =
-               recv_message(Config)
-    end,
-    %% Paging back. Should receive 3 elements: 1, 2, 3.
-    I3 = send(Config,
-              #iq{type = Type,
-                  sub_els = [#mam_query{xmlns = NS,
-                                       rsm = #rsm_set{max = 3,
-                                                       before = First}}]}),
-    maybe_recv_iq_result(Config, NS, I3),
-    lists:foreach(
-      fun(N) ->
-              Text = #text{data = integer_to_binary(N)},
-              #message{to = MyJID,
-                       sub_els =
-                           [#mam_result{
-                              xmlns = NS,
-                               sub_els =
-                                   [#forwarded{
-                                       delay = #delay{},
-                                       sub_els =
-                                           [#message{
-                                               from = MyJID, to = Peer,
-                                               body = [Text]}]}]}]} =
-                 recv_message(Config)
-      end, lists:seq(1, 3)),
-    if NS == ?NS_MAM_TMP ->
-           #iq{type = result, id = I3,
-               sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{count = 5}}]} =
-               recv_iq(Config);
-       true ->
-           #message{
-              sub_els = [#mam_fin{complete = true,
-                                  rsm = #rsm_set{count = 10}}]} =
-               recv_message(Config)
-    end,
-    %% Getting the item count. Should be 5 (or 10).
-    I4 = send(Config,
-             #iq{type = Type,
-                 sub_els = [#mam_query{xmlns = NS,
-                                       rsm = #rsm_set{max = 0}}]}),
-    maybe_recv_iq_result(Config, NS, I4),
-    if NS == ?NS_MAM_TMP ->
-           #iq{type = result, id = I4,
-               sub_els = [#mam_query{
-                             xmlns = NS,
-                             rsm = #rsm_set{count = 5,
-                                            first = undefined,
-                                            last = undefined}}]} =
-               recv_iq(Config);
-       true ->
-           #message{
-              sub_els = [#mam_fin{
-                            complete = false,
-                            rsm = #rsm_set{count = 10,
-                                           first = undefined,
-                                           last = undefined}}]} =
-               recv_message(Config)
-    end,
-    %% Should receive 2 last messages
-    I5 = send(Config,
-             #iq{type = Type,
-                 sub_els = [#mam_query{xmlns = NS,
-                                       rsm = #rsm_set{max = 2,
-                                                      before = <<"">>}}]}),
-    maybe_recv_iq_result(Config, NS, I5),
-    lists:foreach(
-      fun(N) ->
-             Text = #text{data = integer_to_binary(N)},
-             #message{to = MyJID,
-                      sub_els =
-                          [#mam_result{
-                              xmlns = NS,
-                              sub_els =
-                                  [#forwarded{
-                                      delay = #delay{},
-                                      sub_els =
-                                          [#message{
-                                              from = MyJID, to = Peer,
-                                              body = [Text]}]}]}]} =
-                 recv_message(Config)
-      end, lists:seq(4, 5)),
-    if NS == ?NS_MAM_TMP ->
-           #iq{type = result, id = I5,
-               sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{count = 5}}]} =
-               recv_iq(Config);
-       true ->
-           #message{
-              sub_els = [#mam_fin{complete = false,
-                                  rsm = #rsm_set{count = 10}}]} =
-               recv_message(Config)
-    end.
-
-client_state_master(Config) ->
-    true = ?config(csi, Config),
-    Peer = ?config(slave, Config),
-    Presence = #presence{to = Peer},
-    ChatState = #message{to = Peer, thread = <<"1">>,
-                        sub_els = [#chatstate{type = active}]},
-    Message = ChatState#message{body = [#text{data = <<"body">>}]},
-    PepPayload = xmpp:encode(#presence{}),
-    PepOne = #message{
-               to = Peer,
-               sub_els =
-                   [#ps_event{
-                       items =
-                           #ps_items{
-                              node = <<"foo-1">>,
-                              items =
-                                  [#ps_item{
-                                      id = <<"pep-1">>,
-                                      xml_els = [PepPayload]}]}}]},
-    PepTwo = #message{
-               to = Peer,
-               sub_els =
-                   [#ps_event{
-                       items =
-                           #ps_items{
-                              node = <<"foo-2">>,
-                              items =
-                                  [#ps_item{
-                                      id = <<"pep-2">>,
-                                      xml_els = [PepPayload]}]}}]},
-    %% Wait for the slave to become inactive.
-    wait_for_slave(Config),
-    %% Should be queued (but see below):
-    send(Config, Presence),
-    %% Should replace the previous presence in the queue:
-    send(Config, Presence#presence{type = unavailable}),
-    %% The following two PEP stanzas should be queued (but see below):
-    send(Config, PepOne),
-    send(Config, PepTwo),
-    %% The following two PEP stanzas should replace the previous two:
-    send(Config, PepOne),
-    send(Config, PepTwo),
-    %% Should be queued (but see below):
-    send(Config, ChatState),
-    %% Should replace the previous chat state in the queue:
-    send(Config, ChatState#message{sub_els = [#chatstate{type = composing}]}),
-    %% Should be sent immediately, together with the queued stanzas:
-    send(Config, Message),
-    %% Wait for the slave to become active.
-    wait_for_slave(Config),
-    %% Should be delivered, as the client is active again:
-    send(Config, ChatState),
-    disconnect(Config).
-
-client_state_slave(Config) ->
-    Peer = ?config(master, Config),
-    change_client_state(Config, inactive),
-    wait_for_master(Config),
-    #presence{from = Peer, type = unavailable, sub_els = [#delay{}]} =
-       recv_presence(Config),
-    #message{
-       from = Peer,
-       sub_els =
-          [#ps_event{
-              items =
-                  #ps_items{
-                     node = <<"foo-1">>,
-                     items =
-                         [#ps_item{
-                             id = <<"pep-1">>}]}},
-           #delay{}]} = recv_message(Config),
-    #message{
-       from = Peer,
-       sub_els =
-          [#ps_event{
-              items =
-                  #ps_items{
-                     node = <<"foo-2">>,
-                     items =
-                         [#ps_item{
-                             id = <<"pep-2">>}]}},
-           #delay{}]} = recv_message(Config),
-    #message{from = Peer, thread = <<"1">>,
-            sub_els = [#chatstate{type = composing},
-                       #delay{}]} = recv_message(Config),
-    #message{from = Peer, thread = <<"1">>,
-            body = [#text{data = <<"body">>}],
-            sub_els = [#chatstate{type = active}]} = recv_message(Config),
-    change_client_state(Config, active),
-    wait_for_master(Config),
-    #message{from = Peer, thread = <<"1">>,
-            sub_els = [#chatstate{type = active}]} = recv_message(Config),
-    disconnect(Config).
-
 %%%===================================================================
 %%% Aux functions
 %%%===================================================================
-change_client_state(Config, NewState) ->
-    send(Config, #csi{type = NewState}),
-    send_recv(Config, #iq{type = get, to = server_jid(Config),
-                         sub_els = [#ping{}]}).
-
 bookmark_conference() ->
     #bookmark_conference{name = <<"Some name">>,
                          autojoin = true,
@@ -2902,34 +979,22 @@ bookmark_conference() ->
                                  <<"some.conference.org">>,
                                  <<>>)}.
 
-socks5_connect(#streamhost{host = Host, port = Port},
-               {SID, JID1, JID2}) ->
-    Hash = p1_sha:sha([SID, jid:to_string(JID1), jid:to_string(JID2)]),
-    {ok, Sock} = gen_tcp:connect(binary_to_list(Host), Port,
-                                 [binary, {active, false}]),
-    Init = <<?VERSION_5, 1, ?AUTH_ANONYMOUS>>,
-    InitAck = <<?VERSION_5, ?AUTH_ANONYMOUS>>,
-    Req = <<?VERSION_5, ?CMD_CONNECT, 0,
-            ?ATYP_DOMAINNAME, 40, Hash:40/binary, 0, 0>>,
-    Resp = <<?VERSION_5, ?SUCCESS, 0, ?ATYP_DOMAINNAME,
-             40, Hash:40/binary, 0, 0>>,
-    gen_tcp:send(Sock, Init),
-    {ok, InitAck} = gen_tcp:recv(Sock, size(InitAck)),
-    gen_tcp:send(Sock, Req),
-    {ok, Resp} = gen_tcp:recv(Sock, size(Resp)),
-    Sock.
-
-socks5_send(Sock, Data) ->
-    ok = gen_tcp:send(Sock, Data).
-
-socks5_recv(Sock, Data) ->
-    {ok, Data} = gen_tcp:recv(Sock, size(Data)).
-
-set_opts(Config, Options) ->
-    lists:foldl(
-      fun({Opt, Val}, Acc) ->
-             lists:keystore(Opt, 1, Acc, {Opt, Val})
-      end, Config, Options).
+'$handle_undefined_function'(F, [Config]) when is_list(Config) ->
+    case re:split(atom_to_list(F), "_", [{return, list}, {parts, 2}]) of
+       [M, T] ->
+           Module = list_to_atom(M ++ "_tests"),
+           Function = list_to_atom(T),
+           case erlang:function_exported(Module, Function, 1) of
+               true ->
+                   Module:Function(Config);
+               false ->
+                   erlang:error({undef, F})
+           end;
+       _ ->
+           erlang:error({undef, F})
+    end;
+'$handle_undefined_function'(_, _) ->
+    erlang:error(undef).
 
 %%%===================================================================
 %%% SQL stuff
@@ -3011,12 +1076,12 @@ split(Data) ->
 clear_riak_tables(Config) ->
     User = ?config(user, Config),
     Server = ?config(server, Config),
-    Room = muc_room_jid(Config),
-    {URoom, SRoom, _} = jid:tolower(Room),
+    Master = <<"test_master!#$%^*()`~+-;_=[]{}|\\">>,
+    Slave = <<"test_slave!#$%^*()`~+-;_=[]{}|\\">>,
     ejabberd_auth:remove_user(User, Server),
-    ejabberd_auth:remove_user(<<"test_slave">>, Server),
-    ejabberd_auth:remove_user(<<"test_master">>, Server),
-    mod_muc:forget_room(Server, URoom, SRoom),
-    ejabberd_riak:delete(muc_registered, {{<<"test_slave">>, Server}, SRoom}),
-    ejabberd_riak:delete(muc_registered, {{<<"test_master">>, Server}, SRoom}),
+    ejabberd_auth:remove_user(Master, Server),
+    ejabberd_auth:remove_user(Slave, Server),
+    ejabberd_riak:delete(muc_room),
+    ejabberd_riak:delete(muc_registered),
+    timer:sleep(timer:seconds(5)),
     Config.
diff --git a/test/example_tests.erl b/test/example_tests.erl
new file mode 100644 (file)
index 0000000..d796537
--- /dev/null
@@ -0,0 +1,52 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 16 Nov 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(example_tests).
+
+%% API
+-compile(export_all).
+-import(suite, []).
+
+-include("suite.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+%%%===================================================================
+%%% Single user tests
+%%%===================================================================
+single_cases() ->
+    {example_single, [sequence],
+     [single_test(foo)]}.
+
+foo(Config) ->
+    Config.
+
+%%%===================================================================
+%%% Master-slave tests
+%%%===================================================================
+master_slave_cases() ->
+    {example_master_slave, [sequence],
+     [master_slave_test(foo)]}.
+
+foo_master(Config) ->
+    Config.
+
+foo_slave(Config) ->
+    Config.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+single_test(T) ->
+    list_to_atom("example_" ++ atom_to_list(T)).
+
+master_slave_test(T) ->
+    {list_to_atom("example_" ++ atom_to_list(T)), [parallel],
+     [list_to_atom("example_" ++ atom_to_list(T) ++ "_master"),
+      list_to_atom("example_" ++ atom_to_list(T) ++ "_slave")]}.
diff --git a/test/mam_tests.erl b/test/mam_tests.erl
new file mode 100644 (file)
index 0000000..9154ea2
--- /dev/null
@@ -0,0 +1,537 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Nov 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mam_tests).
+
+%% API
+-compile(export_all).
+-import(suite, [get_features/1, disconnect/1, my_jid/1, send_recv/2,
+               wait_for_slave/1, server_jid/1, send/2, get_features/2,
+               wait_for_master/1, recv_message/1, recv_iq/1, muc_room_jid/1,
+               muc_jid/1, is_feature_advertised/3, get_event/1, put_event/2]).
+
+-include("suite.hrl").
+-define(VERSIONS, [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1]).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+%%%===================================================================
+%%% Single user tests
+%%%===================================================================
+single_cases() ->
+    {mam_single, [sequence],
+     [single_test(feature_enabled),
+      single_test(get_set_prefs),
+      single_test(get_form),
+      single_test(fake_by)]}.
+
+feature_enabled(Config) ->
+    BareMyJID = jid:remove_resource(my_jid(Config)),
+    RequiredFeatures = sets:from_list(?VERSIONS),
+    ServerFeatures = sets:from_list(get_features(Config)),
+    UserFeatures = sets:from_list(get_features(Config, BareMyJID)),
+    MUCFeatures = get_features(Config, muc_jid(Config)),
+    ct:comment("Checking if all MAM server features are enabled"),
+    true = sets:is_subset(RequiredFeatures, ServerFeatures),
+    ct:comment("Checking if all MAM user features are enabled"),
+    true = sets:is_subset(RequiredFeatures, UserFeatures),
+    ct:comment("Checking if all MAM conference service features are enabled"),
+    true = lists:member(?NS_MAM_1, MUCFeatures),
+    clean(disconnect(Config)).
+
+fake_by(Config) ->
+    BareServerJID = server_jid(Config),
+    FullServerJID = jid:replace_resource(BareServerJID, randoms:get_string()),
+    FullMyJID = my_jid(Config),
+    BareMyJID = jid:remove_resource(FullMyJID),
+    Fakes = lists:flatmap(
+             fun(JID) ->
+                     [#mam_archived{id = randoms:get_string(), by = JID},
+                      #stanza_id{id = randoms:get_string(), by = JID}]
+             end, [BareServerJID, FullServerJID, BareMyJID, FullMyJID]),
+    Body = xmpp:mk_text(<<"body">>),
+    ForeignJID = jid:make(randoms:get_string()),
+    Archived = #mam_archived{id = randoms:get_string(), by = ForeignJID},
+    StanzaID = #stanza_id{id = randoms:get_string(), by = ForeignJID},
+    #message{body = Body, sub_els = SubEls} =
+       send_recv(Config, #message{to = FullMyJID,
+                                  body = Body,
+                                  sub_els = [Archived, StanzaID|Fakes]}),
+    ct:comment("Checking if only foreign tags present"),
+    [ForeignJID, ForeignJID] = lists:flatmap(
+                                fun(#mam_archived{by = By}) -> [By];
+                                   (#stanza_id{by = By}) -> [By];
+                                   (_) -> []
+                                end, SubEls),
+    clean(disconnect(Config)).
+
+get_set_prefs(Config) ->
+    Range = [{JID, #mam_prefs{xmlns = NS,
+                             default = Default,
+                             always = Always,
+                             never = Never}} ||
+               JID <- [undefined, server_jid(Config)],
+               NS <- ?VERSIONS,
+               Default <- [always, never, roster],
+               Always <- [[], [jid:from_string(<<"foo@bar.baz">>)]],
+               Never <- [[], [jid:from_string(<<"baz@bar.foo">>)]]],
+    lists:foreach(
+      fun({To, Prefs}) ->
+             NS = Prefs#mam_prefs.xmlns,
+             #iq{type = result, sub_els = [Prefs]} =
+                 send_recv(Config, #iq{type = set, to = To,
+                                       sub_els = [Prefs]}),
+             #iq{type = result, sub_els = [Prefs]} =
+                 send_recv(Config, #iq{type = get, to = To,
+                                       sub_els = [#mam_prefs{xmlns = NS}]})
+      end, Range),
+    clean(disconnect(Config)).
+
+get_form(Config) ->
+    ServerJID = server_jid(Config),
+    Range = [{JID, NS} || JID <- [undefined, ServerJID],
+                         NS <- ?VERSIONS -- [?NS_MAM_TMP]],
+    lists:foreach(
+      fun({To, NS}) ->
+             #iq{type = result,
+                 sub_els = [#mam_query{xmlns = NS,
+                                       xdata = #xdata{} = X}]} =
+                 send_recv(Config, #iq{type = get, to = To,
+                                       sub_els = [#mam_query{xmlns = NS}]}),
+             [NS] = xmpp_util:get_xdata_values(<<"FORM_TYPE">>, X),
+             true = xmpp_util:has_xdata_var(<<"with">>, X),
+             true = xmpp_util:has_xdata_var(<<"start">>, X),
+             true = xmpp_util:has_xdata_var(<<"end">>, X)
+      end, Range),
+    clean(disconnect(Config)).
+
+%%%===================================================================
+%%% Master-slave tests
+%%%===================================================================
+master_slave_cases() ->
+    {mam_master_slave, [sequence],
+     [master_slave_test(archived_and_stanza_id),
+      master_slave_test(query_all),
+      master_slave_test(query_with),
+      master_slave_test(query_rsm_max),
+      master_slave_test(query_rsm_after),
+      master_slave_test(query_rsm_before),
+      master_slave_test(muc)]}.
+
+archived_and_stanza_id_master(Config) ->
+    #presence{} = send_recv(Config, #presence{}),
+    wait_for_slave(Config),
+    send_messages(Config, lists:seq(1, 5)),
+    clean(disconnect(Config)).
+
+archived_and_stanza_id_slave(Config) ->
+    ok = set_default(Config, always),
+    #presence{} = send_recv(Config, #presence{}),
+    wait_for_master(Config),
+    recv_messages(Config, lists:seq(1, 5)),
+    clean(disconnect(Config)).
+
+query_all_master(Config) ->
+    Peer = ?config(peer, Config),
+    MyJID = my_jid(Config),
+    ok = set_default(Config, always),
+    #presence{} = send_recv(Config, #presence{}),
+    wait_for_slave(Config),
+    send_messages(Config, lists:seq(1, 5)),
+    query_all(Config, MyJID, Peer),
+    clean(disconnect(Config)).
+
+query_all_slave(Config) ->
+    Peer = ?config(peer, Config),
+    MyJID = my_jid(Config),
+    ok = set_default(Config, always),
+    #presence{} = send_recv(Config, #presence{}),
+    wait_for_master(Config),
+    recv_messages(Config, lists:seq(1, 5)),
+    query_all(Config, Peer, MyJID),
+    clean(disconnect(Config)).
+
+query_with_master(Config) ->
+    Peer = ?config(peer, Config),
+    MyJID = my_jid(Config),
+    ok = set_default(Config, always),
+    #presence{} = send_recv(Config, #presence{}),
+    wait_for_slave(Config),
+    send_messages(Config, lists:seq(1, 5)),
+    query_with(Config, MyJID, Peer),
+    clean(disconnect(Config)).
+
+query_with_slave(Config) ->
+    Peer = ?config(peer, Config),
+    MyJID = my_jid(Config),
+    ok = set_default(Config, always),
+    #presence{} = send_recv(Config, #presence{}),
+    wait_for_master(Config),
+    recv_messages(Config, lists:seq(1, 5)),
+    query_with(Config, Peer, MyJID),
+    clean(disconnect(Config)).
+
+query_rsm_max_master(Config) ->
+    Peer = ?config(peer, Config),
+    MyJID = my_jid(Config),
+    ok = set_default(Config, always),
+    #presence{} = send_recv(Config, #presence{}),
+    wait_for_slave(Config),
+    send_messages(Config, lists:seq(1, 5)),
+    query_rsm_max(Config, MyJID, Peer),
+    clean(disconnect(Config)).
+
+query_rsm_max_slave(Config) ->
+    Peer = ?config(peer, Config),
+    MyJID = my_jid(Config),
+    ok = set_default(Config, always),
+    #presence{} = send_recv(Config, #presence{}),
+    wait_for_master(Config),
+    recv_messages(Config, lists:seq(1, 5)),
+    query_rsm_max(Config, Peer, MyJID),
+    clean(disconnect(Config)).
+
+query_rsm_after_master(Config) ->
+    Peer = ?config(peer, Config),
+    MyJID = my_jid(Config),
+    ok = set_default(Config, always),
+    #presence{} = send_recv(Config, #presence{}),
+    wait_for_slave(Config),
+    send_messages(Config, lists:seq(1, 5)),
+    query_rsm_after(Config, MyJID, Peer),
+    clean(disconnect(Config)).
+
+query_rsm_after_slave(Config) ->
+    Peer = ?config(peer, Config),
+    MyJID = my_jid(Config),
+    ok = set_default(Config, always),
+    #presence{} = send_recv(Config, #presence{}),
+    wait_for_master(Config),
+    recv_messages(Config, lists:seq(1, 5)),
+    query_rsm_after(Config, Peer, MyJID),
+    clean(disconnect(Config)).
+
+query_rsm_before_master(Config) ->
+    Peer = ?config(peer, Config),
+    MyJID = my_jid(Config),
+    ok = set_default(Config, always),
+    #presence{} = send_recv(Config, #presence{}),
+    wait_for_slave(Config),
+    send_messages(Config, lists:seq(1, 5)),
+    query_rsm_before(Config, MyJID, Peer),
+    clean(disconnect(Config)).
+
+query_rsm_before_slave(Config) ->
+    Peer = ?config(peer, Config),
+    MyJID = my_jid(Config),
+    ok = set_default(Config, always),
+    #presence{} = send_recv(Config, #presence{}),
+    wait_for_master(Config),
+    recv_messages(Config, lists:seq(1, 5)),
+    query_rsm_before(Config, Peer, MyJID),
+    clean(disconnect(Config)).
+
+muc_master(Config) ->
+    Room = muc_room_jid(Config),
+    %% Joining
+    ok = muc_tests:join_new(Config),
+    %% MAM feature should not be advertised at this point,
+    %% because MAM is not enabled so far
+    false = is_feature_advertised(Config, ?NS_MAM_1, Room),
+    %% Fill in some history
+    send_messages_to_room(Config, lists:seq(1, 21)),
+    %% We now should be able to retrieve those via MAM, even though
+    %% MAM is disabled. However, only last 20 messages should be received.
+    recv_messages_from_room(Config, lists:seq(2, 21)),
+    %% Now enable MAM for the conference
+    %% Retrieve config first
+    #iq{type = result, sub_els = [#muc_owner{config = #xdata{} = RoomCfg}]} =
+        send_recv(Config, #iq{type = get, sub_els = [#muc_owner{}],
+                              to = Room}),
+    %% Find the MAM field in the config and enable it
+    NewFields = lists:flatmap(
+                 fun(#xdata_field{var = <<"mam">> = Var}) ->
+                         [#xdata_field{var = Var, values = [<<"1">>]}];
+                    (_) ->
+                         []
+                 end, RoomCfg#xdata.fields),
+    NewRoomCfg = #xdata{type = submit, fields = NewFields},
+    #iq{type = result, sub_els = []} =
+       send_recv(Config, #iq{type = set, to = Room,
+                             sub_els = [#muc_owner{config = NewRoomCfg}]}),
+    #message{from = Room, type = groupchat,
+            sub_els = [#muc_user{status_codes = [104]}]} = recv_message(Config),
+    %% Check if MAM has been enabled
+    true = is_feature_advertised(Config, ?NS_MAM_1, Room),
+    %% We now sending some messages again
+    send_messages_to_room(Config, lists:seq(1, 5)),
+    %% And retrieve them via MAM again.
+    recv_messages_from_room(Config, lists:seq(1, 5)),
+    put_event(Config, disconnect),
+    clean(disconnect(Config)).
+
+muc_slave(Config) ->
+    disconnect = get_event(Config),
+    clean(disconnect(Config)).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+single_test(T) ->
+    list_to_atom("mam_" ++ atom_to_list(T)).
+
+master_slave_test(T) ->
+    {list_to_atom("mam_" ++ atom_to_list(T)), [parallel],
+     [list_to_atom("mam_" ++ atom_to_list(T) ++ "_master"),
+      list_to_atom("mam_" ++ atom_to_list(T) ++ "_slave")]}.
+
+clean(Config) ->
+    {U, S, _} = jid:tolower(my_jid(Config)),
+    mod_mam:remove_user(U, S),
+    Config.
+
+set_default(Config, Default) ->
+    lists:foreach(
+      fun(NS) ->
+             ct:comment("Setting default preferences of '~s' to '~s'",
+                        [NS, Default]),
+             #iq{type = result,
+                 sub_els = [#mam_prefs{xmlns = NS, default = Default}]} =
+                 send_recv(Config, #iq{type = set,
+                                       sub_els = [#mam_prefs{xmlns = NS,
+                                                             default = Default}]})
+      end, ?VERSIONS).
+
+send_messages(Config, Range) ->
+    Peer = ?config(peer, Config),
+    lists:foreach(
+      fun(N) ->
+             Body = xmpp:mk_text(integer_to_binary(N)),
+              send(Config, #message{to = Peer, body = Body})
+      end, Range).
+
+recv_messages(Config, Range) ->
+    Peer = ?config(peer, Config),
+    lists:foreach(
+      fun(N) ->
+             Body = xmpp:mk_text(integer_to_binary(N)),
+             #message{from = Peer, body = Body} = Msg =
+                 recv_message(Config),
+             #mam_archived{by = BareMyJID} =
+                 xmpp:get_subtag(Msg, #mam_archived{}),
+             #stanza_id{by = BareMyJID} =
+                 xmpp:get_subtag(Msg, #stanza_id{})
+      end, Range).
+
+recv_archived_messages(Config, From, To, QID, Range) ->
+    MyJID = my_jid(Config),
+    lists:foreach(
+      fun(N) ->
+             ct:comment("Retreiving ~pth message in range ~p",
+                        [N, Range]),
+              Body = xmpp:mk_text(integer_to_binary(N)),
+              #message{to = MyJID,
+                       sub_els =
+                           [#mam_result{
+                               queryid = QID,
+                               sub_els =
+                                   [#forwarded{
+                                       delay = #delay{},
+                                       xml_els = [El]}]}]} = recv_message(Config),
+             #message{from = From, to = To,
+                      body = Body} = xmpp:decode(El)
+      end, Range).
+
+maybe_recv_iq_result(Config, ?NS_MAM_0, I) ->
+    #iq{type = result, id = I} = recv_iq(Config);
+maybe_recv_iq_result(_, _, _) ->
+    ok.
+
+query_iq_type(?NS_MAM_TMP) -> get;
+query_iq_type(_) -> set.
+
+send_query(Config, #mam_query{xmlns = NS} = Query) ->
+    Type = query_iq_type(NS),
+    I = send(Config, #iq{type = Type, sub_els = [Query]}),
+    maybe_recv_iq_result(Config, NS, I),
+    I.
+
+recv_fin(Config, I, QueryID, ?NS_MAM_1 = NS, IsComplete) ->
+    ct:comment("Receiving fin iq for namespace '~s'", [NS]),
+    #iq{type = result, id = I,
+       sub_els = [#mam_fin{xmlns = NS,
+                           id = QueryID,
+                           complete = Complete,
+                           rsm = RSM}]} = recv_iq(Config),
+    ct:comment("Checking if complete is ~s", [IsComplete]),
+    Complete = IsComplete,
+    RSM;
+recv_fin(Config, I, QueryID, ?NS_MAM_TMP = NS, _IsComplete) ->
+    ct:comment("Receiving fin iq for namespace '~s'", [NS]),
+    #iq{type = result, id = I,
+       sub_els = [#mam_query{xmlns = NS,
+                             rsm = RSM,
+                             id = QueryID}]} = recv_iq(Config),
+    RSM;
+recv_fin(Config, _, QueryID, ?NS_MAM_0 = NS, IsComplete) ->
+    ct:comment("Receiving fin message for namespace '~s'", [NS]),
+    #message{} = FinMsg = recv_message(Config),
+    #mam_fin{xmlns = NS,
+            id = QueryID,
+            complete = Complete,
+            rsm = RSM} = xmpp:get_subtag(FinMsg, #mam_fin{xmlns = NS}),
+    ct:comment("Checking if complete is ~s", [IsComplete]),
+    Complete = IsComplete,
+    RSM.
+
+send_messages_to_room(Config, Range) ->
+    MyNick = ?config(master_nick, Config),
+    Room = muc_room_jid(Config),
+    MyNickJID = jid:replace_resource(Room, MyNick),
+    lists:foreach(
+      fun(N) ->
+              Body = xmpp:mk_text(integer_to_binary(N)),
+             #message{from = MyNickJID,
+                      type = groupchat,
+                      body = Body} =
+                 send_recv(Config, #message{to = Room, body = Body,
+                                            type = groupchat})
+      end, Range).
+
+recv_messages_from_room(Config, Range) ->
+    MyNick = ?config(master_nick, Config),
+    Room = muc_room_jid(Config),
+    MyNickJID = jid:replace_resource(Room, MyNick),
+    MyJID = my_jid(Config),
+    QID = randoms:get_string(),
+    Count = length(Range),
+    I = send(Config, #iq{type = set, to = Room,
+                        sub_els = [#mam_query{xmlns = ?NS_MAM_1, id = QID}]}),
+    lists:foreach(
+      fun(N) ->
+             Body = xmpp:mk_text(integer_to_binary(N)),
+             #message{
+                to = MyJID, from = Room,
+                sub_els =
+                    [#mam_result{
+                        xmlns = ?NS_MAM_1,
+                        queryid = QID,
+                        sub_els =
+                            [#forwarded{
+                                delay = #delay{},
+                                xml_els = [El]}]}]} = recv_message(Config),
+             #message{from = MyNickJID,
+                      type = groupchat,
+                      body = Body} = xmpp:decode(El)
+      end, Range),
+    #iq{from = Room, id = I, type = result,
+       sub_els = [#mam_fin{xmlns = ?NS_MAM_1,
+                           id = QID,
+                           rsm = #rsm_set{count = Count},
+                           complete = true}]} = recv_iq(Config).
+
+query_all(Config, From, To) ->
+    lists:foreach(
+      fun(NS) ->
+             query_all(Config, From, To, NS)
+      end, ?VERSIONS).
+
+query_all(Config, From, To, NS) ->
+    QID = randoms:get_string(),
+    Range = lists:seq(1, 5),
+    ID = send_query(Config, #mam_query{xmlns = NS, id = QID}),
+    recv_archived_messages(Config, From, To, QID, Range),
+    #rsm_set{count = 5} = recv_fin(Config, ID, QID, NS, _Complete = true).
+
+query_with(Config, From, To) ->
+    lists:foreach(
+      fun(NS) ->
+             query_with(Config, From, To, NS)
+      end, ?VERSIONS).
+
+query_with(Config, From, To, NS) ->
+    Peer = ?config(peer, Config),
+    BarePeer = jid:remove_resource(Peer),
+    QID = randoms:get_string(),
+    Range = lists:seq(1, 5),
+    lists:foreach(
+      fun(JID) ->
+             ct:comment("Sending query with jid ~s", [jid:to_string(JID)]),
+             Query = if NS == ?NS_MAM_TMP ->
+                             #mam_query{xmlns = NS, with = JID, id = QID};
+                        true ->
+                             Fs = mam_query:encode([{with, JID}]),
+                             #mam_query{xmlns = NS, id = QID,
+                                        xdata = #xdata{type = submit,
+                                                       fields = Fs}}
+                     end,
+             ID = send_query(Config, Query),
+             recv_archived_messages(Config, From, To, QID, Range),
+             #rsm_set{count = 5} = recv_fin(Config, ID, QID, NS, true)
+      end, [Peer, BarePeer]).
+
+query_rsm_max(Config, From, To) ->
+    lists:foreach(
+      fun(NS) ->
+             query_rsm_max(Config, From, To, NS)
+      end, ?VERSIONS).
+
+query_rsm_max(Config, From, To, NS) ->
+    lists:foreach(
+      fun(Max) ->
+             QID = randoms:get_string(),
+             Range = lists:sublist(lists:seq(1, Max), 5),
+             Query = #mam_query{xmlns = NS, id = QID, rsm = #rsm_set{max = Max}},
+             ID = send_query(Config, Query),
+             recv_archived_messages(Config, From, To, QID, Range),
+             IsComplete = Max >= 5,
+             #rsm_set{count = 5} = recv_fin(Config, ID, QID, NS, IsComplete)
+      end, lists:seq(0, 6)).
+
+query_rsm_after(Config, From, To) ->
+    lists:foreach(
+      fun(NS) ->
+             query_rsm_after(Config, From, To, NS)
+      end, ?VERSIONS).
+
+query_rsm_after(Config, From, To, NS) ->
+    lists:foldl(
+      fun(Range, #rsm_first{data = After}) ->
+             ct:comment("Retrieving ~p messages after '~s'",
+                        [length(Range), After]),
+             QID = randoms:get_string(),
+             Query = #mam_query{xmlns = NS, id = QID,
+                                rsm = #rsm_set{'after' = After}},
+             ID = send_query(Config, Query),
+             recv_archived_messages(Config, From, To, QID, Range),
+             #rsm_set{count = 5, first = First} =
+                 recv_fin(Config, ID, QID, NS, true),
+             First
+      end, #rsm_first{}, [lists:seq(N, 5) || N <- lists:seq(1, 6)]).
+
+query_rsm_before(Config, From, To) ->
+    lists:foreach(
+      fun(NS) ->
+             query_rsm_before(Config, From, To, NS)
+      end, ?VERSIONS).
+
+query_rsm_before(Config, From, To, NS) ->
+    lists:foldl(
+      fun(Range, Before) ->
+             ct:comment("Retrieving ~p messages before '~s'",
+                        [length(Range), Before]),
+             QID = randoms:get_string(),
+             Query = #mam_query{xmlns = NS, id = QID,
+                                rsm = #rsm_set{before = Before}},
+             ID = send_query(Config, Query),
+             recv_archived_messages(Config, From, To, QID, Range),
+             #rsm_set{count = 5, last = Last} =
+                 recv_fin(Config, ID, QID, NS, true),
+             Last
+      end, <<"">>, lists:reverse([lists:seq(1, N) || N <- lists:seq(0, 5)])).
diff --git a/test/mix_tests.erl b/test/mix_tests.erl
new file mode 100644 (file)
index 0000000..56b1b35
--- /dev/null
@@ -0,0 +1,139 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 16 Nov 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(mix_tests).
+
+%% API
+-compile(export_all).
+-import(suite, [mix_jid/1, mix_room_jid/1, my_jid/1, is_feature_advertised/3,
+               disconnect/1, send_recv/2, recv_message/1, send/2,
+               put_event/2, get_event/1]).
+-include("suite.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+%%%===================================================================
+%%% Single user tests
+%%%===================================================================
+single_cases() ->
+    {mix_single, [sequence],
+     [single_test(feature_enabled)]}.
+
+feature_enabled(Config) ->
+    MIX = mix_jid(Config),
+    ct:comment("Checking if ~s is set", [?NS_MIX_0]),
+    true = is_feature_advertised(Config, ?NS_MIX_0, MIX),
+    disconnect(Config).
+
+%%%===================================================================
+%%% Master-slave tests
+%%%===================================================================
+master_slave_cases() ->
+    {mix_master_slave, [sequence],
+     [master_slave_test(all)]}.
+
+all_master(Config) ->
+    MIX = mix_jid(Config),
+    Room = mix_room_jid(Config),
+    MyJID = my_jid(Config),
+    MyBareJID = jid:remove_resource(MyJID),
+    #iq{type = result,
+       sub_els =
+           [#disco_info{
+               identities = [#identity{category = <<"conference">>,
+                                       type = <<"text">>}],
+               xdata = [#xdata{type = result, fields = XFields}]}]} =
+       send_recv(Config, #iq{type = get, to = MIX, sub_els = [#disco_info{}]}),
+    true = lists:any(
+            fun(#xdata_field{var = <<"FORM_TYPE">>,
+                             values = [?NS_MIX_SERVICEINFO_0]}) -> true;
+               (_) -> false
+            end, XFields),
+    %% Joining
+    Nodes = [?NS_MIX_NODES_MESSAGES, ?NS_MIX_NODES_PRESENCE,
+            ?NS_MIX_NODES_PARTICIPANTS, ?NS_MIX_NODES_SUBJECT,
+            ?NS_MIX_NODES_CONFIG],
+    #iq{type = result,
+       sub_els = [#mix_join{subscribe = Nodes, jid = MyBareJID}]} =
+       send_recv(Config, #iq{type = set, to = Room,
+                             sub_els = [#mix_join{subscribe = Nodes}]}),
+    #message{from = Room,
+            sub_els =
+                [#ps_event{
+                    items = #ps_items{
+                               node = ?NS_MIX_NODES_PARTICIPANTS,
+                               items = [#ps_item{
+                                           id = ParticipantID,
+                                           xml_els = [PXML]}]}}]} =
+       recv_message(Config),
+    #mix_participant{jid = MyBareJID} = xmpp:decode(PXML),
+    %% Coming online
+    PresenceID = randoms:get_string(),
+    Presence = xmpp:encode(#presence{}),
+    #iq{type = result,
+       sub_els =
+           [#pubsub{
+               publish = #ps_publish{
+                            node = ?NS_MIX_NODES_PRESENCE,
+                            items = [#ps_item{id = PresenceID}]}}]} =
+       send_recv(
+         Config,
+         #iq{type = set, to = Room,
+             sub_els =
+                 [#pubsub{
+                     publish = #ps_publish{
+                                  node = ?NS_MIX_NODES_PRESENCE,
+                                  items = [#ps_item{
+                                              id = PresenceID,
+                                              xml_els = [Presence]}]}}]}),
+    #message{from = Room,
+            sub_els =
+                [#ps_event{
+                    items = #ps_items{
+                               node = ?NS_MIX_NODES_PRESENCE,
+                               items = [#ps_item{
+                                           id = PresenceID,
+                                           xml_els = [Presence]}]}}]} =
+       recv_message(Config),
+    %% Coming offline
+    send(Config, #presence{type = unavailable, to = Room}),
+    %% Receiving presence retract event
+    #message{from = Room,
+            sub_els = [#ps_event{
+                          items = #ps_items{
+                                     node = ?NS_MIX_NODES_PRESENCE,
+                                     retract = PresenceID}}]} =
+       recv_message(Config),
+    %% Leaving
+    #iq{type = result, sub_els = []} =
+       send_recv(Config, #iq{type = set, to = Room, sub_els = [#mix_leave{}]}),
+    #message{from = Room,
+            sub_els =
+                [#ps_event{
+                    items = #ps_items{
+                               node = ?NS_MIX_NODES_PARTICIPANTS,
+                               retract = ParticipantID}}]} =
+       recv_message(Config),
+    put_event(Config, disconnect),
+    disconnect(Config).
+
+all_slave(Config) ->
+    disconnect = get_event(Config),
+    disconnect(Config).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+single_test(T) ->
+    list_to_atom("mix_" ++ atom_to_list(T)).
+
+master_slave_test(T) ->
+    {list_to_atom("mix_" ++ atom_to_list(T)), [parallel],
+     [list_to_atom("mix_" ++ atom_to_list(T) ++ "_master"),
+      list_to_atom("mix_" ++ atom_to_list(T) ++ "_slave")]}.
index 6024acfebe92e12b00f82bd8d0616882eff8c076..dba9775542d63b143f9b709fbc77afc34dc61d7b 100644 (file)
@@ -10,7 +10,7 @@
 -behaviour(gen_mod).
 
 %% API
--export([start/2, stop/1, process_iq/3]).
+-export([start/2, stop/1, mod_opt_type/1, depends/2, process_iq/3]).
 -include("jlib.hrl").
 
 %%%===================================================================
@@ -25,6 +25,12 @@ start(Host, Opts) ->
 stop(Host) ->
     gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?MODULE).
 
+mod_opt_type(_) ->
+    [].
+
+depends(_, _) ->
+    [].
+
 %%%===================================================================
 %%% Internal functions
 %%%===================================================================
index 709a82a22f648975b9cb16946cc7ec2892d76cca..d8e6dd8fbf25c18871ea03affdd7fa75a72fce49 100644 (file)
 %% 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,
+               send/2, recv_message/1, recv_iq/1, muc_jid/1,
                alt_room_jid/1, wait_for_slave/1, wait_for_master/1,
                disconnect/1, put_event/2, get_event/1, peer_muc_jid/1,
-               my_muc_jid/1, get_features/2, flush/1, set_opt/3]).
+               my_muc_jid/1, get_features/2, set_opt/3]).
 -include("suite.hrl").
 -include("jid.hrl").
 
 %%%===================================================================
 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) ->
+     [single_test(service_presence_error),
+      single_test(service_message_error),
+      single_test(service_unknown_ns_iq_error),
+      single_test(service_iq_set_error),
+      single_test(service_improper_iq_error),
+      single_test(service_features),
+      single_test(service_disco_info_node_error),
+      single_test(service_disco_items),
+      single_test(service_unique),
+      single_test(service_vcard),
+      single_test(configure_non_existent),
+      single_test(cancel_configure_non_existent),
+      single_test(service_subscriptions)]}.
+
+service_presence_error(Config) ->
     Service = muc_jid(Config),
     ServiceResource = jid:replace_resource(Service, randoms:get_string()),
     lists:foreach(
@@ -56,7 +56,7 @@ muc_service_presence_error(Config) ->
       end, [Service, ServiceResource]),
     disconnect(Config).
 
-muc_service_message_error(Config) ->
+service_message_error(Config) ->
     Service = muc_jid(Config),
     send(Config, #message{type = error, to = Service}),
     lists:foreach(
@@ -75,7 +75,7 @@ muc_service_message_error(Config) ->
       end, [chat, normal, headline, groupchat]),
     disconnect(Config).
 
-muc_service_unknown_ns_iq_error(Config) ->
+service_unknown_ns_iq_error(Config) ->
     Service = muc_jid(Config),
     ServiceResource = jid:replace_resource(Service, randoms:get_string()),
     lists:foreach(
@@ -93,7 +93,7 @@ muc_service_unknown_ns_iq_error(Config) ->
       end, [Service, ServiceResource]),
     disconnect(Config).
 
-muc_service_iq_set_error(Config) ->
+service_iq_set_error(Config) ->
     Service = muc_jid(Config),
     lists:foreach(
       fun(SubEl) ->
@@ -108,7 +108,7 @@ muc_service_iq_set_error(Config) ->
            #muc_unique{}, #muc_subscriptions{}]),
     disconnect(Config).
 
-muc_service_improper_iq_error(Config) ->
+service_improper_iq_error(Config) ->
     Service = muc_jid(Config),
     lists:foreach(
       fun(SubEl) ->
@@ -127,7 +127,7 @@ muc_service_improper_iq_error(Config) ->
            #vcard_email{}, #muc_subscribe{nick = ?config(nick, Config)}]),
     disconnect(Config).
 
-muc_service_features(Config) ->
+service_features(Config) ->
     ServerHost = ?config(server_host, Config),
     MUC = muc_jid(Config),
     Features = sets:from_list(get_features(Config, MUC)),
@@ -144,7 +144,7 @@ muc_service_features(Config) ->
     true = sets:is_subset(RequiredFeatures, Features),
     disconnect(Config).
 
-muc_service_disco_info_node_error(Config) ->
+service_disco_info_node_error(Config) ->
     MUC = muc_jid(Config),
     Node = randoms:get_string(),
     #iq{type = error} = Err =
@@ -153,7 +153,7 @@ muc_service_disco_info_node_error(Config) ->
     #stanza_error{reason = 'item-not-found'} = xmpp:get_error(Err),
     disconnect(Config).
 
-muc_service_disco_items(Config) ->
+service_disco_items(Config) ->
     #jid{server = Service} = muc_jid(Config),
     Rooms = lists:sort(
              lists:map(
@@ -163,25 +163,25 @@ muc_service_disco_items(Config) ->
                end, lists:seq(1, 5))),
     lists:foreach(
       fun(Room) ->
-             ok = muc_join_new(Config, Room)
+             ok = join_new(Config, Room)
       end, Rooms),
-    Items = muc_disco_items(Config),
+    Items = disco_items(Config),
     Rooms = [J || #disco_item{jid = J} <- Items],
     lists:foreach(
       fun(Room) ->
-             ok = muc_leave(Config, Room)
+             ok = leave(Config, Room)
       end, Rooms),
-    [] = muc_disco_items(Config),
+    [] = disco_items(Config),
     disconnect(Config).
 
-muc_service_vcard(Config) ->
+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) ->
+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}]} =
@@ -190,11 +190,11 @@ muc_service_unique(Config) ->
     <<_, _/binary>> = Name,
     disconnect(Config).
 
-muc_configure_non_existent(Config) ->
-    [_|_] = muc_get_config(Config),
+configure_non_existent(Config) ->
+    [_|_] = get_config(Config),
     disconnect(Config).
 
-muc_cancel_configure_non_existent(Config) ->
+cancel_configure_non_existent(Config) ->
     Room = muc_room_jid(Config),
     #iq{type = result, sub_els = []} =
        send_recv(Config,
@@ -202,7 +202,7 @@ muc_cancel_configure_non_existent(Config) ->
                      sub_els = [#muc_owner{config = #xdata{type = cancel}}]}),
     disconnect(Config).
 
-muc_service_subscriptions(Config) ->
+service_subscriptions(Config) ->
     MUC = #jid{server = Service} = muc_jid(Config),
     Rooms = lists:sort(
              lists:map(
@@ -212,9 +212,9 @@ muc_service_subscriptions(Config) ->
                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)
+             ok = join_new(Config, Room),
+             [104] = set_config(Config, [{allow_subscription, true}], Room),
+             [] = subscribe(Config, [], Room)
       end, Rooms),
     #iq{type = result, sub_els = [#muc_subscriptions{list = JIDs}]} =
        send_recv(Config, #iq{type = get, to = MUC,
@@ -222,8 +222,8 @@ muc_service_subscriptions(Config) ->
     Rooms = lists:sort(JIDs),
     lists:foreach(
       fun(Room) ->
-             ok = muc_unsubscribe(Config, Room),
-             ok = muc_leave(Config, Room)
+             ok = unsubscribe(Config, Room),
+             ok = leave(Config, Room)
       end, Rooms),
     disconnect(Config).
 
@@ -232,61 +232,61 @@ muc_service_subscriptions(Config) ->
 %%%===================================================================
 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),
+     [master_slave_test(register),
+      master_slave_test(groupchat_msg),
+      master_slave_test(private_msg),
+      master_slave_test(set_subject),
+      master_slave_test(history),
+      master_slave_test(invite),
+      master_slave_test(invite_members_only),
+      master_slave_test(invite_password_protected),
+      master_slave_test(voice_request),
+      master_slave_test(change_role),
+      master_slave_test(kick),
+      master_slave_test(change_affiliation),
+      master_slave_test(destroy),
+      master_slave_test(vcard),
+      master_slave_test(nick_change),
+      master_slave_test(config_title_desc),
+      master_slave_test(config_public_list),
+      master_slave_test(config_password),
+      master_slave_test(config_whois),
+      master_slave_test(config_members_only),
+      master_slave_test(config_moderated),
+      master_slave_test(config_private_messages),
+      master_slave_test(config_query),
+      master_slave_test(config_allow_invites),
+      master_slave_test(config_visitor_status),
+      master_slave_test(config_allow_voice_requests),
+      master_slave_test(config_voice_request_interval),
+      master_slave_test(config_visitor_nickchange),
+      master_slave_test(join_conflict)]}.
+
+join_conflict_master(Config) ->
+    ok = join_new(Config),
     put_event(Config, join),
     ct:comment("Waiting for 'leave' command from the slave"),
     leave = get_event(Config),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_join_conflict_slave(Config) ->
+join_conflict_slave(Config) ->
     NewConfig = set_opt(nick, ?config(peer_nick, Config), Config),
     ct:comment("Waiting for 'join' command from the master"),
     join = get_event(Config),
     ct:comment("Fail trying to join the room with conflicting nick"),
-    #stanza_error{reason = 'conflict'} = muc_join(NewConfig),
+    #stanza_error{reason = 'conflict'} = join(NewConfig),
     put_event(Config, leave),
     disconnect(NewConfig).
 
-muc_groupchat_msg_master(Config) ->
+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),
+    ok = master_join(Config),
     lists:foreach(
       fun(I) ->
              Body = xmpp:mk_text(integer_to_binary(I)),
@@ -299,29 +299,29 @@ muc_groupchat_msg_master(Config) ->
                                 role = none,
                                 affiliation = none}]} =
        recv_muc_presence(Config, PeerNickJID, unavailable),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_groupchat_msg_slave(Config) ->
+groupchat_msg_slave(Config) ->
     Room = muc_room_jid(Config),
     PeerNick = ?config(master_nick, Config),
     PeerNickJID = jid:replace_resource(Room, PeerNick),
-    {[], _, _} = muc_slave_join(Config),
+    {[], _, _} = 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),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_private_msg_master(Config) ->
+private_msg_master(Config) ->
     Room = muc_room_jid(Config),
     PeerJID = ?config(slave, Config),
     PeerNick = ?config(slave_nick, Config),
     PeerNickJID = jid:replace_resource(Room, PeerNick),
-    ok = muc_master_join(Config),
+    ok = master_join(Config),
     lists:foreach(
       fun(I) ->
              Body = xmpp:mk_text(integer_to_binary(I)),
@@ -336,31 +336,31 @@ muc_private_msg_master(Config) ->
     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),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_private_msg_slave(Config) ->
+private_msg_slave(Config) ->
     Room = muc_room_jid(Config),
     PeerNick = ?config(master_nick, Config),
     PeerNickJID = jid:replace_resource(Room, PeerNick),
-    {[], _, _} = muc_slave_join(Config),
+    {[], _, _} = 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),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_set_subject_master(Config) ->
+set_subject_master(Config) ->
     Room = muc_room_jid(Config),
     PeerJID = ?config(slave, Config),
     PeerNick = ?config(slave_nick, Config),
     PeerNickJID = jid:replace_resource(Room, PeerNick),
     Subject1 = xmpp:mk_text(?config(room_subject, Config)),
     Subject2 = xmpp:mk_text(<<"new-", (?config(room_subject, Config))/binary>>),
-    ok = muc_master_join(Config),
+    ok = master_join(Config),
     ct:comment("Setting 1st subject"),
     send(Config, #message{type = groupchat, to = Room,
                          subject = Subject1}),
@@ -380,30 +380,30 @@ muc_set_subject_master(Config) ->
     #message{type = groupchat, from = PeerNickJID,
             subject = Subject1} = recv_message(Config),
     ct:comment("Disallow subject change"),
-    [104] = muc_set_config(Config, [{changesubject, false}]),
+    [104] = set_config(Config, [{changesubject, false}]),
     ct:comment("Waiting for the slave to leave"),
     #muc_user{items = [#muc_item{jid = PeerJID,
                                 role = none,
                                 affiliation = none}]} =
        recv_muc_presence(Config, PeerNickJID, unavailable),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_set_subject_slave(Config) ->
+set_subject_slave(Config) ->
     Room = muc_room_jid(Config),
     MyNickJID = my_muc_jid(Config),
     PeerNick = ?config(master_nick, Config),
     PeerNickJID = jid:replace_resource(Room, PeerNick),
     Subject1 = xmpp:mk_text(?config(room_subject, Config)),
     Subject2 = xmpp:mk_text(<<"new-", (?config(room_subject, Config))/binary>>),
-    {[], _, _} = muc_slave_join(Config),
+    {[], _, _} = 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),
+    ok = leave(Config),
     ct:comment("Waiting for 'join' command from the master"),
     join = get_event(Config),
-    {[], SubjMsg2, _} = muc_join(Config),
+    {[], SubjMsg2, _} = join(Config),
     ct:comment("Checking if the master has set 2nd subject during our absence"),
     #message{type = groupchat, from = PeerNickJID,
             subject = Subject2} = SubjMsg2,
@@ -412,15 +412,15 @@ muc_set_subject_slave(Config) ->
     #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),
+    [104] = recv_config_change_message(Config),
     ct:comment("Fail trying to change the subject"),
     send(Config, #message{to = Room, type = groupchat, subject = Subject2}),
     #message{from = Room, type = error} = ErrMsg = recv_message(Config),
     #stanza_error{reason = 'forbidden'} = xmpp:get_error(ErrMsg),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_history_master(Config) ->
+history_master(Config) ->
     Room = muc_room_jid(Config),
     ServerHost = ?config(server_host, Config),
     MyNick = ?config(nick, Config),
@@ -429,7 +429,7 @@ muc_history_master(Config) ->
     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),
+    ok = join_new(Config),
     ct:comment("Putting ~p+1 messages in the history", [Size]),
     %% Only Size messages will be stored
     lists:foreach(
@@ -448,10 +448,10 @@ muc_history_master(Config) ->
            available, unavailable,
            available, unavailable,
            available, unavailable]),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_history_slave(Config) ->
+history_slave(Config) ->
     Room = muc_room_jid(Config),
     PeerNick = ?config(peer_nick, Config),
     PeerNickJID = jid:replace_resource(Room, PeerNick),
@@ -461,45 +461,45 @@ muc_history_slave(Config) ->
                                  20),
     ct:comment("Waiting for 'join' command from the master"),
     join = get_event(Config),
-    {History, _, _} = muc_join(Config),
+    {History, _, _} = join(Config),
     ct:comment("Checking ordering of history events"),
     BodyList = [binary_to_integer(xmpp:get_text(Body))
                || #message{type = groupchat, from = From,
                            body = Body} <- History,
                   From == PeerNickJID],
     BodyList = lists:seq(1, Size),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     %% If the client wishes to receive no history, it MUST set the 'maxchars'
     %% attribute to a value of "0" (zero)
     %% (http://xmpp.org/extensions/xep-0045.html#enter-managehistory)
     ct:comment("Checking if maxchars=0 yields to no history"),
-    {[], _, _} = muc_join(Config, #muc{history = #muc_history{maxchars = 0}}),
-    ok = muc_leave(Config),
+    {[], _, _} = join(Config, #muc{history = #muc_history{maxchars = 0}}),
+    ok = leave(Config),
     ct:comment("Receiving only 10 last stanzas"),
-    {History10, _, _} = muc_join(Config,
+    {History10, _, _} = join(Config,
                                 #muc{history = #muc_history{maxstanzas = 10}}),
     BodyList10 = [binary_to_integer(xmpp:get_text(Body))
                  || #message{type = groupchat, from = From,
                              body = Body} <- History10,
                     From == PeerNickJID],
     BodyList10 = lists:nthtail(Size-10, lists:seq(1, Size)),
-    ok = muc_leave(Config),
+    ok = 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,
+    {HistoryWithoutFirst, _, _} = join(Config,
                                           #muc{history = #muc_history{since = TS}}),
     BodyListWithoutFirst = [binary_to_integer(xmpp:get_text(Body))
                            || #message{type = groupchat, from = From,
                                        body = Body} <- HistoryWithoutFirst,
                               From == PeerNickJID],
     BodyListWithoutFirst = lists:nthtail(1, lists:seq(1, Size)),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_invite_master(Config) ->
+invite_master(Config) ->
     Room = muc_room_jid(Config),
     PeerJID = ?config(peer, Config),
-    ok = muc_join_new(Config),
+    ok = join_new(Config),
     wait_for_slave(Config),
     %% Inviting the peer
     send(Config, #message{to = Room, type = normal,
@@ -510,10 +510,10 @@ muc_invite_master(Config) ->
     #message{from = Room} = DeclineMsg = recv_message(Config),
     #muc_user{decline = #muc_decline{from = PeerJID}} =
        xmpp:get_subtag(DeclineMsg, #muc_user{}),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_invite_slave(Config) ->
+invite_slave(Config) ->
     Room = muc_room_jid(Config),
     wait_for_master(Config),
     PeerJID = ?config(master, Config),
@@ -527,12 +527,12 @@ muc_invite_slave(Config) ->
                                decline = #muc_decline{to = PeerJID}}]}),
     disconnect(Config).
 
-muc_invite_members_only_master(Config) ->
+invite_members_only_master(Config) ->
     Room = muc_room_jid(Config),
     PeerJID = ?config(slave, Config),
-    ok = muc_join_new(Config),
+    ok = join_new(Config),
     %% Setting the room to members-only
-    [_|_] = muc_set_config(Config, [{membersonly, true}]),
+    [_|_] = set_config(Config, [{membersonly, true}]),
     wait_for_slave(Config),
     %% Inviting the peer
     send(Config, #message{to = Room, type = normal,
@@ -543,22 +543,22 @@ muc_invite_members_only_master(Config) ->
     #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),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_invite_members_only_slave(Config) ->
+invite_members_only_slave(Config) ->
     Room = muc_room_jid(Config),
     wait_for_master(Config),
     %% Receiving invitation
     #message{from = Room, type = normal} = recv_message(Config),
     disconnect(Config).
 
-muc_invite_password_protected_master(Config) ->
+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},
+    ok = join_new(Config),
+    [104] = set_config(Config, [{passwordprotectedroom, true},
                                     {roomsecret, Password}]),
     put_event(Config, Password),
     %% Inviting the peer
@@ -567,10 +567,10 @@ muc_invite_password_protected_master(Config) ->
                              [#muc_user{
                                  invites =
                                      [#muc_invite{to = PeerJID}]}]}),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_invite_password_protected_slave(Config) ->
+invite_password_protected_slave(Config) ->
     Room = muc_room_jid(Config),
     Password = get_event(Config),
     %% Receiving invitation
@@ -578,13 +578,13 @@ muc_invite_password_protected_slave(Config) ->
     #muc_user{password = Password} = xmpp:get_subtag(Msg, #muc_user{}),
     disconnect(Config).
 
-muc_voice_request_master(Config) ->
+voice_request_master(Config) ->
     Room = muc_room_jid(Config),
     PeerJID = ?config(slave, Config),
     PeerNick = ?config(slave_nick, Config),
     PeerNickJID = jid:replace_resource(Room, PeerNick),
-    ok = muc_join_new(Config),
-    [104] = muc_set_config(Config, [{members_by_default, false}]),
+    ok = join_new(Config),
+    [104] = set_config(Config, [{members_by_default, false}]),
     wait_for_slave(Config),
     #muc_user{
        items = [#muc_item{role = visitor,
@@ -610,16 +610,16 @@ muc_voice_request_master(Config) ->
        recv_muc_presence(Config, PeerNickJID, available),
     ct:comment("Waiting for the slave to leave"),
     recv_muc_presence(Config, PeerNickJID, unavailable),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_voice_request_slave(Config) ->
+voice_request_slave(Config) ->
     Room = muc_room_jid(Config),
     MyJID = my_jid(Config),
     MyNick = ?config(nick, Config),
     MyNickJID = jid:replace_resource(Room, MyNick),
     wait_for_master(Config),
-    {[], _, _} = muc_join(Config, visitor),
+    {[], _, _} = join(Config, visitor),
     ct:comment("Requesting voice"),
     Fs = muc_request:encode([{role, participant}]),
     X = #xdata{type = submit, fields = Fs},
@@ -630,17 +630,17 @@ muc_voice_request_slave(Config) ->
                          jid = MyJID,
                          affiliation = none}]} =
        recv_muc_presence(Config, MyNickJID, available),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_change_role_master(Config) ->
+change_role_master(Config) ->
     Room = muc_room_jid(Config),
     MyJID = my_jid(Config),
     MyNick = ?config(nick, Config),
     PeerJID = ?config(slave, Config),
     PeerNick = ?config(slave_nick, Config),
     PeerNickJID = jid:replace_resource(Room, PeerNick),
-    ok = muc_join_new(Config),
+    ok = join_new(Config),
     ct:comment("Waiting for the slave to join"),
     wait_for_slave(Config),
     #muc_user{items = [#muc_item{role = participant,
@@ -650,7 +650,7 @@ muc_change_role_master(Config) ->
     lists:foreach(
       fun(Role) ->
              ct:comment("Checking if the slave is not in the roles list"),
-             case muc_get_role(Config, Role) of
+             case get_role(Config, Role) of
                  [#muc_item{jid = MyJID, affiliation = owner,
                             role = moderator, nick = MyNick}] when Role == moderator ->
                      ok;
@@ -659,7 +659,7 @@ muc_change_role_master(Config) ->
              end,
              Reason = randoms:get_string(),
              put_event(Config, {Role, Reason}),
-             ok = muc_set_role(Config, Role, Reason),
+             ok = set_role(Config, Role, Reason),
              ct:comment("Receiving role change to ~s", [Role]),
              #muc_user{
                 items = [#muc_item{role = Role,
@@ -667,19 +667,19 @@ muc_change_role_master(Config) ->
                                    reason = Reason}]} =
                  recv_muc_presence(Config, PeerNickJID, available),
              [#muc_item{role = Role, affiliation = none,
-                        nick = PeerNick}|_] = muc_get_role(Config, Role)
+                        nick = PeerNick}|_] = get_role(Config, Role)
       end, [visitor, participant, moderator]),
     put_event(Config, disconnect),
     recv_muc_presence(Config, PeerNickJID, unavailable),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_change_role_slave(Config) ->
+change_role_slave(Config) ->
     wait_for_master(Config),
-    {[], _, _} = muc_join(Config),
-    muc_change_role_slave(Config, get_event(Config)).
+    {[], _, _} = join(Config),
+    change_role_slave(Config, get_event(Config)).
 
-muc_change_role_slave(Config, {Role, Reason}) ->
+change_role_slave(Config, {Role, Reason}) ->
     Room = muc_room_jid(Config),
     MyNick = ?config(slave_nick, Config),
     MyNickJID = jid:replace_resource(Room, MyNick),
@@ -690,12 +690,12 @@ muc_change_role_slave(Config, {Role, Reason}) ->
                                 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),
+    change_role_slave(Config, get_event(Config));
+change_role_slave(Config, disconnect) ->
+    ok = leave(Config),
     disconnect(Config).
 
-muc_change_affiliation_master(Config) ->
+change_affiliation_master(Config) ->
     Room = muc_room_jid(Config),
     MyJID = my_jid(Config),
     MyBareJID = jid:remove_resource(MyJID),
@@ -704,7 +704,7 @@ muc_change_affiliation_master(Config) ->
     PeerBareJID = jid:remove_resource(PeerJID),
     PeerNick = ?config(slave_nick, Config),
     PeerNickJID = jid:replace_resource(Room, PeerNick),
-    ok = muc_join_new(Config),
+    ok = join_new(Config),
     ct:comment("Waiting for the slave to join"),
     wait_for_slave(Config),
     #muc_user{items = [#muc_item{role = participant,
@@ -714,7 +714,7 @@ muc_change_affiliation_master(Config) ->
     lists:foreach(
       fun({Aff, Role, Status}) ->
              ct:comment("Checking if slave is not in affiliation list"),
-             case muc_get_affiliation(Config, Aff) of
+             case get_affiliation(Config, Aff) of
                  [#muc_item{jid = MyBareJID,
                             affiliation = owner}] when Aff == owner ->
                      ok;
@@ -723,7 +723,7 @@ muc_change_affiliation_master(Config) ->
              end,
              Reason = randoms:get_string(),
              put_event(Config, {Aff, Role, Status, Reason}),
-             ok = muc_set_affiliation(Config, Aff, Reason),
+             ok = set_affiliation(Config, Aff, Reason),
              ct:comment("Receiving affiliation change to ~s", [Aff]),
              #muc_user{
                 items = [#muc_item{role = Role,
@@ -737,7 +737,7 @@ muc_change_affiliation_master(Config) ->
                 true ->
                      ok
              end,
-             Affs = muc_get_affiliation(Config, Aff),
+             Affs = get_affiliation(Config, Aff),
              ct:comment("Checking if the affiliation was correctly set"),
              case lists:keyfind(PeerBareJID, #muc_item.jid, Affs) of
                  false when Aff == none ->
@@ -748,15 +748,15 @@ muc_change_affiliation_master(Config) ->
       end, [{member, participant, available}, {none, participant, available},
            {admin, moderator, available}, {owner, moderator, available},
            {outcast, none, unavailable}]),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_change_affiliation_slave(Config) ->
+change_affiliation_slave(Config) ->
     wait_for_master(Config),
-    {[], _, _} = muc_join(Config),
-    muc_change_affiliation_slave(Config, get_event(Config)).
+    {[], _, _} = join(Config),
+    change_affiliation_slave(Config, get_event(Config)).
 
-muc_change_affiliation_slave(Config, {Aff, Role, Status, Reason}) ->
+change_affiliation_slave(Config, {Aff, Role, Status, Reason}) ->
     Room = muc_room_jid(Config),
     PeerNick = ?config(master_nick, Config),
     MyNick = ?config(nick, Config),
@@ -776,17 +776,17 @@ muc_change_affiliation_slave(Config, {Aff, Role, Status, Reason}) ->
            #muc_actor{nick = PeerNick} = Actor,
            disconnect(Config);
        true ->
-           muc_change_affiliation_slave(Config, get_event(Config))
+           change_affiliation_slave(Config, get_event(Config))
     end.
 
-muc_kick_master(Config) ->
+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),
+    ok = join_new(Config),
     ct:comment("Waiting for the slave to join"),
     wait_for_slave(Config),
     #muc_user{items = [#muc_item{role = participant,
@@ -794,9 +794,9 @@ muc_kick_master(Config) ->
                                 affiliation = none}]} =
        recv_muc_presence(Config, PeerNickJID, available),
     [#muc_item{role = participant, affiliation = none,
-              nick = PeerNick}|_] = muc_get_role(Config, participant),
+              nick = PeerNick}|_] = get_role(Config, participant),
     ct:comment("Kicking slave"),
-    ok = muc_set_role(Config, none, Reason),
+    ok = set_role(Config, none, Reason),
     ct:comment("Receiving role change to 'none'"),
     #muc_user{
        status_codes = Codes,
@@ -805,20 +805,20 @@ muc_kick_master(Config) ->
                          actor = #muc_actor{nick = MyNick},
                          reason = Reason}]} =
        recv_muc_presence(Config, PeerNickJID, unavailable),
-    [] = muc_get_role(Config, participant),
+    [] = get_role(Config, participant),
     ct:comment("Checking if the code is '307' (kicked)"),
     true = lists:member(307, Codes),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_kick_slave(Config) ->
+kick_slave(Config) ->
     Room = muc_room_jid(Config),
     PeerNick = ?config(master_nick, Config),
     MyNick = ?config(nick, Config),
     MyNickJID = jid:replace_resource(Room, MyNick),
     Reason = <<"Testing">>,
     wait_for_master(Config),
-    {[], _, _} = muc_join(Config),
+    {[], _, _} = join(Config),
     ct:comment("Receiving role change to 'none'"),
     #muc_user{status_codes = Codes,
              items = [#muc_item{role = none,
@@ -832,7 +832,7 @@ muc_kick_slave(Config) ->
     true = lists:member(307, Codes),
     disconnect(Config).
 
-muc_destroy_master(Config) ->
+destroy_master(Config) ->
     Reason = <<"Testing">>,
     Room = muc_room_jid(Config),
     AltRoom = alt_room_jid(Config),
@@ -841,7 +841,7 @@ muc_destroy_master(Config) ->
     PeerNickJID = jid:replace_resource(Room, PeerNick),
     MyNick = ?config(nick, Config),
     MyNickJID = jid:replace_resource(Room, MyNick),
-    ok = muc_join_new(Config),
+    ok = join_new(Config),
     ct:comment("Waiting for slave to join"),
     wait_for_slave(Config),
     #muc_user{items = [#muc_item{role = participant,
@@ -849,7 +849,7 @@ muc_destroy_master(Config) ->
                                 affiliation = none}]} =
        recv_muc_presence(Config, PeerNickJID, available),
     wait_for_slave(Config),
-    ok = muc_destroy(Config, Reason),
+    ok = destroy(Config, Reason),
     ct:comment("Receiving destruction presence"),
     #muc_user{items = [#muc_item{role = none,
                                 affiliation = none}],
@@ -858,15 +858,15 @@ muc_destroy_master(Config) ->
        recv_muc_presence(Config, MyNickJID, unavailable),
     disconnect(Config).
 
-muc_destroy_slave(Config) ->
+destroy_slave(Config) ->
     Reason = <<"Testing">>,
     Room = muc_room_jid(Config),
     AltRoom = alt_room_jid(Config),
     MyNick = ?config(nick, Config),
     MyNickJID = jid:replace_resource(Room, MyNick),
     wait_for_master(Config),
-    {[], _, _} = muc_join(Config),
-    #stanza_error{reason = 'forbidden'} = muc_destroy(Config, Reason),
+    {[], _, _} = join(Config),
+    #stanza_error{reason = 'forbidden'} = destroy(Config, Reason),
     wait_for_master(Config),
     ct:comment("Receiving destruction presence"),
     #muc_user{items = [#muc_item{role = none,
@@ -876,43 +876,43 @@ muc_destroy_slave(Config) ->
        recv_muc_presence(Config, MyNickJID, unavailable),
     disconnect(Config).
 
-muc_vcard_master(Config) ->
+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),
+    ok = join_new(Config),
     ct:comment("Waiting for slave to join"),
     wait_for_slave(Config),
     #muc_user{items = [#muc_item{role = participant,
                                 affiliation = none}]} =
        recv_muc_presence(Config, PeerNickJID, available),
-    #stanza_error{reason = 'item-not-found'} = muc_get_vcard(Config),
-    ok = muc_set_vcard(Config, VCard),
-    VCard = muc_get_vcard(Config),
+    #stanza_error{reason = 'item-not-found'} = get_vcard(Config),
+    ok = set_vcard(Config, VCard),
+    VCard = get_vcard(Config),
     put_event(Config, VCard),
     recv_muc_presence(Config, PeerNickJID, unavailable),
     leave = get_event(Config),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_vcard_slave(Config) ->
+vcard_slave(Config) ->
     wait_for_master(Config),
-    {[], _, _} = muc_join(Config),
+    {[], _, _} = 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),
+    VCard = get_vcard(Config),
+    #stanza_error{reason = 'forbidden'} = set_vcard(Config, VCard),
+    ok = leave(Config),
+    VCard = get_vcard(Config),
     put_event(Config, leave),
     disconnect(Config).
 
-muc_nick_change_master(Config) ->
+nick_change_master(Config) ->
     NewNick = randoms:get_string(),
     PeerJID = ?config(peer, Config),
     PeerNickJID = peer_muc_jid(Config),
-    ok = muc_master_join(Config),
+    ok = master_join(Config),
     put_event(Config, {new_nick, NewNick}),
     ct:comment("Waiting for nickchange presence from the slave"),
     #muc_user{status_codes = Codes,
@@ -926,13 +926,13 @@ muc_nick_change_master(Config) ->
     recv_muc_presence(Config, PeerNewNickJID, available),
     ct:comment("Waiting for the slave to leave"),
     recv_muc_presence(Config, PeerNewNickJID, unavailable),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_nick_change_slave(Config) ->
+nick_change_slave(Config) ->
     MyJID = my_jid(Config),
     MyNickJID = my_muc_jid(Config),
-    {[], _, _} = muc_slave_join(Config),
+    {[], _, _} = slave_join(Config),
     {new_nick, NewNick} = get_event(Config),
     MyNewNickJID = jid:replace_resource(MyNickJID, NewNick),
     ct:comment("Sending new presence"),
@@ -955,131 +955,131 @@ muc_nick_change_slave(Config) ->
     ct:comment("Checking if code '110' (self-presence) is set"),
     lists:member(110, Codes2),
     NewConfig = set_opt(nick, NewNick, Config),
-    ok = muc_leave(NewConfig),
+    ok = leave(NewConfig),
     disconnect(NewConfig).
 
-muc_config_title_desc_master(Config) ->
+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),
+    ok = master_join(Config),
+    [104] = set_config(Config, [{roomname, Title}, {roomdesc, Desc}]),
+    RoomCfg = get_config(Config),
     Title = proplists:get_value(roomname, RoomCfg),
     Desc = proplists:get_value(roomdesc, RoomCfg),
     recv_muc_presence(Config, PeerNickJID, unavailable),
-    ok = muc_leave(Config),
+    ok = 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),
+config_title_desc_slave(Config) ->
+    {[], _, _} = slave_join(Config),
+    [104] = recv_config_change_message(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_config_public_list_master(Config) ->
+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),
+    ok = 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},
+    [104] = set_config(Config, [{public_list, false},
                                    {publicroom, false}]),
     recv_muc_presence(Config, PeerNickJID, unavailable),
     lists:member(<<"muc_hidden">>, get_features(Config, Room)),
     wait_for_slave(Config),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_config_public_list_slave(Config) ->
+config_public_list_slave(Config) ->
     Room = muc_room_jid(Config),
     wait_for_master(Config),
     PeerNick = ?config(peer_nick, Config),
     PeerNickJID = peer_muc_jid(Config),
-    [#disco_item{jid = Room}] = muc_disco_items(Config),
+    [#disco_item{jid = Room}] = 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),
+                name = PeerNick}] = disco_room_items(Config),
+    {[], _, _} = join(Config),
+    [104] = recv_config_change_message(Config),
+    ok = leave(Config),
+    [] = disco_items(Config),
+    [] = disco_room_items(Config),
     wait_for_master(Config),
     disconnect(Config).
 
-muc_config_password_master(Config) ->
+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),
+    ok = join_new(Config),
     lists:member(<<"muc_unsecured">>, get_features(Config, Room)),
-    [104] = muc_set_config(Config, [{passwordprotectedroom, true},
+    [104] = set_config(Config, [{passwordprotectedroom, true},
                                    {roomsecret, Password}]),
     lists:member(<<"muc_passwordprotected">>, get_features(Config, Room)),
     put_event(Config, Password),
     recv_muc_presence(Config, PeerNickJID, available),
     recv_muc_presence(Config, PeerNickJID, unavailable),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_config_password_slave(Config) ->
+config_password_slave(Config) ->
     Password = get_event(Config),
-    #stanza_error{reason = 'not-authorized'} = muc_join(Config),
+    #stanza_error{reason = 'not-authorized'} = 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),
+       join(Config, #muc{password = randoms:get_string()}),
+    {[], _, _} = join(Config, #muc{password = Password}),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_config_whois_master(Config) ->
+config_whois_master(Config) ->
     Room = muc_room_jid(Config),
     PeerNickJID = peer_muc_jid(Config),
     MyNickJID = my_muc_jid(Config),
-    ok = muc_master_join(Config),
+    ok = master_join(Config),
     lists:member(<<"muc_semianonymous">>, get_features(Config, Room)),
-    [172] = muc_set_config(Config, [{whois, anyone}]),
+    [172] = set_config(Config, [{whois, anyone}]),
     lists:member(<<"muc_nonanonymous">>, get_features(Config, Room)),
     recv_muc_presence(Config, PeerNickJID, unavailable),
     recv_muc_presence(Config, PeerNickJID, available),
     send(Config, #presence{to = Room}),
     recv_muc_presence(Config, MyNickJID, available),
-    [173] = muc_set_config(Config, [{whois, moderators}]),
+    [173] = set_config(Config, [{whois, moderators}]),
     recv_muc_presence(Config, PeerNickJID, unavailable),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_config_whois_slave(Config) ->
+config_whois_slave(Config) ->
     PeerJID = ?config(peer, Config),
     PeerNickJID = peer_muc_jid(Config),
-    {[], _, _} = muc_slave_join(Config),
+    {[], _, _} = slave_join(Config),
     ct:comment("Checking if the room becomes non-anonymous (code '172')"),
-    [172] = muc_recv_config_change_message(Config),
+    [172] = recv_config_change_message(Config),
     ct:comment("Re-joining in order to check status codes"),
-    ok = muc_leave(Config),
-    {[], _, Codes} = muc_join(Config),
+    ok = leave(Config),
+    {[], _, Codes} = join(Config),
     ct:comment("Checking if code '100' (non-anonymous) present"),
     true = lists:member(100, Codes),
     ct:comment("Receiving presence from peer with JID exposed"),
     #muc_user{items = [#muc_item{jid = PeerJID}]} =
        recv_muc_presence(Config, PeerNickJID, available),
     ct:comment("Waiting for the room to become anonymous again (code '173')"),
-    [173] = muc_recv_config_change_message(Config),
-    ok = muc_leave(Config),
+    [173] = recv_config_change_message(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_config_members_only_master(Config) ->
+config_members_only_master(Config) ->
     Room = muc_room_jid(Config),
     PeerJID = ?config(peer, Config),
     PeerBareJID = jid:remove_resource(PeerJID),
     PeerNickJID = peer_muc_jid(Config),
-    ok = muc_master_join(Config),
+    ok = master_join(Config),
     lists:member(<<"muc_open">>, get_features(Config, Room)),
-    [104] = muc_set_config(Config, [{membersonly, true}]),
+    [104] = set_config(Config, [{membersonly, true}]),
     #muc_user{status_codes = Codes,
              items = [#muc_item{jid = PeerJID,
                                 affiliation = none,
@@ -1090,7 +1090,7 @@ muc_config_members_only_master(Config) ->
     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()),
+    ok = 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}]} =
@@ -1099,7 +1099,7 @@ muc_config_members_only_master(Config) ->
     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()),
+    ok = 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,
@@ -1108,14 +1108,14 @@ muc_config_members_only_master(Config) ->
     ct:comment("Checking if code '321' (became non-member in "
               "members-only room) is set"),
     true = lists:member(321, NewCodes),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_config_members_only_slave(Config) ->
+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),
+    {[], _, _} = slave_join(Config),
+    [104] = 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,
@@ -1127,12 +1127,12 @@ muc_config_members_only_slave(Config) ->
     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),
+    #stanza_error{reason = 'registration-required'} = join(Config),
     ct:comment("Asking the peer to set us member"),
     put_event(Config, set_member),
     ct:comment("Waiting for the peer to ask for join"),
     join = get_event(Config),
-    {[], _, _} = muc_join(Config, participant, member),
+    {[], _, _} = join(Config, participant, member),
     #muc_user{status_codes = NewCodes,
              items = [#muc_item{jid = MyJID,
                                 role = none,
@@ -1144,71 +1144,71 @@ muc_config_members_only_slave(Config) ->
     true = lists:member(321, NewCodes),
     disconnect(Config).
 
-muc_config_moderated_master(Config) ->
+config_moderated_master(Config) ->
     Room = muc_room_jid(Config),
     PeerNickJID = peer_muc_jid(Config),
-    ok = muc_master_join(Config),
+    ok = master_join(Config),
     lists:member(<<"muc_moderated">>, get_features(Config, Room)),
-    ok = muc_set_role(Config, visitor, randoms:get_string()),
+    ok = 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}]),
+    [104] = set_config(Config, [{moderatedroom, false}]),
     #message{from = PeerNickJID, type = groupchat} = recv_message(Config),
     recv_muc_presence(Config, PeerNickJID, unavailable),
     lists:member(<<"muc_unmoderated">>, get_features(Config, Room)),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_config_moderated_slave(Config) ->
+config_moderated_slave(Config) ->
     Room = muc_room_jid(Config),
     MyNickJID = my_muc_jid(Config),
-    {[], _, _} = muc_slave_join(Config),
+    {[], _, _} = slave_join(Config),
     #muc_user{items = [#muc_item{role = visitor}]} =
        recv_muc_presence(Config, MyNickJID, available),
     send(Config, #message{to = Room, type = groupchat}),
     ErrMsg = #message{from = Room, type = error} = recv_message(Config),
     #stanza_error{reason = 'forbidden'} = xmpp:get_error(ErrMsg),
     put_event(Config, set_unmoderated),
-    [104] = muc_recv_config_change_message(Config),
+    [104] = recv_config_change_message(Config),
     send(Config, #message{to = Room, type = groupchat}),
     #message{from = MyNickJID, type = groupchat} = recv_message(Config),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_config_private_messages_master(Config) ->
+config_private_messages_master(Config) ->
     PeerNickJID = peer_muc_jid(Config),
-    ok = muc_master_join(Config),
+    ok = 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, <<>>),
+    ok = set_role(Config, visitor, <<>>),
     ct:comment("Waiting for the peer to become a visitor"),
     recv_muc_presence(Config, PeerNickJID, available),
     ct:comment("Waiting for a private message from the slave"),
     #message{from = PeerNickJID, type = chat} = recv_message(Config),
-    [104] = muc_set_config(Config, [{allow_private_messages_from_visitors, moderators}]),
+    [104] = set_config(Config, [{allow_private_messages_from_visitors, moderators}]),
     ct:comment("Waiting for a private message from the slave"),
     #message{from = PeerNickJID, type = chat} = recv_message(Config),
-    [104] = muc_set_config(Config, [{allow_private_messages_from_visitors, nobody}]),
+    [104] = set_config(Config, [{allow_private_messages_from_visitors, nobody}]),
     wait_for_slave(Config),
-    [104] = muc_set_config(Config, [{allow_private_messages_from_visitors, anyone},
+    [104] = 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, <<>>),
+    ok = set_role(Config, participant, <<>>),
     ct:comment("Waiting for the peer to become a participant"),
     recv_muc_presence(Config, PeerNickJID, available),
     ct:comment("Waiting for the peer to leave"),
     recv_muc_presence(Config, PeerNickJID, unavailable),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_config_private_messages_slave(Config) ->
+config_private_messages_slave(Config) ->
     MyNickJID = my_muc_jid(Config),
     PeerNickJID = peer_muc_jid(Config),
-    {[], _, _} = muc_slave_join(Config),
+    {[], _, _} = slave_join(Config),
     ct:comment("Sending a private message"),
     send(Config, #message{to = PeerNickJID, type = chat}),
     ct:comment("Waiting to become a visitor"),
@@ -1216,16 +1216,16 @@ muc_config_private_messages_slave(Config) ->
        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),
+    [104] = 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),
+    [104] = recv_config_change_message(Config),
     ct:comment("Fail trying to send a private message"),
     send(Config, #message{to = PeerNickJID, type = chat}),
     #message{from = PeerNickJID, type = error} = ErrMsg1 = recv_message(Config),
     #stanza_error{reason = 'forbidden'} = xmpp:get_error(ErrMsg1),
     wait_for_master(Config),
-    [104] = muc_recv_config_change_message(Config),
+    [104] = recv_config_change_message(Config),
     ct:comment("Waiting to become a participant again"),
     #muc_user{items = [#muc_item{role = participant}]} =
        recv_muc_presence(Config, MyNickJID, available),
@@ -1233,29 +1233,29 @@ muc_config_private_messages_slave(Config) ->
     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),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_config_query_master(Config) ->
+config_query_master(Config) ->
     PeerNickJID = peer_muc_jid(Config),
-    ok = muc_join_new(Config),
+    ok = join_new(Config),
     wait_for_slave(Config),
     recv_muc_presence(Config, PeerNickJID, available),
     ct:comment("Receiving IQ query from the slave"),
     #iq{type = get, from = PeerNickJID, id = I,
        sub_els = [#ping{}]} = recv_iq(Config),
     send(Config, #iq{type = result, to = PeerNickJID, id = I}),
-    [104] = muc_set_config(Config, [{allow_query_users, false}]),
+    [104] = set_config(Config, [{allow_query_users, false}]),
     ct:comment("Fail trying to send IQ"),
     #iq{type = error, from = PeerNickJID} = Err =
        send_recv(Config, #iq{type = get, to = PeerNickJID,
                              sub_els = [#ping{}]}),
     #stanza_error{reason = 'not-allowed'} = xmpp:get_error(Err),
     recv_muc_presence(Config, PeerNickJID, unavailable),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_config_query_slave(Config) ->
+config_query_slave(Config) ->
     PeerNickJID = peer_muc_jid(Config),
     wait_for_master(Config),
     ct:comment("Checking if IQ queries are denied from non-occupants"),
@@ -1263,28 +1263,28 @@ muc_config_query_slave(Config) ->
        send_recv(Config, #iq{type = get, to = PeerNickJID,
                              sub_els = [#ping{}]}),
     #stanza_error{reason = 'not-acceptable'} = xmpp:get_error(Err1),
-    {[], _, _} = muc_join(Config),
+    {[], _, _} = 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),
+    [104] = recv_config_change_message(Config),
     ct:comment("Fail trying to send IQ"),
     #iq{type = error, from = PeerNickJID} = Err2 =
        send_recv(Config, #iq{type = get, to = PeerNickJID,
                              sub_els = [#ping{}]}),
     #stanza_error{reason = 'not-allowed'} = xmpp:get_error(Err2),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_config_allow_invites_master(Config) ->
+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}]),
+    ok = master_join(Config),
+    [104] = set_config(Config, [{allowinvites, true}]),
     ct:comment("Receiving an invitation from the slave"),
     #message{from = Room, type = normal} = recv_message(Config),
-    [104] = muc_set_config(Config, [{allowinvites, false}]),
+    [104] = set_config(Config, [{allowinvites, false}]),
     send_invitation = get_event(Config),
     ct:comment("Sending an invitation"),
     send(Config, #message{to = Room, type = normal,
@@ -1293,10 +1293,10 @@ muc_config_allow_invites_master(Config) ->
                                  invites =
                                      [#muc_invite{to = PeerJID}]}]}),
     recv_muc_presence(Config, PeerNickJID, unavailable),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_config_allow_invites_slave(Config) ->
+config_allow_invites_slave(Config) ->
     Room = muc_room_jid(Config),
     PeerJID = ?config(peer, Config),
     InviteMsg = #message{to = Room, type = normal,
@@ -1304,11 +1304,11 @@ muc_config_allow_invites_slave(Config) ->
                             [#muc_user{
                                 invites =
                                     [#muc_invite{to = PeerJID}]}]},
-    {[], _, _} = muc_slave_join(Config),
-    [104] = muc_recv_config_change_message(Config),
+    {[], _, _} = slave_join(Config),
+    [104] = recv_config_change_message(Config),
     ct:comment("Sending an invitation"),
     send(Config, InviteMsg),
-    [104] = muc_recv_config_change_message(Config),
+    [104] = recv_config_change_message(Config),
     ct:comment("Fail sending an invitation"),
     send(Config, InviteMsg),
     #message{from = Room, type = error} = Err = recv_message(Config),
@@ -1316,85 +1316,85 @@ muc_config_allow_invites_slave(Config) ->
     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),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_config_visitor_status_master(Config) ->
+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}]),
+    ok = join_new(Config),
+    [104] = set_config(Config, [{members_by_default, false}]),
     ct:comment("Asking the slave to join as a visitor"),
     put_event(Config, {join, Status}),
     #muc_user{items = [#muc_item{role = visitor}]} =
        recv_muc_presence(Config, PeerNickJID, available),
     ct:comment("Receiving status change from the visitor"),
     #presence{from = PeerNickJID, status = Status} = recv_presence(Config),
-    [104] = muc_set_config(Config, [{allow_visitor_status, false}]),
+    [104] = 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),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_config_visitor_status_slave(Config) ->
+config_visitor_status_slave(Config) ->
     Room = muc_room_jid(Config),
     MyNickJID = my_muc_jid(Config),
     ct:comment("Waiting for 'join' command from the master"),
     {join, Status} = get_event(Config),
-    {[], _, _} = muc_join(Config, visitor, none),
+    {[], _, _} = 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),
+    [104] = recv_config_change_message(Config),
     ct:comment("Sending status change again"),
     send(Config, #presence{to = Room, status = Status}),
     #presence{from = MyNickJID, status = []} = recv_presence(Config),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_config_allow_voice_requests_master(Config) ->
+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}]),
+    ok = join_new(Config),
+    [104] = set_config(Config, [{members_by_default, false}]),
     ct:comment("Asking the slave to join as a visitor"),
     put_event(Config, join),
     #muc_user{items = [#muc_item{role = visitor}]} =
        recv_muc_presence(Config, PeerNickJID, available),
-    [104] = muc_set_config(Config, [{allow_voice_requests, false}]),
+    [104] = 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),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_config_allow_voice_requests_slave(Config) ->
+config_allow_voice_requests_slave(Config) ->
     Room = muc_room_jid(Config),
     ct:comment("Waiting for 'join' command from the master"),
     join = get_event(Config),
-    {[], _, _} = muc_join(Config, visitor),
-    [104] = muc_recv_config_change_message(Config),
+    {[], _, _} = join(Config, visitor),
+    [104] = recv_config_change_message(Config),
     ct:comment("Fail sending voice request"),
     Fs = muc_request:encode([{role, participant}]),
     X = #xdata{type = submit, fields = Fs},
     send(Config, #message{to = Room, sub_els = [X]}),
     #message{from = Room, type = error} = Err = recv_message(Config),
     #stanza_error{reason = 'forbidden'} = xmpp:get_error(Err),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_config_voice_request_interval_master(Config) ->
+config_voice_request_interval_master(Config) ->
     Room = muc_room_jid(Config),
     PeerJID = ?config(peer, Config),
     PeerNick = ?config(peer_nick, Config),
     PeerNickJID = peer_muc_jid(Config),
-    ok = muc_join_new(Config),
-    [104] = muc_set_config(Config, [{members_by_default, false}]),
+    ok = join_new(Config),
+    [104] = set_config(Config, [{members_by_default, false}]),
     ct:comment("Asking the slave to join as a visitor"),
     put_event(Config, join),
     #muc_user{items = [#muc_item{role = visitor}]} =
        recv_muc_presence(Config, PeerNickJID, available),
-    [104] = muc_set_config(Config, [{voice_request_min_interval, 5}]),
+    [104] = set_config(Config, [{voice_request_min_interval, 5}]),
     ct:comment("Receiving a voice request from slave"),
     #message{from = Room, type = normal} = recv_message(Config),
     ct:comment("Deny voice request at first"),
@@ -1407,17 +1407,17 @@ muc_config_voice_request_interval_master(Config) ->
     #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),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_config_voice_request_interval_slave(Config) ->
+config_voice_request_interval_slave(Config) ->
     Room = muc_room_jid(Config),
     Fs = muc_request:encode([{role, participant}]),
     X = #xdata{type = submit, fields = Fs},
     ct:comment("Waiting for 'join' command from the master"),
     join = get_event(Config),
-    {[], _, _} = muc_join(Config, visitor),
-    [104] = muc_recv_config_change_message(Config),
+    {[], _, _} = join(Config, visitor),
+    [104] = recv_config_change_message(Config),
     ct:comment("Sending voice request"),
     send(Config, #message{to = Room, sub_els = [X]}),
     ct:comment("Waiting for the master to deny our voice request"),
@@ -1431,59 +1431,59 @@ muc_config_voice_request_interval_slave(Config) ->
     timer:sleep(timer:seconds(5)),
     ct:comment("Repeating again"),
     send(Config, #message{to = Room, sub_els = [X]}),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_config_visitor_nickchange_master(Config) ->
+config_visitor_nickchange_master(Config) ->
     PeerNickJID = peer_muc_jid(Config),
-    ok = muc_join_new(Config),
-    [104] = muc_set_config(Config, [{members_by_default, false}]),
+    ok = join_new(Config),
+    [104] = set_config(Config, [{members_by_default, false}]),
     ct:comment("Asking the slave to join as a visitor"),
     put_event(Config, join),
     ct:comment("Waiting for the slave to join"),
     #muc_user{items = [#muc_item{role = visitor}]} =
        recv_muc_presence(Config, PeerNickJID, available),
-    [104] = muc_set_config(Config, [{allow_visitor_nickchange, false}]),
+    [104] = 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),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_config_visitor_nickchange_slave(Config) ->
+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),
+    {[], _, _} = join(Config, visitor),
+    [104] = recv_config_change_message(Config),
     ct:comment("Fail trying to change nickname"),
     send(Config, #presence{to = MyNewNickJID}),
     #presence{from = MyNewNickJID, type = error} = Err = recv_presence(Config),
     #stanza_error{reason = 'not-allowed'} = xmpp:get_error(Err),
-    ok = muc_leave(Config),
+    ok = leave(Config),
     disconnect(Config).
 
-muc_register_master(Config) ->
+register_master(Config) ->
     MUC = muc_jid(Config),
     %% Register nick "master1"
-    muc_register_nick(Config, MUC, <<"">>, <<"master1">>),
+    register_nick(Config, MUC, <<"">>, <<"master1">>),
     %% Unregister nick "master1" via jabber:register
     #iq{type = result, sub_els = []} =
        send_recv(Config, #iq{type = set, to = MUC,
                              sub_els = [#register{remove = true}]}),
     %% Register nick "master2"
-    muc_register_nick(Config, MUC, <<"">>, <<"master2">>),
+    register_nick(Config, MUC, <<"">>, <<"master2">>),
     %% Now register nick "master"
-    muc_register_nick(Config, MUC, <<"master2">>, <<"master">>),
+    register_nick(Config, MUC, <<"master2">>, <<"master">>),
     %% Wait for slave to fail trying to register nick "master"
     wait_for_slave(Config),
     wait_for_slave(Config),
     %% Now register empty ("") nick, which means we're unregistering
-    muc_register_nick(Config, MUC, <<"master">>, <<"">>),
+    register_nick(Config, MUC, <<"master">>, <<"">>),
     disconnect(Config).
 
-muc_register_slave(Config) ->
+register_slave(Config) ->
     MUC = muc_jid(Config),
     wait_for_master(Config),
     %% Trying to register occupied nick "master"
@@ -1498,18 +1498,22 @@ muc_register_slave(Config) ->
 %%%===================================================================
 %%% Internal functions
 %%%===================================================================
+single_test(T) ->
+    list_to_atom("muc_" ++ atom_to_list(T)).
+
 master_slave_test(T) ->
-    {T, [parallel], [list_to_atom(atom_to_list(T) ++ "_master"),
-                    list_to_atom(atom_to_list(T) ++ "_slave")]}.
+    {list_to_atom("muc_" ++ atom_to_list(T)), [parallel],
+     [list_to_atom("muc_" ++ atom_to_list(T) ++ "_master"),
+      list_to_atom("muc_" ++ atom_to_list(T) ++ "_slave")]}.
 
 recv_muc_presence(Config, From, Type) ->
     Pres = #presence{from = From, type = Type} = recv_presence(Config),
     xmpp:get_subtag(Pres, #muc_user{}).
 
-muc_join_new(Config) ->
-    muc_join_new(Config, muc_room_jid(Config)).
+join_new(Config) ->
+    join_new(Config, muc_room_jid(Config)).
 
-muc_join_new(Config, Room) ->
+join_new(Config, Room) ->
     MyJID = my_jid(Config),
     MyNick = ?config(nick, Config),
     MyNickJID = jid:replace_resource(Room, MyNick),
@@ -1537,41 +1541,41 @@ muc_join_new(Config, Room) ->
             subject = [#text{data = <<>>}]} = recv_message(Config),
     case ?config(persistent_room, Config) of
        true ->
-           [104] = muc_set_config(Config, [{persistentroom, true}], Room),
+           [104] = set_config(Config, [{persistentroom, true}], Room),
            ok;
        false ->
            ok
     end.
 
-muc_recv_history_and_subject(Config) ->
+recv_history_and_subject(Config) ->
     ct:comment("Receiving room history and/or subject"),
-    muc_recv_history_and_subject(Config, []).
+    recv_history_and_subject(Config, []).
 
-muc_recv_history_and_subject(Config, History) ->
+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]);
+           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{}).
+join(Config) ->
+    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).
+join(Config, Role) when is_atom(Role) ->
+    join(Config, Role, none, #muc{});
+join(Config, #muc{} = SubEl) ->
+    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).
+join(Config, Role, Aff) when is_atom(Role), is_atom(Aff) ->
+    join(Config, Role, Aff, #muc{});
+join(Config, Role, #muc{} = SubEl) when is_atom(Role) ->
+    join(Config, Role, none, SubEl).
 
-muc_join(Config, Role, Aff, SubEl) ->
+join(Config, Role, Aff, SubEl) ->
     ct:comment("Joining existing room as ~s/~s", [Aff, Role]),
     MyJID = my_jid(Config),
     Room = muc_room_jid(Config),
@@ -1595,7 +1599,7 @@ muc_join(Config, Role, Aff, SubEl) ->
                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} = recv_history_and_subject(Config),
            {History, Subj, Codes};
        #presence{type = available, from = MyNickJID} = Pres ->
            #muc_user{status_codes = Codes,
@@ -1605,21 +1609,21 @@ muc_join(Config, Role, Aff, SubEl) ->
                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),
+           {History, Subj} = recv_history_and_subject(Config),
            {empty, History, Subj, Codes}
     end.
 
-muc_leave(Config) ->
-    muc_leave(Config, muc_room_jid(Config)).
+leave(Config) ->
+    leave(Config, muc_room_jid(Config)).
 
-muc_leave(Config, Room) ->
+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);
+           [104] = set_config(Config, [{persistentroom, false}], Room);
        true ->
            ok
     end,
@@ -1633,7 +1637,7 @@ muc_leave(Config, Room) ->
     true = lists:member(110, Codes),
     ok.
 
-muc_get_config(Config) ->
+get_config(Config) ->
     ct:comment("Get room config"),
     Room = muc_room_jid(Config),
     case send_recv(Config,
@@ -1646,10 +1650,10 @@ muc_get_config(Config) ->
            xmpp:get_subtag(Err, #stanza_error{})
     end.
 
-muc_set_config(Config, RoomConfig) ->
-    muc_set_config(Config, RoomConfig, muc_room_jid(Config)).
+set_config(Config, RoomConfig) ->
+    set_config(Config, RoomConfig, muc_room_jid(Config)).
 
-muc_set_config(Config, RoomConfig, Room) ->
+set_config(Config, RoomConfig, Room) ->
     ct:comment("Set room config: ~p", [RoomConfig]),
     Fs = case RoomConfig of
             [] -> [];
@@ -1667,15 +1671,15 @@ muc_set_config(Config, RoomConfig, Room) ->
            xmpp:get_subtag(Err, #stanza_error{})
     end.
 
-muc_create_persistent(Config) ->
-    [_|_] = muc_get_config(Config),
-    [] = muc_set_config(Config, [{persistentroom, true}], false),
+create_persistent(Config) ->
+    [_|_] = get_config(Config),
+    [] = set_config(Config, [{persistentroom, true}], false),
     ok.
 
-muc_destroy(Config) ->
-    muc_destroy(Config, <<>>).
+destroy(Config) ->
+    destroy(Config, <<>>).
 
-muc_destroy(Config, Reason) ->
+destroy(Config, Reason) ->
     Room = muc_room_jid(Config),
     AltRoom = alt_room_jid(Config),
     ct:comment("Destroying a room"),
@@ -1690,7 +1694,7 @@ muc_destroy(Config, Reason) ->
            xmpp:get_subtag(Err, #stanza_error{})
     end.
 
-muc_disco_items(Config) ->
+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]} =
@@ -1698,14 +1702,14 @@ muc_disco_items(Config) ->
                              sub_els = [#disco_items{}]}),
     lists:keysort(#disco_item.jid, DiscoItems#disco_items.items).
 
-muc_disco_room_items(Config) ->
+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) ->
+get_affiliations(Config, Aff) ->
     Room = muc_room_jid(Config),
     case send_recv(Config,
                   #iq{type = get, to = Room,
@@ -1716,12 +1720,12 @@ muc_get_affiliations(Config, Aff) ->
            xmpp:get_subtag(Err, #stanza_error{})
     end.
 
-muc_master_join(Config) ->
+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),
+    ok = join_new(Config),
     wait_for_slave(Config),
     #muc_user{items = [#muc_item{jid = PeerJID,
                                 role = participant,
@@ -1729,11 +1733,11 @@ muc_master_join(Config) ->
        recv_muc_presence(Config, PeerNickJID, available),
     ok.
 
-muc_slave_join(Config) ->
+slave_join(Config) ->
     wait_for_master(Config),
-    muc_join(Config).
+    join(Config).
 
-muc_set_role(Config, Role, Reason) ->
+set_role(Config, Role, Reason) ->
     ct:comment("Changing role to ~s", [Role]),
     Room = muc_room_jid(Config),
     PeerNick = ?config(slave_nick, Config),
@@ -1751,7 +1755,7 @@ muc_set_role(Config, Role, Reason) ->
            xmpp:get_subtag(Err, #stanza_error{})
     end.
 
-muc_get_role(Config, Role) ->
+get_role(Config, Role) ->
     ct:comment("Requesting list for role '~s'", [Role]),
     Room = muc_room_jid(Config),
     case send_recv(
@@ -1765,7 +1769,7 @@ muc_get_role(Config, Role) ->
            xmpp:get_subtag(Err, #stanza_error{})
     end.
 
-muc_set_affiliation(Config, Aff, Reason) ->
+set_affiliation(Config, Aff, Reason) ->
     ct:comment("Changing affiliation to ~s", [Aff]),
     Room = muc_room_jid(Config),
     PeerJID = ?config(slave, Config),
@@ -1784,7 +1788,7 @@ muc_set_affiliation(Config, Aff, Reason) ->
            xmpp:get_subtag(Err, #stanza_error{})
     end.
 
-muc_get_affiliation(Config, Aff) ->
+get_affiliation(Config, Aff) ->
     ct:comment("Requesting list for affiliation '~s'", [Aff]),
     Room = muc_room_jid(Config),
     case send_recv(
@@ -1798,7 +1802,7 @@ muc_get_affiliation(Config, Aff) ->
            xmpp:get_subtag(Err, #stanza_error{})
     end.
 
-muc_set_vcard(Config, VCard) ->
+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,
@@ -1809,7 +1813,7 @@ muc_set_vcard(Config, VCard) ->
            xmpp:get_subtag(Err, #stanza_error{})
     end.
 
-muc_get_vcard(Config) ->
+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,
@@ -1820,14 +1824,14 @@ muc_get_vcard(Config) ->
            xmpp:get_subtag(Err, #stanza_error{})
     end.
 
-muc_recv_config_change_message(Config) ->
+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) ->
+register_nick(Config, MUC, PrevNick, Nick) ->
     PrevRegistered = if PrevNick /= <<"">> -> true;
                        true -> false
                     end,
@@ -1859,7 +1863,7 @@ muc_register_nick(Config, MUC, PrevNick, Nick) ->
     Nick = proplists:get_value(
             roomnick, muc_register:decode(FsWithNick)).
 
-muc_subscribe(Config, Events, Room) ->
+subscribe(Config, Events, Room) ->
     MyNick = ?config(nick, Config),
     case send_recv(Config,
                   #iq{type = set, to = Room,
@@ -1871,7 +1875,7 @@ muc_subscribe(Config, Events, Room) ->
            xmpp:get_error(Err)
     end.
 
-muc_unsubscribe(Config, Room) ->
+unsubscribe(Config, Room) ->
     case send_recv(Config, #iq{type = set, to = Room,
                               sub_els = [#muc_unsubscribe{}]}) of
        #iq{type = result, sub_els = []} ->
index 2ee945f1e4e8c571f490ba2d024637d806d40e13..640f53d4805c2370cd38c2c7ba9317883badab2b 100644 (file)
@@ -13,7 +13,7 @@
 -import(suite, [disconnect/1, send_recv/2, get_event/1, put_event/2,
                recv_iq/1, recv_presence/1, recv_message/1, recv/1,
                send/2, my_jid/1, server_jid/1, get_features/1,
-               set_roster/3, del_roster/1]).
+               set_roster/3, del_roster/1, get_roster/1]).
 -include("suite.hrl").
 -include("mod_roster.hrl").
 
@@ -233,7 +233,7 @@ set_get_block(Config) ->
 %%% Master-slave cases
 %%%===================================================================
 master_slave_cases() ->
-    {privacy_master_slave, [parallel],
+    {privacy_master_slave, [sequence],
      [master_slave_test(deny_bare_jid),
       master_slave_test(deny_full_jid),
       master_slave_test(deny_server_jid),
@@ -319,7 +319,8 @@ deny_master(Config, {Type, Value}) ->
     set_roster(Config, Sub, Groups),
     lists:foreach(
       fun(Opts) ->
-             ListName = str:format("deny-~s-~s-~p", [Type, Value, Opts]),
+             ct:pal("Set list for ~s, ~s, ~w", [Type, Value, Opts]),
+             ListName = randoms:get_string(),
              Item = #privacy_item{order = 0,
                                   action = deny,
                                   iq = proplists:get_bool(iq, Opts),
diff --git a/test/proxy65_tests.erl b/test/proxy65_tests.erl
new file mode 100644 (file)
index 0000000..01292f5
--- /dev/null
@@ -0,0 +1,105 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 16 Nov 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(proxy65_tests).
+
+%% API
+-compile(export_all).
+-import(suite, [disconnect/1, is_feature_advertised/3, proxy_jid/1,
+               my_jid/1, wait_for_slave/1, wait_for_master/1,
+               send_recv/2, put_event/2, get_event/1]).
+
+-include("suite.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+%%%===================================================================
+%%% Single user tests
+%%%===================================================================
+single_cases() ->
+    {proxy65_single, [sequence],
+     [single_test(feature_enabled)]}.
+
+feature_enabled(Config) ->
+    true = is_feature_advertised(Config, ?NS_BYTESTREAMS, proxy_jid(Config)),
+    disconnect(Config).
+
+%%%===================================================================
+%%% Master-slave tests
+%%%===================================================================
+master_slave_cases() ->
+    {proxy65_master_slave, [sequence],
+     [master_slave_test(all)]}.
+
+all_master(Config) ->
+    Proxy = proxy_jid(Config),
+    MyJID = my_jid(Config),
+    Peer = ?config(slave, Config),
+    wait_for_slave(Config),
+    #presence{} = send_recv(Config, #presence{}),
+    #iq{type = result, sub_els = [#bytestreams{hosts = [StreamHost]}]} =
+        send_recv(
+          Config,
+          #iq{type = get, sub_els = [#bytestreams{}], to = Proxy}),
+    SID = randoms:get_string(),
+    Data = randoms:bytes(1024),
+    put_event(Config, {StreamHost, SID, Data}),
+    Socks5 = socks5_connect(StreamHost, {SID, MyJID, Peer}),
+    wait_for_slave(Config),
+    #iq{type = result, sub_els = []} =
+        send_recv(Config,
+                  #iq{type = set, to = Proxy,
+                      sub_els = [#bytestreams{activate = Peer, sid = SID}]}),
+    socks5_send(Socks5, Data),
+    disconnect(Config).
+
+all_slave(Config) ->
+    MyJID = my_jid(Config),
+    Peer = ?config(master, Config),
+    #presence{} = send_recv(Config, #presence{}),
+    wait_for_master(Config),
+    {StreamHost, SID, Data} = get_event(Config),
+    Socks5 = socks5_connect(StreamHost, {SID, Peer, MyJID}),
+    wait_for_master(Config),
+    socks5_recv(Socks5, Data),
+    disconnect(Config).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+single_test(T) ->
+    list_to_atom("proxy65_" ++ atom_to_list(T)).
+
+master_slave_test(T) ->
+    {list_to_atom("proxy65_" ++ atom_to_list(T)), [parallel],
+     [list_to_atom("proxy65_" ++ atom_to_list(T) ++ "_master"),
+      list_to_atom("proxy65_" ++ atom_to_list(T) ++ "_slave")]}.
+
+socks5_connect(#streamhost{host = Host, port = Port},
+               {SID, JID1, JID2}) ->
+    Hash = p1_sha:sha([SID, jid:to_string(JID1), jid:to_string(JID2)]),
+    {ok, Sock} = gen_tcp:connect(binary_to_list(Host), Port,
+                                 [binary, {active, false}]),
+    Init = <<?VERSION_5, 1, ?AUTH_ANONYMOUS>>,
+    InitAck = <<?VERSION_5, ?AUTH_ANONYMOUS>>,
+    Req = <<?VERSION_5, ?CMD_CONNECT, 0,
+            ?ATYP_DOMAINNAME, 40, Hash:40/binary, 0, 0>>,
+    Resp = <<?VERSION_5, ?SUCCESS, 0, ?ATYP_DOMAINNAME,
+             40, Hash:40/binary, 0, 0>>,
+    gen_tcp:send(Sock, Init),
+    {ok, InitAck} = gen_tcp:recv(Sock, size(InitAck)),
+    gen_tcp:send(Sock, Req),
+    {ok, Resp} = gen_tcp:recv(Sock, size(Resp)),
+    Sock.
+
+socks5_send(Sock, Data) ->
+    ok = gen_tcp:send(Sock, Data).
+
+socks5_recv(Sock, Data) ->
+    {ok, Data} = gen_tcp:recv(Sock, size(Data)).
diff --git a/test/pubsub_tests.erl b/test/pubsub_tests.erl
new file mode 100644 (file)
index 0000000..fae7234
--- /dev/null
@@ -0,0 +1,729 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 16 Nov 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(pubsub_tests).
+
+%% API
+-compile(export_all).
+-import(suite, [pubsub_jid/1, send_recv/2, get_features/2, disconnect/1,
+               put_event/2, get_event/1, wait_for_master/1, wait_for_slave/1,
+               recv_message/1, my_jid/1, send/2, recv_presence/1, recv/1]).
+
+-include("suite.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+%%%===================================================================
+%%% Single user tests
+%%%===================================================================
+single_cases() ->
+    {pubsub_single, [sequence],
+     [single_test(test_features),
+      single_test(test_create),
+      single_test(test_configure),
+      single_test(test_delete),
+      single_test(test_get_affiliations),
+      single_test(test_get_subscriptions),
+      single_test(test_create_instant),
+      single_test(test_default),
+      single_test(test_create_configure),
+      single_test(test_publish),
+      single_test(test_auto_create),
+      single_test(test_get_items),
+      single_test(test_delete_item),
+      single_test(test_purge),
+      single_test(test_subscribe),
+      single_test(test_unsubscribe)]}.
+
+test_features(Config) ->
+    PJID = pubsub_jid(Config),
+    AllFeatures = sets:from_list(get_features(Config, PJID)),
+    NeededFeatures = sets:from_list(
+                      [?NS_PUBSUB,
+                       ?PUBSUB("access-open"),
+                       ?PUBSUB("access-authorize"),
+                       ?PUBSUB("create-nodes"),
+                       ?PUBSUB("instant-nodes"),
+                       ?PUBSUB("config-node"),
+                       ?PUBSUB("retrieve-default"),
+                       ?PUBSUB("create-and-configure"),
+                       ?PUBSUB("publish"),
+                       ?PUBSUB("auto-create"),
+                       ?PUBSUB("retrieve-items"),
+                       ?PUBSUB("delete-items"),
+                       ?PUBSUB("subscribe"),
+                       ?PUBSUB("retrieve-affiliations"),
+                       ?PUBSUB("modify-affiliations"),
+                       ?PUBSUB("retrieve-subscriptions"),
+                       ?PUBSUB("manage-subscriptions"),
+                       ?PUBSUB("purge-nodes"),
+                       ?PUBSUB("delete-nodes")]),
+    true = sets:is_subset(NeededFeatures, AllFeatures),
+    disconnect(Config).
+
+test_create(Config) ->
+    Node = ?config(pubsub_node, Config),
+    Node = create_node(Config, Node),
+    disconnect(Config).
+
+test_create_instant(Config) ->
+    Node = create_node(Config, <<>>),
+    delete_node(Config, Node),
+    disconnect(Config).
+
+test_configure(Config) ->
+    Node = ?config(pubsub_node, Config),
+    NodeTitle = ?config(pubsub_node_title, Config),
+    NodeConfig = get_node_config(Config, Node),
+    MyNodeConfig = set_opts(NodeConfig,
+                           [{title, NodeTitle}]),
+    set_node_config(Config, Node, MyNodeConfig),
+    NewNodeConfig = get_node_config(Config, Node),
+    NodeTitle = proplists:get_value(title, NewNodeConfig),
+    disconnect(Config).
+
+test_default(Config) ->
+    get_default_node_config(Config),
+    disconnect(Config).
+
+test_create_configure(Config) ->
+    NodeTitle = ?config(pubsub_node_title, Config),
+    DefaultNodeConfig = get_default_node_config(Config),
+    CustomNodeConfig = set_opts(DefaultNodeConfig,
+                               [{title, NodeTitle}]),
+    Node = create_node(Config, <<>>, CustomNodeConfig),
+    NodeConfig = get_node_config(Config, Node),
+    NodeTitle = proplists:get_value(title, NodeConfig),
+    delete_node(Config, Node),
+    disconnect(Config).
+
+test_publish(Config) ->
+    Node = create_node(Config, <<>>),
+    publish_item(Config, Node),
+    delete_node(Config, Node),
+    disconnect(Config).
+
+test_auto_create(Config) ->
+    Node = randoms:get_string(),
+    publish_item(Config, Node),
+    delete_node(Config, Node),
+    disconnect(Config).
+
+test_get_items(Config) ->
+    Node = create_node(Config, <<>>),
+    ItemsIn = [publish_item(Config, Node) || _ <- lists:seq(1, 5)],
+    ItemsOut = get_items(Config, Node),
+    true = [I || #ps_item{id = I} <- lists:sort(ItemsIn)]
+       == [I || #ps_item{id = I} <- lists:sort(ItemsOut)],
+    delete_node(Config, Node),
+    disconnect(Config).
+
+test_delete_item(Config) ->
+    Node = create_node(Config, <<>>),
+    #ps_item{id = I} = publish_item(Config, Node),
+    [#ps_item{id = I}] = get_items(Config, Node),
+    delete_item(Config, Node, I),
+    [] = get_items(Config, Node),
+    delete_node(Config, Node),
+    disconnect(Config).
+
+test_subscribe(Config) ->
+    Node = create_node(Config, <<>>),
+    #ps_subscription{type = subscribed} = subscribe_node(Config, Node),
+    [#ps_subscription{node = Node}] = get_subscriptions(Config),
+    delete_node(Config, Node),
+    disconnect(Config).
+
+test_unsubscribe(Config) ->
+    Node = create_node(Config, <<>>),
+    subscribe_node(Config, Node),
+    [#ps_subscription{node = Node}] = get_subscriptions(Config),
+    unsubscribe_node(Config, Node),
+    [] = get_subscriptions(Config),
+    delete_node(Config, Node),
+    disconnect(Config).
+
+test_get_affiliations(Config) ->
+    Nodes = lists:sort([create_node(Config, <<>>) || _ <- lists:seq(1, 5)]),
+    Affs = get_affiliations(Config),
+    Nodes = lists:sort([Node || #ps_affiliation{node = Node,
+                                               type = owner} <- Affs]),
+    [delete_node(Config, Node) || Node <- Nodes],
+    disconnect(Config).
+
+test_get_subscriptions(Config) ->
+    Nodes = lists:sort([create_node(Config, <<>>) || _ <- lists:seq(1, 5)]),
+    [subscribe_node(Config, Node) || Node <- Nodes],
+    Subs = get_subscriptions(Config),
+    Nodes = lists:sort([Node || #ps_subscription{node = Node} <- Subs]),
+    [delete_node(Config, Node) || Node <- Nodes],
+    disconnect(Config).
+
+test_purge(Config) ->
+    Node = create_node(Config, <<>>),
+    ItemsIn = [publish_item(Config, Node) || _ <- lists:seq(1, 5)],
+    ItemsOut = get_items(Config, Node),
+    true = [I || #ps_item{id = I} <- lists:sort(ItemsIn)]
+       == [I || #ps_item{id = I} <- lists:sort(ItemsOut)],
+    purge_node(Config, Node),
+    [] = get_items(Config, Node),
+    delete_node(Config, Node),
+    disconnect(Config).
+
+test_delete(Config) ->
+    Node = ?config(pubsub_node, Config),
+    delete_node(Config, Node),
+    disconnect(Config).
+
+%%%===================================================================
+%%% Master-slave tests
+%%%===================================================================
+master_slave_cases() ->
+    {pubsub_master_slave, [sequence],
+     [master_slave_test(publish),
+      master_slave_test(subscriptions),
+      master_slave_test(affiliations),
+      master_slave_test(authorize)]}.
+
+publish_master(Config) ->
+    Node = create_node(Config, <<>>),
+    put_event(Config, Node),
+    wait_for_slave(Config),
+    #ps_item{id = ID} = publish_item(Config, Node),
+    #ps_item{id = ID} = get_event(Config),
+    delete_node(Config, Node),
+    disconnect(Config).
+
+publish_slave(Config) ->
+    Node = get_event(Config),
+    subscribe_node(Config, Node),
+    wait_for_master(Config),
+    #message{
+       sub_els =
+          [#ps_event{
+              items = #ps_items{node = Node,
+                                items = [Item]}}]} = recv_message(Config),
+    put_event(Config, Item),
+    disconnect(Config).
+
+subscriptions_master(Config) ->
+    Peer = ?config(slave, Config),
+    Node = ?config(pubsub_node, Config),
+    Node = create_node(Config, Node),
+    [] = get_subscriptions(Config, Node),
+    wait_for_slave(Config),
+    lists:foreach(
+      fun(Type) ->
+             ok = set_subscriptions(Config, Node, [{Peer, Type}]),
+             #ps_item{} = publish_item(Config, Node),
+             case get_subscriptions(Config, Node) of
+                 [] when Type == none; Type == pending ->
+                     ok;
+                 [#ps_subscription{jid = Peer, type = Type}] ->
+                     ok
+             end
+      end, [subscribed, unconfigured, pending, none]),
+    delete_node(Config, Node),
+    disconnect(Config).
+
+subscriptions_slave(Config) ->
+    wait_for_master(Config),
+    MyJID = my_jid(Config),
+    Node = ?config(pubsub_node, Config),
+    lists:foreach(
+      fun(subscribed = Type) ->
+             ?recv2(#message{
+                       sub_els =
+                           [#ps_event{
+                               subscription = #ps_subscription{
+                                                 node = Node,
+                                                 jid = MyJID,
+                                                 type = Type}}]},
+                    #message{sub_els = [#ps_event{}]});
+        (Type) ->
+             #message{
+                sub_els =
+                    [#ps_event{
+                        subscription = #ps_subscription{
+                                          node = Node,
+                                          jid = MyJID,
+                                          type = Type}}]} =
+                 recv_message(Config)
+      end, [subscribed, unconfigured, pending, none]),
+    disconnect(Config).
+
+affiliations_master(Config) ->
+    Peer = ?config(slave, Config),
+    BarePeer = jid:remove_resource(Peer),
+    lists:foreach(
+      fun(Aff) ->
+             Node = <<(atom_to_binary(Aff, utf8))/binary,
+                      $-, (randoms:get_string())/binary>>,
+             create_node(Config, Node, default_node_config(Config)),
+             #ps_item{id = I} = publish_item(Config, Node),
+             ok = set_affiliations(Config, Node, [{Peer, Aff}]),
+             Affs = get_affiliations(Config, Node),
+             case lists:keyfind(BarePeer, #ps_affiliation.jid, Affs) of
+                 false when Aff == none ->
+                     ok;
+                 #ps_affiliation{type = Aff} ->
+                     ok
+             end,
+             put_event(Config, {Aff, Node, I}),
+             wait_for_slave(Config),
+             delete_node(Config, Node)
+      end, [outcast, none, member, publish_only, publisher, owner]),
+    put_event(Config, disconnect),
+    disconnect(Config).
+
+affiliations_slave(Config) ->
+    affiliations_slave(Config, get_event(Config)).
+
+affiliations_slave(Config, {outcast, Node, ItemID}) ->
+    #stanza_error{reason = 'forbidden'} = subscribe_node(Config, Node),
+    #stanza_error{} = unsubscribe_node(Config, Node),
+    #stanza_error{reason = 'forbidden'} = get_items(Config, Node),
+    #stanza_error{reason = 'forbidden'} = publish_item(Config, Node),
+    #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID),
+    #stanza_error{reason = 'forbidden'} = purge_node(Config, Node),
+    #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_node_config(Config, Node, default_node_config(Config)),
+    #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]),
+    #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_affiliations(Config, Node, [{?config(master, Config), outcast},
+                                       {my_jid(Config), owner}]),
+    #stanza_error{reason = 'forbidden'} = delete_node(Config, Node),
+    wait_for_master(Config),
+    affiliations_slave(Config, get_event(Config));
+affiliations_slave(Config, {none, Node, ItemID}) ->
+    #ps_subscription{type = subscribed} = subscribe_node(Config, Node),
+    ok = unsubscribe_node(Config, Node),
+    %% This violates the affiliation char from section 4.1
+    [_|_] = get_items(Config, Node),
+    #stanza_error{reason = 'forbidden'} = publish_item(Config, Node),
+    #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID),
+    #stanza_error{reason = 'forbidden'} = purge_node(Config, Node),
+    #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_node_config(Config, Node, default_node_config(Config)),
+    #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]),
+    #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_affiliations(Config, Node, [{?config(master, Config), outcast},
+                                       {my_jid(Config), owner}]),
+    #stanza_error{reason = 'forbidden'} = delete_node(Config, Node),
+    wait_for_master(Config),
+    affiliations_slave(Config, get_event(Config));
+affiliations_slave(Config, {member, Node, ItemID}) ->
+    #ps_subscription{type = subscribed} = subscribe_node(Config, Node),
+    ok = unsubscribe_node(Config, Node),
+    [_|_] = get_items(Config, Node),
+    #stanza_error{reason = 'forbidden'} = publish_item(Config, Node),
+    #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID),
+    #stanza_error{reason = 'forbidden'} = purge_node(Config, Node),
+    #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_node_config(Config, Node, default_node_config(Config)),
+    #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]),
+    #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_affiliations(Config, Node, [{?config(master, Config), outcast},
+                                       {my_jid(Config), owner}]),
+    #stanza_error{reason = 'forbidden'} = delete_node(Config, Node),
+    wait_for_master(Config),
+    affiliations_slave(Config, get_event(Config));
+affiliations_slave(Config, {publish_only, Node, ItemID}) ->
+    #stanza_error{reason = 'forbidden'} = subscribe_node(Config, Node),
+    #stanza_error{} = unsubscribe_node(Config, Node),
+    #stanza_error{reason = 'forbidden'} = get_items(Config, Node),
+    #ps_item{id = _MyItemID} = publish_item(Config, Node),
+    %% BUG: This should be fixed
+    %% ?match(ok, delete_item(Config, Node, MyItemID)),
+    #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID),
+    #stanza_error{reason = 'forbidden'} = purge_node(Config, Node),
+    #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_node_config(Config, Node, default_node_config(Config)),
+    #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]),
+    #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_affiliations(Config, Node, [{?config(master, Config), outcast},
+                                       {my_jid(Config), owner}]),
+    #stanza_error{reason = 'forbidden'} = delete_node(Config, Node),
+    wait_for_master(Config),
+    affiliations_slave(Config, get_event(Config));
+affiliations_slave(Config, {publisher, Node, _ItemID}) ->
+    #ps_subscription{type = subscribed} = subscribe_node(Config, Node),
+    ok = unsubscribe_node(Config, Node),
+    [_|_] = get_items(Config, Node),
+    #ps_item{id = MyItemID} = publish_item(Config, Node),
+    ok = delete_item(Config, Node, MyItemID),
+    %% BUG: this should be fixed
+    %% #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID),
+    #stanza_error{reason = 'forbidden'} = purge_node(Config, Node),
+    #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_node_config(Config, Node, default_node_config(Config)),
+    #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]),
+    #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node),
+    #stanza_error{reason = 'forbidden'} =
+       set_affiliations(Config, Node, [{?config(master, Config), outcast},
+                                       {my_jid(Config), owner}]),
+    #stanza_error{reason = 'forbidden'} = delete_node(Config, Node),
+    wait_for_master(Config),
+    affiliations_slave(Config, get_event(Config));
+affiliations_slave(Config, {owner, Node, ItemID}) ->
+    MyJID = my_jid(Config),
+    Peer = ?config(master, Config),
+    #ps_subscription{type = subscribed} = subscribe_node(Config, Node),
+    ok = unsubscribe_node(Config, Node),
+    [_|_] = get_items(Config, Node),
+    #ps_item{id = MyItemID} = publish_item(Config, Node),
+    ok = delete_item(Config, Node, MyItemID),
+    ok = delete_item(Config, Node, ItemID),
+    ok = purge_node(Config, Node),
+    [_|_] = get_node_config(Config, Node),
+    ok = set_node_config(Config, Node, default_node_config(Config)),
+    ok = set_subscriptions(Config, Node, []),
+    [] = get_subscriptions(Config, Node),
+    ok = set_affiliations(Config, Node, [{Peer, outcast}, {MyJID, owner}]),
+    [_, _] = get_affiliations(Config, Node),
+    ok = delete_node(Config, Node),
+    wait_for_master(Config),
+    affiliations_slave(Config, get_event(Config));
+affiliations_slave(Config, disconnect) ->
+    disconnect(Config).
+
+authorize_master(Config) ->
+    send(Config, #presence{}),
+    #presence{} = recv_presence(Config),
+    Peer = ?config(slave, Config),
+    PJID = pubsub_jid(Config),
+    NodeConfig = set_opts(default_node_config(Config),
+                         [{access_model, authorize}]),
+    Node = ?config(pubsub_node, Config),
+    Node = create_node(Config, Node, NodeConfig),
+    wait_for_slave(Config),
+    #message{sub_els = [#xdata{fields = F1}]} = recv_message(Config),
+    C1 = pubsub_subscribe_authorization:decode(F1),
+    Node = proplists:get_value(node, C1),
+    Peer = proplists:get_value(subscriber_jid, C1),
+    %% Deny it at first
+    Deny = #xdata{type = submit,
+                 fields = pubsub_subscribe_authorization:encode(
+                            [{node, Node},
+                             {subscriber_jid, Peer},
+                             {allow, false}])},
+    send(Config, #message{to = PJID, sub_els = [Deny]}),
+    %% We should not have any subscriptions
+    [] = get_subscriptions(Config, Node),
+    wait_for_slave(Config),
+    #message{sub_els = [#xdata{fields = F2}]} = recv_message(Config),
+    C2 = pubsub_subscribe_authorization:decode(F2),
+    Node = proplists:get_value(node, C2),
+    Peer = proplists:get_value(subscriber_jid, C2),
+    %% Now we accept is as the peer is very insisting ;)
+    Approve = #xdata{type = submit,
+                    fields = pubsub_subscribe_authorization:encode(
+                               [{node, Node},
+                                {subscriber_jid, Peer},
+                                {allow, true}])},
+    send(Config, #message{to = PJID, sub_els = [Approve]}),
+    wait_for_slave(Config),
+    delete_node(Config, Node),
+    disconnect(Config).
+
+authorize_slave(Config) ->
+    Node = ?config(pubsub_node, Config),
+    MyJID = my_jid(Config),
+    wait_for_master(Config),
+    #ps_subscription{type = pending} = subscribe_node(Config, Node),
+    %% We're denied at first
+    #message{
+       sub_els =
+          [#ps_event{
+              subscription = #ps_subscription{type = none,
+                                              jid = MyJID}}]} =
+       recv_message(Config),
+    wait_for_master(Config),
+    #ps_subscription{type = pending} = subscribe_node(Config, Node),
+    %% Now much better!
+    #message{
+       sub_els =
+          [#ps_event{
+              subscription = #ps_subscription{type = subscribed,
+                                              jid = MyJID}}]} =
+       recv_message(Config),
+    wait_for_master(Config),
+    disconnect(Config).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+single_test(T) ->
+    list_to_atom("pubsub_" ++ atom_to_list(T)).
+
+master_slave_test(T) ->
+    {list_to_atom("pubsub_" ++ atom_to_list(T)), [parallel],
+     [list_to_atom("pubsub_" ++ atom_to_list(T) ++ "_master"),
+      list_to_atom("pubsub_" ++ atom_to_list(T) ++ "_slave")]}.
+
+set_opts(Config, Options) ->
+    lists:foldl(
+      fun({Opt, Val}, Acc) ->
+             lists:keystore(Opt, 1, Acc, {Opt, Val})
+      end, Config, Options).
+
+create_node(Config, Node) ->
+    create_node(Config, Node, undefined).
+
+create_node(Config, Node, Options) ->
+    PJID = pubsub_jid(Config),
+    NodeConfig = if is_list(Options) ->
+                        #xdata{type = submit,
+                               fields = pubsub_node_config:encode(Options)};
+                   true ->
+                        undefined
+                end,
+    case send_recv(Config,
+                  #iq{type = set, to = PJID,
+                      sub_els = [#pubsub{create = Node,
+                                         configure = {<<>>, NodeConfig}}]}) of
+       #iq{type = result, sub_els = [#pubsub{create = NewNode}]} ->
+           NewNode;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+delete_node(Config, Node) ->
+    PJID = pubsub_jid(Config),
+    case send_recv(Config,
+                  #iq{type = set, to = PJID,
+                      sub_els = [#pubsub_owner{delete = {Node, <<>>}}]}) of
+       #iq{type = result, sub_els = []} ->
+           ok;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+purge_node(Config, Node) ->
+    PJID = pubsub_jid(Config),
+    case send_recv(Config,
+                  #iq{type = set, to = PJID,
+                      sub_els = [#pubsub_owner{purge = Node}]}) of
+       #iq{type = result, sub_els = []} ->
+           ok;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+get_default_node_config(Config) ->
+    PJID = pubsub_jid(Config),
+    case send_recv(Config,
+                  #iq{type = get, to = PJID,
+                      sub_els = [#pubsub_owner{default = {<<>>, undefined}}]}) of
+       #iq{type = result,
+           sub_els = [#pubsub_owner{default = {<<>>, NodeConfig}}]} ->
+           pubsub_node_config:decode(NodeConfig#xdata.fields);
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+get_node_config(Config, Node) ->
+    PJID = pubsub_jid(Config),
+    case send_recv(Config,
+                  #iq{type = get, to = PJID,
+                      sub_els = [#pubsub_owner{configure = {Node, undefined}}]}) of
+       #iq{type = result,
+           sub_els = [#pubsub_owner{configure = {Node, NodeConfig}}]} ->
+           pubsub_node_config:decode(NodeConfig#xdata.fields);
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+set_node_config(Config, Node, Options) ->
+    PJID = pubsub_jid(Config),
+    NodeConfig = #xdata{type = submit,
+                       fields = pubsub_node_config:encode(Options)},
+    case send_recv(Config,
+                  #iq{type = set, to = PJID,
+                      sub_els = [#pubsub_owner{configure =
+                                                   {Node, NodeConfig}}]}) of
+       #iq{type = result, sub_els = []} ->
+           ok;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+publish_item(Config, Node) ->
+    PJID = pubsub_jid(Config),
+    ItemID = randoms:get_string(),
+    Item = #ps_item{id = ItemID, xml_els = [xmpp:encode(#presence{id = ItemID})]},
+    case send_recv(Config,
+                  #iq{type = set, to = PJID,
+                      sub_els = [#pubsub{publish = #ps_publish{
+                                                      node = Node,
+                                                      items = [Item]}}]}) of
+       #iq{type = result,
+           sub_els = [#pubsub{publish = #ps_publish{
+                                           node = Node,
+                                           items = [#ps_item{id = ItemID}]}}]} ->
+           Item;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+get_items(Config, Node) ->
+    PJID = pubsub_jid(Config),
+    case send_recv(Config,
+                  #iq{type = get, to = PJID,
+                      sub_els = [#pubsub{items = #ps_items{node = Node}}]}) of
+       #iq{type = result,
+           sub_els = [#pubsub{items = #ps_items{node = Node, items = Items}}]} ->
+           Items;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+delete_item(Config, Node, I) ->
+    PJID = pubsub_jid(Config),
+    case send_recv(Config,
+                  #iq{type = set, to = PJID,
+                      sub_els = [#pubsub{retract =
+                                             #ps_retract{
+                                                node = Node,
+                                                items = [#ps_item{id = I}]}}]}) of
+       #iq{type = result, sub_els = []} ->
+           ok;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+subscribe_node(Config, Node) ->
+    PJID = pubsub_jid(Config),
+    MyJID = my_jid(Config),
+    case send_recv(Config,
+                  #iq{type = set, to = PJID,
+                      sub_els = [#pubsub{subscribe = #ps_subscribe{
+                                                        node = Node,
+                                                        jid = MyJID}}]}) of
+       #iq{type = result,
+           sub_els = [#pubsub{
+                         subscription = #ps_subscription{
+                                           node = Node,
+                                           jid = MyJID} = Sub}]} ->
+           Sub;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+unsubscribe_node(Config, Node) ->
+    PJID = pubsub_jid(Config),
+    MyJID = my_jid(Config),
+    case send_recv(Config,
+                  #iq{type = set, to = PJID,
+                      sub_els = [#pubsub{
+                                    unsubscribe = #ps_unsubscribe{
+                                                     node = Node,
+                                                     jid = MyJID}}]}) of
+       #iq{type = result, sub_els = []} ->
+           ok;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+get_affiliations(Config) ->
+    PJID = pubsub_jid(Config),
+    case send_recv(Config,
+                  #iq{type = get, to = PJID,
+                      sub_els = [#pubsub{affiliations = {<<>>, []}}]}) of
+       #iq{type = result,
+           sub_els = [#pubsub{affiliations = {<<>>, Affs}}]} ->
+           Affs;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+get_affiliations(Config, Node) ->
+    PJID = pubsub_jid(Config),
+    case send_recv(Config,
+                  #iq{type = get, to = PJID,
+                      sub_els = [#pubsub_owner{affiliations = {Node, []}}]}) of
+       #iq{type = result,
+           sub_els = [#pubsub_owner{affiliations = {Node, Affs}}]} ->
+           Affs;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+set_affiliations(Config, Node, JTs) ->
+    PJID = pubsub_jid(Config),
+    Affs = [#ps_affiliation{jid = J, type = T} || {J, T} <- JTs],
+    case send_recv(Config,
+                  #iq{type = set, to = PJID,
+                      sub_els = [#pubsub_owner{affiliations =
+                                                   {Node, Affs}}]}) of
+       #iq{type = result, sub_els = []} ->
+           ok;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+get_subscriptions(Config) ->
+    PJID = pubsub_jid(Config),
+    case send_recv(Config,
+                  #iq{type = get, to = PJID,
+                      sub_els = [#pubsub{subscriptions = {<<>>, []}}]}) of
+       #iq{type = result, sub_els = [#pubsub{subscriptions = {<<>>, Subs}}]} ->
+           Subs;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+get_subscriptions(Config, Node) ->
+    PJID = pubsub_jid(Config),
+    case send_recv(Config,
+                  #iq{type = get, to = PJID,
+                      sub_els = [#pubsub_owner{subscriptions = {Node, []}}]}) of
+       #iq{type = result,
+           sub_els = [#pubsub_owner{subscriptions = {Node, Subs}}]} ->
+           Subs;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+set_subscriptions(Config, Node, JTs) ->
+    PJID = pubsub_jid(Config),
+    Subs = [#ps_subscription{jid = J, type = T} || {J, T} <- JTs],
+    case send_recv(Config,
+                  #iq{type = set, to = PJID,
+                      sub_els = [#pubsub_owner{subscriptions =
+                                                   {Node, Subs}}]}) of
+       #iq{type = result, sub_els = []} ->
+           ok;
+       #iq{type = error} = IQ ->
+           xmpp:get_subtag(IQ, #stanza_error{})
+    end.
+
+default_node_config(Config) ->
+    [{title, ?config(pubsub_node_title, Config)},
+     {notify_delete, false},
+     {send_last_published_item, never}].
diff --git a/test/replaced_tests.erl b/test/replaced_tests.erl
new file mode 100644 (file)
index 0000000..e50c27f
--- /dev/null
@@ -0,0 +1,57 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 16 Nov 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(replaced_tests).
+
+%% API
+-compile(export_all).
+-import(suite, [bind/1, wait_for_slave/1, wait_for_master/1, recv/1,
+               close_socket/1, disconnect/1]).
+
+-include("suite.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+%%%===================================================================
+%%% Single user tests
+%%%===================================================================
+single_cases() ->
+    {replaced_single, [sequence], []}.
+
+%%%===================================================================
+%%% Master-slave tests
+%%%===================================================================
+master_slave_cases() ->
+    {replaced_master_slave, [sequence], []}.
+%% Disable tests for now due to a race condition
+%% because ejabberd_sm:sid() is generated in ejabberd_s2s:init()
+%%[master_slave_test(conflict)]}.
+
+conflict_master(Config0) ->
+    Config = bind(Config0),
+    wait_for_slave(Config),
+    #stream_error{reason = conflict} = recv(Config),
+    {xmlstreamend, <<"stream:stream">>} = recv(Config),
+    close_socket(Config).
+
+conflict_slave(Config0) ->
+    wait_for_master(Config0),
+    Config = bind(Config0),
+    disconnect(Config).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+single_test(T) ->
+    list_to_atom("replaced_" ++ atom_to_list(T)).
+
+master_slave_test(T) ->
+    {list_to_atom("replaced_" ++ atom_to_list(T)), [parallel],
+     [list_to_atom("replaced_" ++ atom_to_list(T) ++ "_master"),
+      list_to_atom("replaced_" ++ atom_to_list(T) ++ "_slave")]}.
index 2d05709ab6c3b88e99f0edd9e42d00aac26924c3..4aa06b95302053438d2516f68be8b6bc71e5e285 100644 (file)
@@ -13,8 +13,7 @@
 -import(suite, [send_recv/2, recv_iq/1, send/2, disconnect/1, del_roster/1,
                del_roster/2, make_iq_result/1, wait_for_slave/1,
                wait_for_master/1, recv_presence/1, self_presence/2,
-               put_event/2, get_event/1, match_failure/2, get_roster/1,
-               is_feature_advertised/2]).
+               put_event/2, get_event/1, match_failure/2, get_roster/1]).
 -include("suite.hrl").
 -include("mod_roster.hrl").
 
@@ -132,7 +131,7 @@ version(Config) ->
 %%% Master-slave tests
 %%%===================================================================
 master_slave_cases() ->
-    {roster_master_slave, [parallel],
+    {roster_master_slave, [sequence],
      [master_slave_test(subscribe)]}.
 
 subscribe_master(Config) ->
@@ -149,6 +148,7 @@ process_subscriptions_master(Config, Actions) ->
     self_presence(Config, available),
     lists:foldl(
       fun({N, {Dir, Type}}, State) ->
+             timer:sleep(100),
              if Dir == out -> put_event(Config, {N, in, Type});
                 Dir == in -> put_event(Config, {N, out, Type})
              end,
diff --git a/test/sm_tests.erl b/test/sm_tests.erl
new file mode 100644 (file)
index 0000000..0a74d39
--- /dev/null
@@ -0,0 +1,99 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 16 Nov 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(sm_tests).
+
+%% API
+-compile(export_all).
+-import(suite, [send/2, recv/1, close_socket/1, set_opt/3, my_jid/1,
+               recv_message/1, disconnect/1]).
+
+-include("suite.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+%%%===================================================================
+%%% Single user tests
+%%%===================================================================
+single_cases() ->
+    {sm_single, [sequence],
+     [single_test(feature_enabled),
+      single_test(enable),
+      single_test(resume),
+      single_test(resume_failed)]}.
+
+feature_enabled(Config) ->
+    true = ?config(sm, Config),
+    disconnect(Config).
+
+enable(Config) ->
+    Server = ?config(server, Config),
+    ServerJID = jid:make(<<"">>, Server, <<"">>),
+    %% Send messages of type 'headline' so the server discards them silently
+    Msg = #message{to = ServerJID, type = headline,
+                  body = [#text{data = <<"body">>}]},
+    %% Enable the session management with resumption enabled
+    send(Config, #sm_enable{resume = true, xmlns = ?NS_STREAM_MGMT_3}),
+    #sm_enabled{id = ID, resume = true} = recv(Config),
+    %% Initial request; 'h' should be 0.
+    send(Config, #sm_r{xmlns = ?NS_STREAM_MGMT_3}),
+    #sm_a{h = 0} = recv(Config),
+    %% sending two messages and requesting again; 'h' should be 3.
+    send(Config, Msg),
+    send(Config, Msg),
+    send(Config, Msg),
+    send(Config, #sm_r{xmlns = ?NS_STREAM_MGMT_3}),
+    #sm_a{h = 3} = recv(Config),
+    close_socket(Config),
+    {save_config, set_opt(sm_previd, ID, Config)}.
+
+resume(Config) ->
+    {_, SMConfig} = ?config(saved_config, Config),
+    ID = ?config(sm_previd, SMConfig),
+    Server = ?config(server, Config),
+    ServerJID = jid:make(<<"">>, Server, <<"">>),
+    MyJID = my_jid(Config),
+    Txt = #text{data = <<"body">>},
+    Msg = #message{from = ServerJID, to = MyJID, body = [Txt]},
+    %% Route message. The message should be queued by the C2S process.
+    ejabberd_router:route(ServerJID, MyJID, Msg),
+    send(Config, #sm_resume{previd = ID, h = 0, xmlns = ?NS_STREAM_MGMT_3}),
+    #sm_resumed{previd = ID, h = 3} = recv(Config),
+    #message{from = ServerJID, to = MyJID, body = [Txt]} = recv_message(Config),
+    #sm_r{} = recv(Config),
+    send(Config, #sm_a{h = 1, xmlns = ?NS_STREAM_MGMT_3}),
+    %% Send another stanza to increment the server's 'h' for sm_resume_failed.
+    send(Config, #presence{to = ServerJID}),
+    close_socket(Config),
+    {save_config, set_opt(sm_previd, ID, Config)}.
+
+resume_failed(Config) ->
+    {_, SMConfig} = ?config(saved_config, Config),
+    ID = ?config(sm_previd, SMConfig),
+    ct:sleep(5000), % Wait for session to time out.
+    send(Config, #sm_resume{previd = ID, h = 1, xmlns = ?NS_STREAM_MGMT_3}),
+    #sm_failed{reason = 'item-not-found', h = 4} = recv(Config),
+    disconnect(Config).
+
+%%%===================================================================
+%%% Master-slave tests
+%%%===================================================================
+master_slave_cases() ->
+    {sm_master_slave, [sequence], []}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+single_test(T) ->
+    list_to_atom("sm_" ++ atom_to_list(T)).
+
+master_slave_test(T) ->
+    {list_to_atom("sm_" ++ atom_to_list(T)), [parallel],
+     [list_to_atom("sm_" ++ atom_to_list(T) ++ "_master"),
+      list_to_atom("sm_" ++ atom_to_list(T) ++ "_slave")]}.
index 52c030df117f7d7ddce09d30f3d165ca3bb7f845..a288a5f663a565492fdf1c661635d0100f1be251 100644 (file)
@@ -488,8 +488,9 @@ decode(El, NS, Opts) ->
               [format_element(El), xmpp:pp(Pkt)]),
        Pkt
     catch _:{xmpp_codec, Why} ->
-           ct:fail("recv failed: ~p->~n~s",
-                   [El, xmpp:format_error(Why)])
+           ct:pal("recv failed: ~p->~n~s",
+                  [El, xmpp:format_error(Why)]),
+           erlang:error({xmpp_codec, Why})
     end.
 
 send_text(Config, Text) ->
diff --git a/test/vcard_tests.erl b/test/vcard_tests.erl
new file mode 100644 (file)
index 0000000..bb3efb4
--- /dev/null
@@ -0,0 +1,125 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 16 Nov 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(vcard_tests).
+
+%% API
+-compile(export_all).
+-import(suite, [send_recv/2, disconnect/1, is_feature_advertised/2,
+               is_feature_advertised/3,
+               my_jid/1, wait_for_slave/1, wait_for_master/1,
+               recv_presence/1, recv/1]).
+
+-include("suite.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+%%%===================================================================
+%%% Single user tests
+%%%===================================================================
+single_cases() ->
+    {vcard_single, [sequence],
+     [single_test(feature_enabled),
+      single_test(get_set)]}.
+
+feature_enabled(Config) ->
+    BareMyJID = jid:remove_resource(my_jid(Config)),
+    true = is_feature_advertised(Config, ?NS_VCARD),
+    true = is_feature_advertised(Config, ?NS_VCARD, BareMyJID),
+    disconnect(Config).
+
+get_set(Config) ->
+    VCard =
+        #vcard_temp{fn = <<"Peter Saint-Andre">>,
+                   n = #vcard_name{family = <<"Saint-Andre">>,
+                                   given = <<"Peter">>},
+                   nickname = <<"stpeter">>,
+                   bday = <<"1966-08-06">>,
+                   adr = [#vcard_adr{work = true,
+                                     extadd = <<"Suite 600">>,
+                                     street = <<"1899 Wynkoop Street">>,
+                                     locality = <<"Denver">>,
+                                     region = <<"CO">>,
+                                     pcode = <<"80202">>,
+                                     ctry = <<"USA">>},
+                          #vcard_adr{home = true,
+                                     locality = <<"Denver">>,
+                                     region = <<"CO">>,
+                                     pcode = <<"80209">>,
+                                     ctry = <<"USA">>}],
+                   tel = [#vcard_tel{work = true,voice = true,
+                                     number = <<"303-308-3282">>},
+                          #vcard_tel{home = true,voice = true,
+                                     number = <<"303-555-1212">>}],
+                   email = [#vcard_email{internet = true,pref = true,
+                                         userid = <<"stpeter@jabber.org">>}],
+                   jabberid = <<"stpeter@jabber.org">>,
+                   title = <<"Executive Director">>,role = <<"Patron Saint">>,
+                   org = #vcard_org{name = <<"XMPP Standards Foundation">>},
+                   url = <<"http://www.xmpp.org/xsf/people/stpeter.shtml">>,
+                   desc = <<"More information about me is located on my "
+                            "personal website: http://www.saint-andre.com/">>},
+    #iq{type = result, sub_els = []} =
+        send_recv(Config, #iq{type = set, sub_els = [VCard]}),
+    %% TODO: check if VCard == VCard1.
+    #iq{type = result, sub_els = [_VCard1]} =
+        send_recv(Config, #iq{type = get, sub_els = [#vcard_temp{}]}),
+    disconnect(Config).
+
+%%%===================================================================
+%%% Master-slave tests
+%%%===================================================================
+master_slave_cases() ->
+    {vcard_master_slave, [sequence], []}.
+   %%[master_slave_test(xupdate)]}.
+
+xupdate_master(Config) ->
+    Img = <<137, "PNG\r\n", 26, $\n>>,
+    ImgHash = p1_sha:sha(Img),
+    MyJID = my_jid(Config),
+    Peer = ?config(slave, Config),
+    wait_for_slave(Config),
+    #presence{from = MyJID, type = available} = send_recv(Config, #presence{}),
+    #presence{from = Peer, type = available} = recv_presence(Config),
+    VCard = #vcard_temp{photo = #vcard_photo{type = <<"image/png">>, binval = Img}},
+    #iq{type = result, sub_els = []} =
+       send_recv(Config, #iq{type = set, sub_els = [VCard]}),
+    #presence{from = MyJID, type = available,
+             sub_els = [#vcard_xupdate{hash = ImgHash}]} = recv_presence(Config),
+    #iq{type = result, sub_els = []} =
+       send_recv(Config, #iq{type = set, sub_els = [#vcard_temp{}]}),
+    ?recv2(#presence{from = MyJID, type = available,
+                    sub_els = [#vcard_xupdate{hash = undefined}]},
+          #presence{from = Peer, type = unavailable}),
+    disconnect(Config).
+
+xupdate_slave(Config) ->
+    Img = <<137, "PNG\r\n", 26, $\n>>,
+    ImgHash = p1_sha:sha(Img),
+    MyJID = my_jid(Config),
+    Peer = ?config(master, Config),
+    #presence{from = MyJID, type = available} = send_recv(Config, #presence{}),
+    wait_for_master(Config),
+    #presence{from = Peer, type = available} = recv_presence(Config),
+    #presence{from = Peer, type = available,
+             sub_els = [#vcard_xupdate{hash = ImgHash}]} = recv_presence(Config),
+    #presence{from = Peer, type = available,
+             sub_els = [#vcard_xupdate{hash = undefined}]} = recv_presence(Config),
+    disconnect(Config).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+single_test(T) ->
+    list_to_atom("vcard_" ++ atom_to_list(T)).
+
+master_slave_test(T) ->
+    {list_to_atom("vcard_" ++ atom_to_list(T)), [parallel],
+     [list_to_atom("vcard_" ++ atom_to_list(T) ++ "_master"),
+      list_to_atom("vcard_" ++ atom_to_list(T) ++ "_slave")]}.