From b8dcc911a3b1e3cc05074d9ac4bd8da80b431388 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Fri, 18 Nov 2016 13:38:08 +0300 Subject: [PATCH] Make common tests working again --- include/mam_query.hrl | 13 + specs/mam_query.cfg | 9 + specs/mam_query.xdata | 23 + src/ejabberd_local.erl | 12 +- src/flex_offline.erl | 2 +- src/mam_query.erl | 220 +++ src/mod_carboncopy.erl | 4 +- src/mod_mam.erl | 201 ++- src/mod_mam_mnesia.erl | 8 +- src/mod_mam_sql.erl | 18 +- src/mod_offline.erl | 3 +- src/mod_offline_riak.erl | 2 +- src/mod_offline_sql.erl | 3 +- src/mod_roster.erl | 2 +- src/muc_register.erl | 2 +- src/muc_request.erl | 2 +- src/muc_roomconfig.erl | 2 +- src/muc_roominfo.erl | 2 +- src/pubsub_get_pending.erl | 2 +- src/pubsub_node_config.erl | 2 +- src/pubsub_publish_options.erl | 2 +- src/pubsub_subscribe_authorization.erl | 2 +- src/pubsub_subscribe_options.erl | 2 +- src/xmpp_util.erl | 9 +- test/announce_tests.erl | 61 + test/carbons_tests.erl | 202 +++ test/csi_tests.erl | 147 ++ test/ejabberd_SUITE.erl | 2059 +----------------------- test/example_tests.erl | 52 + test/mam_tests.erl | 537 ++++++ test/mix_tests.erl | 139 ++ test/mod_legacy.erl | 8 +- test/muc_tests.erl | 750 ++++----- test/privacy_tests.erl | 7 +- test/proxy65_tests.erl | 105 ++ test/pubsub_tests.erl | 729 +++++++++ test/replaced_tests.erl | 57 + test/roster_tests.erl | 6 +- test/sm_tests.erl | 99 ++ test/suite.erl | 5 +- test/vcard_tests.erl | 125 ++ 41 files changed, 3116 insertions(+), 2519 deletions(-) create mode 100644 include/mam_query.hrl create mode 100644 specs/mam_query.cfg create mode 100644 specs/mam_query.xdata create mode 100644 src/mam_query.erl create mode 100644 test/announce_tests.erl create mode 100644 test/carbons_tests.erl create mode 100644 test/csi_tests.erl create mode 100644 test/example_tests.erl create mode 100644 test/mam_tests.erl create mode 100644 test/mix_tests.erl create mode 100644 test/proxy65_tests.erl create mode 100644 test/pubsub_tests.erl create mode 100644 test/replaced_tests.erl create mode 100644 test/sm_tests.erl create mode 100644 test/vcard_tests.erl diff --git a/include/mam_query.hrl b/include/mam_query.hrl new file mode 100644 index 000000000..4ec48c05e --- /dev/null +++ b/include/mam_query.hrl @@ -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 index 000000000..a8921a52b --- /dev/null +++ b/specs/mam_query.cfg @@ -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 index 000000000..58fad41cd --- /dev/null +++ b/specs/mam_query.xdata @@ -0,0 +1,23 @@ + + urn:xmpp:mam:1 + XEP-0313 + Form to query message archives + + + + + + + diff --git a/src/ejabberd_local.erl b/src/ejabberd_local.erl index d7849396b..74d86945d 100644 --- a/src/ejabberd_local.erl +++ b/src/ejabberd_local.erl @@ -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, diff --git a/src/flex_offline.erl b/src/flex_offline.erl index 090ab3ddf..acc57342e 100644 --- a/src/flex_offline.erl +++ b/src/flex_offline.erl @@ -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 index 000000000..cb5bfe13a --- /dev/null +++ b/src/mam_query.erl @@ -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">>)}. diff --git a/src/mod_carboncopy.erl b/src/mod_carboncopy.erl index 7d8ca5332..1c8ca1fdc 100644 --- a/src/mod_carboncopy.erl +++ b/src/mod_carboncopy.erl @@ -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. diff --git a/src/mod_mam.erl b/src/mod_mam.erl index f9ef104bd..61754ae59 100644 --- a/src/mod_mam.erl +++ b/src/mod_mam.erl @@ -54,12 +54,13 @@ -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, - 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 - end - end), + Els = xmpp:get_els(Pkt), 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). + fun(El) -> + 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, 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; - _ -> - false - 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}) -> + 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) - end, xmpp:get_els(NewPkt)), - xmpp:set_els(NewPkt, NewEls). + end, Items); + _ -> + true + end + 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) -> diff --git a/src/mod_mam_mnesia.erl b/src/mod_mam_mnesia.erl index e913d5a45..8b9c6676c 100644 --- a/src/mod_mam_mnesia.erl +++ b/src/mod_mam_mnesia.erl @@ -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, diff --git a/src/mod_mam_sql.erl b/src/mod_mam_sql.erl index 1491f70f2..c500745a3 100644 --- a/src/mod_mam_sql.erl +++ b/src/mod_mam_sql.erl @@ -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}, diff --git a/src/mod_offline.erl b/src/mod_offline.erl index 2f6d52c36..d007bf3c6 100644 --- a/src/mod_offline.erl +++ b/src/mod_offline.erl @@ -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()}. diff --git a/src/mod_offline_riak.erl b/src/mod_offline_riak.erl index 241a8d650..24d565383 100644 --- a/src/mod_offline_riak.erl +++ b/src/mod_offline_riak.erl @@ -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 -> diff --git a/src/mod_offline_sql.erl b/src/mod_offline_sql.erl index 2b7a40bff..025aa56f5 100644 --- a/src/mod_offline_sql.erl +++ b/src/mod_offline_sql.erl @@ -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 diff --git a/src/mod_roster.erl b/src/mod_roster.erl index c344213f3..2da09d317 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -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) -> diff --git a/src/muc_register.erl b/src/muc_register.erl index cddce2b98..c2b951dfc 100644 --- a/src/muc_register.erl +++ b/src/muc_register.erl @@ -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; diff --git a/src/muc_request.erl b/src/muc_request.erl index 4c7becd2e..2d79ba0a5 100644 --- a/src/muc_request.erl +++ b/src/muc_request.erl @@ -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), diff --git a/src/muc_roomconfig.erl b/src/muc_roomconfig.erl index 73ceb649e..7d18bab66 100644 --- a/src/muc_roomconfig.erl +++ b/src/muc_roomconfig.erl @@ -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 diff --git a/src/muc_roominfo.erl b/src/muc_roominfo.erl index 809dcef5b..bd5cb011b 100644 --- a/src/muc_roominfo.erl +++ b/src/muc_roominfo.erl @@ -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 diff --git a/src/pubsub_get_pending.erl b/src/pubsub_get_pending.erl index 1a7de6a2d..c1f2ba3ad 100644 --- a/src/pubsub_get_pending.erl +++ b/src/pubsub_get_pending.erl @@ -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, "'">>; diff --git a/src/pubsub_node_config.erl b/src/pubsub_node_config.erl index 47ed10b49..e831d6a83 100644 --- a/src/pubsub_node_config.erl +++ b/src/pubsub_node_config.erl @@ -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 diff --git a/src/pubsub_publish_options.erl b/src/pubsub_publish_options.erl index 8d0229071..6e96946fd 100644 --- a/src/pubsub_publish_options.erl +++ b/src/pubsub_publish_options.erl @@ -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), diff --git a/src/pubsub_subscribe_authorization.erl b/src/pubsub_subscribe_authorization.erl index e019ed6b9..46538da8d 100644 --- a/src/pubsub_subscribe_authorization.erl +++ b/src/pubsub_subscribe_authorization.erl @@ -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; diff --git a/src/pubsub_subscribe_options.erl b/src/pubsub_subscribe_options.erl index 446a84a00..02c046995 100644 --- a/src/pubsub_subscribe_options.erl +++ b/src/pubsub_subscribe_options.erl @@ -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), diff --git a/src/xmpp_util.erl b/src/xmpp_util.erl index 57440b50e..22b8ea597 100644 --- a/src/xmpp_util.erl +++ b/src/xmpp_util.erl @@ -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 index 000000000..3eea5298c --- /dev/null +++ b/test/announce_tests.erl @@ -0,0 +1,61 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 16 Nov 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-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 index 000000000..2780dab66 --- /dev/null +++ b/test/carbons_tests.erl @@ -0,0 +1,202 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 16 Nov 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-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 index 000000000..9a96b8a59 --- /dev/null +++ b/test/csi_tests.erl @@ -0,0 +1,147 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 16 Nov 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-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{}]}). diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl index 121719cdf..46711ad49 100644 --- a/test/ejabberd_SUITE.erl +++ b/test/ejabberd_SUITE.erl @@ -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 = <>, - InitAck = <>, - Req = <>, - Resp = <>, - 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 index 000000000..d7965376e --- /dev/null +++ b/test/example_tests.erl @@ -0,0 +1,52 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 16 Nov 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-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 index 000000000..9154ea240 --- /dev/null +++ b/test/mam_tests.erl @@ -0,0 +1,537 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 14 Nov 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-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 index 000000000..56b1b35d7 --- /dev/null +++ b/test/mix_tests.erl @@ -0,0 +1,139 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 16 Nov 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-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")]}. diff --git a/test/mod_legacy.erl b/test/mod_legacy.erl index 6024acfeb..dba977554 100644 --- a/test/mod_legacy.erl +++ b/test/mod_legacy.erl @@ -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 %%%=================================================================== diff --git a/test/muc_tests.erl b/test/muc_tests.erl index 709a82a22..d8e6dd8fb 100644 --- a/test/muc_tests.erl +++ b/test/muc_tests.erl @@ -11,10 +11,10 @@ %% 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"). @@ -26,21 +26,21 @@ %%%=================================================================== 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]}. + [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)]}. -muc_service_presence_error(Config) -> +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)]}. + [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)]}. -muc_join_conflict_master(Config) -> - ok = muc_join_new(Config), +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 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 = []} -> diff --git a/test/privacy_tests.erl b/test/privacy_tests.erl index 2ee945f1e..640f53d48 100644 --- a/test/privacy_tests.erl +++ b/test/privacy_tests.erl @@ -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 index 000000000..01292f508 --- /dev/null +++ b/test/proxy65_tests.erl @@ -0,0 +1,105 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 16 Nov 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-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 = <>, + InitAck = <>, + Req = <>, + Resp = <>, + 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 index 000000000..fae7234e4 --- /dev/null +++ b/test/pubsub_tests.erl @@ -0,0 +1,729 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 16 Nov 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-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 index 000000000..e50c27f05 --- /dev/null +++ b/test/replaced_tests.erl @@ -0,0 +1,57 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 16 Nov 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-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")]}. diff --git a/test/roster_tests.erl b/test/roster_tests.erl index 2d05709ab..4aa06b953 100644 --- a/test/roster_tests.erl +++ b/test/roster_tests.erl @@ -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 index 000000000..0a74d392a --- /dev/null +++ b/test/sm_tests.erl @@ -0,0 +1,99 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 16 Nov 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-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")]}. diff --git a/test/suite.erl b/test/suite.erl index 52c030df1..a288a5f66 100644 --- a/test/suite.erl +++ b/test/suite.erl @@ -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 index 000000000..bb3efb475 --- /dev/null +++ b/test/vcard_tests.erl @@ -0,0 +1,125 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 16 Nov 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-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")]}.