diff options
author | Evgeniy Khramtsov <ekhramtsov@process-one.net> | 2016-11-07 10:10:57 +0300 |
---|---|---|
committer | Evgeniy Khramtsov <ekhramtsov@process-one.net> | 2016-11-07 10:10:57 +0300 |
commit | 56c91d3c58ec5461600f70d2d0413c846767f882 (patch) | |
tree | fae4ac81fe1b3556c059f69deff4209f0591add7 /test | |
parent | Use base64:mime_decode/1 for SASL packets (diff) |
Add roster tests
Diffstat (limited to 'test')
-rw-r--r-- | test/ejabberd_SUITE.erl | 224 | ||||
-rw-r--r-- | test/roster_tests.erl | 527 | ||||
-rw-r--r-- | test/suite.erl | 29 |
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). |