aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>2016-11-07 10:10:57 +0300
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>2016-11-07 10:10:57 +0300
commit56c91d3c58ec5461600f70d2d0413c846767f882 (patch)
treefae4ac81fe1b3556c059f69deff4209f0591add7 /test
parentUse base64:mime_decode/1 for SASL packets (diff)
Add roster tests
Diffstat (limited to 'test')
-rw-r--r--test/ejabberd_SUITE.erl224
-rw-r--r--test/roster_tests.erl527
-rw-r--r--test/suite.erl29
3 files changed, 578 insertions, 202 deletions
diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl
index 59936352b..a13d801ea 100644
--- a/test/ejabberd_SUITE.erl
+++ b/test/ejabberd_SUITE.erl
@@ -394,7 +394,7 @@ db_tests(riak) ->
auth_md5,
presence_broadcast,
last,
- roster_get,
+ roster_tests:single_cases(),
private,
privacy_tests:single_cases(),
vcard,
@@ -402,9 +402,7 @@ db_tests(riak) ->
test_unregister]},
muc_tests:master_slave_cases(),
privacy_tests:master_slave_cases(),
- {test_roster_subscribe, [parallel],
- [roster_subscribe_master,
- roster_subscribe_slave]},
+ roster_tests:master_slave_cases(),
{test_flex_offline, [sequence],
[flex_offline_master, flex_offline_slave]},
{test_offline, [sequence],
@@ -412,10 +410,7 @@ db_tests(riak) ->
{test_announce, [sequence],
[announce_master, announce_slave]},
{test_vcard_xupdate, [parallel],
- [vcard_xupdate_master, vcard_xupdate_slave]},
- {test_roster_remove, [parallel],
- [roster_remove_master,
- roster_remove_slave]}];
+ [vcard_xupdate_master, vcard_xupdate_slave]}];
db_tests(DB) when DB == mnesia; DB == redis ->
[{single_user, [sequence],
[test_register,
@@ -424,8 +419,7 @@ db_tests(DB) when DB == mnesia; DB == redis ->
auth_md5,
presence_broadcast,
last,
- roster_get,
- roster_ver,
+ roster_tests:single_cases(),
private,
privacy_tests:single_cases(),
vcard,
@@ -435,11 +429,9 @@ db_tests(DB) when DB == mnesia; DB == redis ->
muc_tests:master_slave_cases(),
privacy_tests:master_slave_cases(),
pubsub_multiple_tests(),
+ roster_tests:master_slave_cases(),
{test_mix, [parallel],
[mix_master, mix_slave]},
- {test_roster_subscribe, [parallel],
- [roster_subscribe_master,
- roster_subscribe_slave]},
{test_flex_offline, [sequence],
[flex_offline_master, flex_offline_slave]},
{test_offline, [sequence],
@@ -457,10 +449,7 @@ db_tests(DB) when DB == mnesia; DB == redis ->
{test_announce, [sequence],
[announce_master, announce_slave]},
{test_vcard_xupdate, [parallel],
- [vcard_xupdate_master, vcard_xupdate_slave]},
- {test_roster_remove, [parallel],
- [roster_remove_master,
- roster_remove_slave]}];
+ [vcard_xupdate_master, vcard_xupdate_slave]}];
db_tests(_) ->
%% No support for carboncopy
[{single_user, [sequence],
@@ -470,8 +459,7 @@ db_tests(_) ->
auth_md5,
presence_broadcast,
last,
- roster_get,
- roster_ver,
+ roster_tests:single_cases(),
private,
privacy_tests:single_cases(),
vcard,
@@ -481,11 +469,9 @@ db_tests(_) ->
muc_tests:master_slave_cases(),
privacy_tests:master_slave_cases(),
pubsub_multiple_tests(),
+ roster_tests:master_slave_cases(),
{test_mix, [parallel],
[mix_master, mix_slave]},
- {test_roster_subscribe, [parallel],
- [roster_subscribe_master,
- roster_subscribe_slave]},
{test_flex_offline, [sequence],
[flex_offline_master, flex_offline_slave]},
{test_offline, [sequence],
@@ -499,10 +485,7 @@ db_tests(_) ->
{test_announce, [sequence],
[announce_master, announce_slave]},
{test_vcard_xupdate, [parallel],
- [vcard_xupdate_master, vcard_xupdate_slave]},
- {test_roster_remove, [parallel],
- [roster_remove_master,
- roster_remove_slave]}].
+ [vcard_xupdate_master, vcard_xupdate_slave]}].
ldap_tests() ->
[{ldap_tests, [sequence],
@@ -862,33 +845,26 @@ test_bind(Config) ->
test_open_session(Config) ->
disconnect(open_session(Config, true)).
-roster_get(Config) ->
- #iq{type = result, sub_els = [#roster_query{items = []}]} =
- send_recv(Config, #iq{type = get, sub_els = [#roster_query{}]}),
- disconnect(Config).
-
-roster_ver(Config) ->
- %% Get initial "ver"
- #iq{type = result, sub_els = [#roster_query{ver = Ver1, items = []}]} =
- send_recv(Config, #iq{type = get,
- sub_els = [#roster_query{ver = <<"">>}]}),
- %% Should receive empty IQ-result
- #iq{type = result, sub_els = []} =
- send_recv(Config, #iq{type = get,
- sub_els = [#roster_query{ver = Ver1}]}),
- %% Attempting to subscribe to server's JID
- send(Config, #presence{type = subscribe, to = server_jid(Config)}),
- %% Receive a single roster push with the new "ver"
- #iq{type = set, sub_els = [#roster_query{ver = Ver2}]} = recv_iq(Config),
- %% Requesting roster with the previous "ver". Should receive Ver2 again
- #iq{type = result, sub_els = [#roster_query{ver = Ver2}]} =
- send_recv(Config, #iq{type = get,
- sub_els = [#roster_query{ver = Ver1}]}),
- %% Now requesting roster with the newest "ver". Should receive empty IQ.
- #iq{type = result, sub_els = []} =
- send_recv(Config, #iq{type = get,
- sub_els = [#roster_query{ver = Ver2}]}),
- disconnect(Config).
+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),
@@ -2043,148 +2019,6 @@ mix_slave(Config) ->
disconnect = get_event(Config),
disconnect(Config).
-roster_subscribe_master(Config) ->
- #presence{} = send_recv(Config, #presence{}),
- wait_for_slave(Config),
- Peer = ?config(peer, Config),
- LPeer = jid:remove_resource(Peer),
- send(Config, #presence{type = subscribe, to = LPeer}),
- Push1 = #iq{type = set,
- sub_els = [#roster_query{items = [#roster_item{
- ask = subscribe,
- subscription = none,
- jid = LPeer}]}]} =
- recv_iq(Config),
- send(Config, make_iq_result(Push1)),
- #presence{type = subscribed, from = LPeer} = recv_presence(Config),
- Push2 = #iq{type = set,
- sub_els = [#roster_query{items = [#roster_item{
- subscription = to,
- jid = LPeer}]}]} =
- recv_iq(Config),
- send(Config, make_iq_result(Push2)),
- #presence{type = available, from = Peer} = recv_presence(Config),
- %% BUG: ejabberd sends previous push again. Is it ok?
- Push3 = #iq{type = set,
- sub_els = [#roster_query{items = [#roster_item{
- subscription = to,
- jid = LPeer}]}]} =
- recv_iq(Config),
- send(Config, make_iq_result(Push3)),
- #presence{type = subscribe, from = LPeer} = recv_presence(Config),
- send(Config, #presence{type = subscribed, to = LPeer}),
- Push4 = #iq{type = set,
- sub_els = [#roster_query{items = [#roster_item{
- subscription = both,
- jid = LPeer}]}]} =
- recv_iq(Config),
- send(Config, make_iq_result(Push4)),
- %% Move into a group
- Groups = [<<"A">>, <<"B">>],
- Item = #roster_item{jid = LPeer, groups = Groups},
- #iq{type = result, sub_els = []} =
- send_recv(Config,
- #iq{type = set, sub_els = [#roster_query{items = [Item]}]}),
- Push5 = #iq{type = set,
- sub_els =
- [#roster_query{items = [#roster_item{
- jid = LPeer,
- subscription = both}]}]} =
- recv_iq(Config),
- send(Config, make_iq_result(Push5)),
- #iq{sub_els = [#roster_query{items = [#roster_item{groups = G1}]}]} = Push5,
- Groups = lists:sort(G1),
- wait_for_slave(Config),
- #presence{type = unavailable, from = Peer} = recv_presence(Config),
- disconnect(Config).
-
-roster_subscribe_slave(Config) ->
- #presence{} = send_recv(Config, #presence{}),
- wait_for_master(Config),
- Peer = ?config(master, Config),
- LPeer = jid:remove_resource(Peer),
- #presence{type = subscribe, from = LPeer} = recv_presence(Config),
- send(Config, #presence{type = subscribed, to = LPeer}),
- Push1 = #iq{type = set,
- sub_els = [#roster_query{items = [#roster_item{
- subscription = from,
- jid = LPeer}]}]} =
- recv_iq(Config),
- send(Config, make_iq_result(Push1)),
- send(Config, #presence{type = subscribe, to = LPeer}),
- Push2 = #iq{type = set,
- sub_els = [#roster_query{items = [#roster_item{
- ask = subscribe,
- subscription = from,
- jid = LPeer}]}]} =
- recv_iq(Config),
- send(Config, make_iq_result(Push2)),
- #presence{type = subscribed, from = LPeer} = recv_presence(Config),
- Push3 = #iq{type = set,
- sub_els = [#roster_query{items = [#roster_item{
- subscription = both,
- jid = LPeer}]}]} =
- recv_iq(Config),
- send(Config, make_iq_result(Push3)),
- #presence{type = available, from = Peer} = recv_presence(Config),
- wait_for_master(Config),
- disconnect(Config).
-
-roster_remove_master(Config) ->
- MyJID = my_jid(Config),
- Peer = ?config(slave, Config),
- LPeer = jid:remove_resource(Peer),
- Groups = [<<"A">>, <<"B">>],
- wait_for_slave(Config),
- #presence{from = MyJID, type = available} = send_recv(Config, #presence{}),
- #presence{from = Peer, type = available} = recv_presence(Config),
- %% The peer removed us from its roster.
- {Push1, Push2, _, _, _} =
- ?recv5(
- %% TODO: I guess this can be optimized, we don't need
- %% to send transient roster push with subscription = 'to'.
- #iq{type = set,
- sub_els =
- [#roster_query{items = [#roster_item{
- jid = LPeer,
- subscription = to}]}]},
- #iq{type = set,
- sub_els =
- [#roster_query{items = [#roster_item{
- jid = LPeer,
- subscription = none}]}]},
- #presence{type = unsubscribe, from = LPeer},
- #presence{type = unsubscribed, from = LPeer},
- #presence{type = unavailable, from = Peer}),
- send(Config, make_iq_result(Push1)),
- send(Config, make_iq_result(Push2)),
- #iq{sub_els = [#roster_query{items = [#roster_item{groups = G1}]}]} = Push1,
- #iq{sub_els = [#roster_query{items = [#roster_item{groups = G2}]}]} = Push2,
- Groups = lists:sort(G1), Groups = lists:sort(G2),
- disconnect(Config).
-
-roster_remove_slave(Config) ->
- MyJID = my_jid(Config),
- Peer = ?config(master, Config),
- LPeer = jid:remove_resource(Peer),
- #presence{from = MyJID, type = available} = send_recv(Config, #presence{}),
- wait_for_master(Config),
- #presence{from = Peer, type = available} = recv_presence(Config),
- %% Remove the peer from roster.
- Item = #roster_item{jid = LPeer, subscription = remove},
- #iq{type = result, sub_els = []} =
- send_recv(Config, #iq{type = set,
- sub_els = [#roster_query{items = [Item]}]}),
- Push = #iq{type = set,
- sub_els =
- [#roster_query{items = [#roster_item{
- jid = LPeer,
- subscription = remove}]}]} =
- recv_iq(Config),
- #presence{type = unavailable, from = Peer} = recv_presence(Config),
- send(Config, make_iq_result(Push)),
- disconnect(Config).
-
proxy65_master(Config) ->
Proxy = proxy_jid(Config),
MyJID = my_jid(Config),
diff --git a/test/roster_tests.erl b/test/roster_tests.erl
new file mode 100644
index 000000000..2d05709ab
--- /dev/null
+++ b/test/roster_tests.erl
@@ -0,0 +1,527 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 22 Oct 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(roster_tests).
+
+%% API
+-compile(export_all).
+-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]).
+-include("suite.hrl").
+-include("mod_roster.hrl").
+
+-record(state, {subscription = none :: none | from | to | both,
+ peer_available = false,
+ pending_in = false :: boolean(),
+ pending_out = false :: boolean()}).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+init(_TestCase, Config) ->
+ Config.
+
+stop(_TestCase, Config) ->
+ Config.
+
+%%%===================================================================
+%%% Single user tests
+%%%===================================================================
+single_cases() ->
+ {roster_single, [sequence],
+ [single_test(feature_enabled),
+ single_test(iq_set_many_items),
+ single_test(iq_set_duplicated_groups),
+ single_test(iq_get_item),
+ single_test(iq_unexpected_element),
+ single_test(iq_set_ask),
+ single_test(set_item),
+ single_test(version)]}.
+
+feature_enabled(Config) ->
+ ct:comment("Checking if roster versioning stream feature is set"),
+ true = ?config(rosterver, Config),
+ disconnect(Config).
+
+set_item(Config) ->
+ JID = jid:from_string(<<"nurse@example.com">>),
+ Item = #roster_item{jid = JID},
+ {V1, Item} = set_items(Config, [Item]),
+ {V1, [Item]} = get_items(Config),
+ ItemWithGroups = Item#roster_item{groups = [<<"G1">>, <<"G2">>]},
+ {V2, ItemWithGroups} = set_items(Config, [ItemWithGroups]),
+ {V2, [ItemWithGroups]} = get_items(Config),
+ {V3, Item} = set_items(Config, [Item]),
+ {V3, [Item]} = get_items(Config),
+ ItemWithName = Item#roster_item{name = <<"some name">>},
+ {V4, ItemWithName} = set_items(Config, [ItemWithName]),
+ {V4, [ItemWithName]} = get_items(Config),
+ ItemRemoved = Item#roster_item{subscription = remove},
+ {V5, ItemRemoved} = set_items(Config, [ItemRemoved]),
+ {V5, []} = get_items(Config),
+ del_roster(disconnect(Config), JID).
+
+iq_set_many_items(Config) ->
+ J1 = jid:from_string(<<"nurse1@example.com">>),
+ J2 = jid:from_string(<<"nurse2@example.com">>),
+ ct:comment("Trying to send roster-set with many <item/> elements"),
+ Items = [#roster_item{jid = J1}, #roster_item{jid = J2}],
+ #stanza_error{reason = 'bad-request'} = set_items(Config, Items),
+ disconnect(Config).
+
+iq_set_duplicated_groups(Config) ->
+ JID = jid:from_string(<<"nurse@example.com">>),
+ G = randoms:get_string(),
+ ct:comment("Trying to send roster-set with duplicated groups"),
+ Item = #roster_item{jid = JID, groups = [G, G]},
+ #stanza_error{reason = 'bad-request'} = set_items(Config, [Item]),
+ disconnect(Config).
+
+iq_set_ask(Config) ->
+ JID = jid:from_string(<<"nurse@example.com">>),
+ ct:comment("Trying to send roster-set with 'ask' included"),
+ Item = #roster_item{jid = JID, ask = subscribe},
+ #stanza_error{reason = 'bad-request'} = set_items(Config, [Item]),
+ disconnect(Config).
+
+iq_get_item(Config) ->
+ JID = jid:from_string(<<"nurse@example.com">>),
+ ct:comment("Trying to send roster-get with <item/> element"),
+ #iq{type = error} = Err3 =
+ send_recv(Config, #iq{type = get,
+ sub_els = [#roster_query{
+ items = [#roster_item{jid = JID}]}]}),
+ #stanza_error{reason = 'bad-request'} = xmpp:get_error(Err3),
+ disconnect(Config).
+
+iq_unexpected_element(Config) ->
+ JID = jid:from_string(<<"nurse@example.com">>),
+ ct:comment("Trying to send IQs with unexpected element"),
+ lists:foreach(
+ fun(Type) ->
+ #iq{type = error} = Err4 =
+ send_recv(Config, #iq{type = Type,
+ sub_els = [#roster_item{jid = JID}]}),
+ #stanza_error{reason = 'service-unavailable'} = xmpp:get_error(Err4)
+ end, [get, set]),
+ disconnect(Config).
+
+version(Config) ->
+ JID = jid:from_string(<<"nurse@example.com">>),
+ ct:comment("Requesting roster"),
+ {InitialVersion, _} = get_items(Config, <<"">>),
+ ct:comment("Requesting roster with initial version"),
+ {empty, []} = get_items(Config, InitialVersion),
+ ct:comment("Adding JID to the roster"),
+ {NewVersion, _} = set_items(Config, [#roster_item{jid = JID}]),
+ ct:comment("Requesting roster with initial version"),
+ {NewVersion, _} = get_items(Config, InitialVersion),
+ ct:comment("Requesting roster with new version"),
+ {empty, []} = get_items(Config, NewVersion),
+ del_roster(disconnect(Config), JID).
+
+%%%===================================================================
+%%% Master-slave tests
+%%%===================================================================
+master_slave_cases() ->
+ {roster_master_slave, [parallel],
+ [master_slave_test(subscribe)]}.
+
+subscribe_master(Config) ->
+ Actions = actions(),
+ process_subscriptions_master(Config, Actions),
+ del_roster(disconnect(Config)).
+
+subscribe_slave(Config) ->
+ process_subscriptions_slave(Config),
+ del_roster(disconnect(Config)).
+
+process_subscriptions_master(Config, Actions) ->
+ EnumeratedActions = lists:zip(lists:seq(1, length(Actions)), Actions),
+ self_presence(Config, available),
+ lists:foldl(
+ fun({N, {Dir, Type}}, State) ->
+ if Dir == out -> put_event(Config, {N, in, Type});
+ Dir == in -> put_event(Config, {N, out, Type})
+ end,
+ wait_for_slave(Config),
+ ct:pal("Performing ~s-~s (#~p) "
+ "in state:~n~s~nwith roster:~n~s",
+ [Dir, Type, N, pp(State),
+ pp(get_roster(Config))]),
+ transition(Config, Dir, Type, State)
+ end, #state{}, EnumeratedActions),
+ put_event(Config, done),
+ wait_for_slave(Config),
+ Config.
+
+process_subscriptions_slave(Config) ->
+ self_presence(Config, available),
+ process_subscriptions_slave(Config, get_event(Config), #state{}).
+
+process_subscriptions_slave(Config, done, _State) ->
+ wait_for_master(Config),
+ Config;
+process_subscriptions_slave(Config, {N, Dir, Type}, State) ->
+ wait_for_master(Config),
+ ct:pal("Performing ~s-~s (#~p) "
+ "in state:~n~s~nwith roster:~n~s",
+ [Dir, Type, N, pp(State), pp(get_roster(Config))]),
+ NewState = transition(Config, Dir, Type, State),
+ process_subscriptions_slave(Config, get_event(Config), NewState).
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+single_test(T) ->
+ list_to_atom("roster_" ++ atom_to_list(T)).
+
+master_slave_test(T) ->
+ {list_to_atom("roster_" ++ atom_to_list(T)), [parallel],
+ [list_to_atom("roster_" ++ atom_to_list(T) ++ "_master"),
+ list_to_atom("roster_" ++ atom_to_list(T) ++ "_slave")]}.
+
+get_items(Config) ->
+ get_items(Config, <<"">>).
+
+get_items(Config, Version) ->
+ case send_recv(Config, #iq{type = get,
+ sub_els = [#roster_query{ver = Version}]}) of
+ #iq{type = result,
+ sub_els = [#roster_query{ver = NewVersion, items = Items}]} ->
+ {NewVersion, Items};
+ #iq{type = result, sub_els = []} ->
+ {empty, []};
+ #iq{type = error} = Err ->
+ xmpp:get_error(Err)
+ end.
+
+get_item(Config, JID) ->
+ case get_items(Config) of
+ {_Ver, Items} when is_list(Items) ->
+ lists:keyfind(JID, #roster_item.jid, Items);
+ _ ->
+ false
+ end.
+
+set_items(Config, Items) ->
+ case send_recv(Config, #iq{type = set,
+ sub_els = [#roster_query{items = Items}]}) of
+ #iq{type = result, sub_els = []} ->
+ recv_push(Config);
+ #iq{type = error} = Err ->
+ xmpp:get_error(Err)
+ end.
+
+recv_push(Config) ->
+ ct:comment("Receiving roster push"),
+ Push = #iq{type = set,
+ sub_els = [#roster_query{ver = Ver, items = [PushItem]}]}
+ = recv_iq(Config),
+ send(Config, make_iq_result(Push)),
+ {Ver, PushItem}.
+
+recv_push(Config, Subscription, Ask) ->
+ PeerJID = ?config(peer, Config),
+ PeerBareJID = jid:remove_resource(PeerJID),
+ Match = #roster_item{jid = PeerBareJID,
+ subscription = Subscription,
+ ask = Ask,
+ groups = [],
+ name = <<"">>},
+ ct:comment("Receiving roster push"),
+ Push = #iq{type = set, sub_els = [#roster_query{items = [Item]}]} =
+ recv_iq(Config),
+ case Item of
+ Match -> send(Config, make_iq_result(Push));
+ _ -> match_failure(Item, Match)
+ end.
+
+recv_presence(Config, Type) ->
+ PeerJID = ?config(peer, Config),
+ case recv_presence(Config) of
+ #presence{from = PeerJID, type = Type} -> ok;
+ Pres -> match_failure(Pres, #presence{from = PeerJID, type = Type})
+ end.
+
+recv_subscription(Config, Type) ->
+ PeerJID = ?config(peer, Config),
+ PeerBareJID = jid:remove_resource(PeerJID),
+ case recv_presence(Config) of
+ #presence{from = PeerBareJID, type = Type} -> ok;
+ Pres -> match_failure(Pres, #presence{from = PeerBareJID, type = Type})
+ end.
+
+pp(Term) ->
+ io_lib_pretty:print(Term, fun pp/2).
+
+pp(state, N) ->
+ Fs = record_info(fields, state),
+ try N = length(Fs), Fs
+ catch _:_ -> no end;
+pp(roster, N) ->
+ Fs = record_info(fields, roster),
+ try N = length(Fs), Fs
+ catch _:_ -> no end;
+pp(_, _) -> no.
+
+%% RFC6121, A.2.1
+transition(Config, out, subscribe,
+ #state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
+ PeerJID = ?config(peer, Config),
+ PeerBareJID = jid:remove_resource(PeerJID),
+ send(Config, #presence{to = PeerBareJID, type = subscribe}),
+ case {Sub, Out, In} of
+ {none, false, _} ->
+ recv_push(Config, none, subscribe),
+ State#state{pending_out = true};
+ {none, true, false} ->
+ %% BUG: we should not receive roster push here
+ recv_push(Config, none, subscribe),
+ State;
+ {from, false, false} ->
+ recv_push(Config, from, subscribe),
+ State#state{pending_out = true};
+ _ ->
+ State
+ end;
+%% RFC6121, A.2.2
+transition(Config, out, unsubscribe,
+ #state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
+ PeerJID = ?config(peer, Config),
+ PeerBareJID = jid:remove_resource(PeerJID),
+ send(Config, #presence{to = PeerBareJID, type = unsubscribe}),
+ case {Sub, Out, In} of
+ {none, true, _} ->
+ recv_push(Config, none, undefined),
+ State#state{pending_out = false};
+ {to, false, _} ->
+ recv_push(Config, none, undefined),
+ recv_presence(Config, unavailable),
+ State#state{subscription = none, peer_available = false};
+ {from, true, false} ->
+ recv_push(Config, from, undefined),
+ State#state{pending_out = false};
+ {both, false, false} ->
+ recv_push(Config, from, undefined),
+ recv_presence(Config, unavailable),
+ State#state{subscription = from, peer_available = false};
+ _ ->
+ State
+ end;
+%% RFC6121, A.2.3
+transition(Config, out, subscribed,
+ #state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
+ PeerJID = ?config(peer, Config),
+ PeerBareJID = jid:remove_resource(PeerJID),
+ send(Config, #presence{to = PeerBareJID, type = subscribed}),
+ case {Sub, Out, In} of
+ {none, false, true} ->
+ recv_push(Config, from, undefined),
+ State#state{subscription = from, pending_in = false};
+ {none, true, true} ->
+ recv_push(Config, from, subscribe),
+ State#state{subscription = from, pending_in = false};
+ {to, false, true} ->
+ recv_push(Config, both, undefined),
+ State#state{subscription = both, pending_in = false};
+ {to, false, _} ->
+ %% BUG: we should not transition to 'both' state
+ recv_push(Config, both, undefined),
+ State#state{subscription = both};
+ _ ->
+ State
+ end;
+%% RFC6121, A.2.4
+transition(Config, out, unsubscribed,
+ #state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
+ PeerJID = ?config(peer, Config),
+ PeerBareJID = jid:remove_resource(PeerJID),
+ send(Config, #presence{to = PeerBareJID, type = unsubscribed}),
+ case {Sub, Out, In} of
+ {none, false, true} ->
+ State#state{subscription = none, pending_in = false};
+ {none, true, true} ->
+ recv_push(Config, none, subscribe),
+ State#state{subscription = none, pending_in = false};
+ {to, _, true} ->
+ State#state{pending_in = false};
+ {from, false, _} ->
+ recv_push(Config, none, undefined),
+ State#state{subscription = none};
+ {from, true, _} ->
+ recv_push(Config, none, subscribe),
+ State#state{subscription = none};
+ {both, _, _} ->
+ recv_push(Config, to, undefined),
+ State#state{subscription = to};
+ _ ->
+ State
+ end;
+%% RFC6121, A.3.1
+transition(Config, in, subscribe = Type,
+ #state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
+ case {Sub, Out, In} of
+ {none, false, false} ->
+ recv_subscription(Config, Type),
+ State#state{pending_in = true};
+ {none, true, false} ->
+ recv_push(Config, none, subscribe),
+ recv_subscription(Config, Type),
+ State#state{pending_in = true};
+ {to, false, false} ->
+ %% BUG: we should not receive roster push in this state!
+ recv_push(Config, to, undefined),
+ recv_subscription(Config, Type),
+ State#state{pending_in = true};
+ _ ->
+ State
+ end;
+%% RFC6121, A.3.2
+transition(Config, in, unsubscribe = Type,
+ #state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
+ case {Sub, Out, In} of
+ {none, _, true} ->
+ State#state{pending_in = false};
+ {to, _, true} ->
+ recv_push(Config, to, undefined),
+ recv_subscription(Config, Type),
+ State#state{pending_in = false};
+ {from, false, _} ->
+ recv_push(Config, none, undefined),
+ recv_subscription(Config, Type),
+ State#state{subscription = none};
+ {from, true, _} ->
+ recv_push(Config, none, subscribe),
+ recv_subscription(Config, Type),
+ State#state{subscription = none};
+ {both, _, _} ->
+ recv_push(Config, to, undefined),
+ recv_subscription(Config, Type),
+ State#state{subscription = to};
+ _ ->
+ State
+ end;
+%% RFC6121, A.3.3
+transition(Config, in, subscribed = Type,
+ #state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
+ case {Sub, Out, In} of
+ {none, true, _} ->
+ recv_push(Config, to, undefined),
+ recv_subscription(Config, Type),
+ recv_presence(Config, available),
+ State#state{subscription = to, pending_out = false, peer_available = true};
+ {from, true, _} ->
+ recv_push(Config, both, undefined),
+ recv_subscription(Config, Type),
+ recv_presence(Config, available),
+ State#state{subscription = both, pending_out = false, peer_available = true};
+ {from, false, _} ->
+ %% BUG: we should not transition to 'both' in this state
+ recv_push(Config, both, undefined),
+ recv_subscription(Config, Type),
+ recv_presence(Config, available),
+ State#state{subscription = both, pending_out = false, peer_available = true};
+ _ ->
+ State
+ end;
+%% RFC6121, A.3.4
+transition(Config, in, unsubscribed = Type,
+ #state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
+ case {Sub, Out, In} of
+ {none, true, true} ->
+ %% BUG: we should receive roster push in this state!
+ recv_subscription(Config, Type),
+ State#state{subscription = none, pending_out = false};
+ {none, true, false} ->
+ recv_push(Config, none, undefined),
+ recv_subscription(Config, Type),
+ State#state{subscription = none, pending_out = false};
+ {none, false, false} ->
+ State;
+ {to, false, _} ->
+ recv_push(Config, none, undefined),
+ recv_subscription(Config, Type),
+ recv_presence(Config, unavailable),
+ State#state{subscription = none, peer_available = false};
+ {from, true, false} ->
+ recv_push(Config, from, undefined),
+ recv_subscription(Config, Type),
+ State#state{subscription = from, pending_out = false};
+ {both, _, _} ->
+ recv_push(Config, from, undefined),
+ recv_subscription(Config, Type),
+ recv_presence(Config, unavailable),
+ State#state{subscription = from, peer_available = false};
+ _ ->
+ State
+ end;
+%% Outgoing roster remove
+transition(Config, out, remove,
+ #state{subscription = Sub, pending_in = In, pending_out = Out}) ->
+ PeerJID = ?config(peer, Config),
+ PeerBareJID = jid:remove_resource(PeerJID),
+ Item = #roster_item{jid = PeerBareJID, subscription = remove},
+ #iq{type = result, sub_els = []} =
+ send_recv(Config, #iq{type = set,
+ sub_els = [#roster_query{items = [Item]}]}),
+ recv_push(Config, remove, undefined),
+ case {Sub, Out, In} of
+ {to, _, _} ->
+ recv_presence(Config, unavailable);
+ {both, _, _} ->
+ recv_presence(Config, unavailable);
+ _ ->
+ ok
+ end,
+ #state{};
+%% Incoming roster remove
+transition(Config, in, remove,
+ #state{subscription = Sub, pending_in = In, pending_out = Out} = State) ->
+ case {Sub, Out, In} of
+ {none, true, _} ->
+ ok;
+ {from, false, _} ->
+ recv_push(Config, none, undefined),
+ recv_subscription(Config, unsubscribe);
+ {from, true, _} ->
+ recv_push(Config, none, subscribe),
+ recv_subscription(Config, unsubscribe);
+ {to, false, _} ->
+ %% BUG: we should receive push here
+ %% recv_push(Config, none, undefined),
+ recv_presence(Config, unavailable),
+ recv_subscription(Config, unsubscribed);
+ {both, _, _} ->
+ recv_presence(Config, unavailable),
+ recv_push(Config, to, undefined),
+ recv_subscription(Config, unsubscribe),
+ recv_push(Config, none, undefined),
+ recv_subscription(Config, unsubscribed);
+ _ ->
+ ok
+ end,
+ State#state{subscription = none}.
+
+actions() ->
+ States = [{Dir, Type} || Dir <- [out, in],
+ Type <- [subscribe, subscribed,
+ unsubscribe, unsubscribed,
+ remove]],
+ Actions = lists:flatten([[X, Y] || X <- States, Y <- States]),
+ remove_dups(Actions, []).
+
+remove_dups([X|T], [X,X|_] = Acc) ->
+ remove_dups(T, Acc);
+remove_dups([X|T], Acc) ->
+ remove_dups(T, [X|Acc]);
+remove_dups([], Acc) ->
+ lists:reverse(Acc).
diff --git a/test/suite.erl b/test/suite.erl
index 3bed62562..f88ac5a5e 100644
--- a/test/suite.erl
+++ b/test/suite.erl
@@ -86,6 +86,7 @@ init_config(Config) ->
{stream_from, <<"">>},
{db_xmlns, <<"">>},
{mechs, []},
+ {rosterver, false},
{lang, <<"en">>},
{base_dir, BaseDir},
{socket, undefined},
@@ -421,6 +422,8 @@ wait_auth_SASL_result(Config, ShouldFail) ->
set_opt(sm, true, ConfigAcc);
(#feature_csi{}, ConfigAcc) ->
set_opt(csi, true, ConfigAcc);
+ (#rosterver_feature{}, ConfigAcc) ->
+ set_opt(rosterver, true, ConfigAcc);
(_, ConfigAcc) ->
ConfigAcc
end, Config, Fs);
@@ -674,26 +677,32 @@ set_opt(Opt, Val, Config) ->
[{Opt, Val}|lists:keydelete(Opt, 1, Config)].
wait_for_master(Config) ->
- put_event(Config, slave_ready),
+ put_event(Config, peer_ready),
case get_event(Config) of
- master_ready ->
+ peer_ready ->
ok;
Other ->
- suite:match_failure([Other], [master_ready])
+ suite:match_failure(Other, peer_ready)
end.
wait_for_slave(Config) ->
- put_event(Config, master_ready),
+ put_event(Config, peer_ready),
case get_event(Config) of
- slave_ready ->
+ peer_ready ->
ok;
Other ->
- suite:match_failure([Other], [slave_ready])
+ suite:match_failure(Other, peer_ready)
end.
make_iq_result(#iq{from = From} = IQ) ->
IQ#iq{type = result, to = From, from = undefined, sub_els = []}.
+self_presence(Config, Type) ->
+ MyJID = my_jid(Config),
+ ct:comment("Sending self-presence"),
+ #presence{type = Type, from = MyJID} =
+ send_recv(Config, #presence{type = Type}).
+
set_roster(Config, Subscription, Groups) ->
MyJID = my_jid(Config),
{U, S, _} = jid:tolower(MyJID),
@@ -710,15 +719,21 @@ set_roster(Config, Subscription, Groups) ->
Config.
del_roster(Config) ->
+ del_roster(Config, ?config(peer, Config)).
+
+del_roster(Config, PeerJID) ->
MyJID = my_jid(Config),
{U, S, _} = jid:tolower(MyJID),
- PeerJID = ?config(peer, Config),
PeerBareJID = jid:remove_resource(PeerJID),
PeerLJID = jid:tolower(PeerBareJID),
ct:comment("Removing ~s from roster", [jid:to_string(PeerBareJID)]),
{atomic, _} = mod_roster:del_roster(U, S, PeerLJID),
Config.
+get_roster(Config) ->
+ {LUser, LServer, _} = jid:tolower(my_jid(Config)),
+ mod_roster:get_roster(LUser, LServer).
+
receiver(NS, Owner) ->
MRef = erlang:monitor(process, Owner),
receiver(NS, Owner, MRef).