mirror of
https://github.com/processone/ejabberd.git
synced 2024-12-24 17:29:28 +01:00
Add roster tests
This commit is contained in:
parent
9d977e484a
commit
56c91d3c58
@ -442,135 +442,96 @@ online(Sessions) ->
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
-spec do_route(jid(), jid(), stanza() | broadcast()) -> any().
|
||||
do_route(From, #jid{lresource = <<"">>} = To, {broadcast, _} = Packet) ->
|
||||
?DEBUG("processing broadcast to bare JID: ~p", [Packet]),
|
||||
lists:foreach(
|
||||
fun(R) ->
|
||||
do_route(From, jid:replace_resource(To, R), Packet)
|
||||
end, get_user_resources(To#jid.user, To#jid.server));
|
||||
do_route(From, To, {broadcast, _} = Packet) ->
|
||||
case To#jid.lresource of
|
||||
<<"">> ->
|
||||
lists:foreach(fun(R) ->
|
||||
do_route(From,
|
||||
jid:replace_resource(To, R),
|
||||
Packet)
|
||||
end,
|
||||
get_user_resources(To#jid.user, To#jid.server));
|
||||
_ ->
|
||||
{U, S, R} = jid:tolower(To),
|
||||
Mod = get_sm_backend(S),
|
||||
case online(Mod:get_sessions(U, S, R)) of
|
||||
[] ->
|
||||
?DEBUG("packet dropped~n", []);
|
||||
Ss ->
|
||||
Session = lists:max(Ss),
|
||||
Pid = element(2, Session#session.sid),
|
||||
?DEBUG("sending to process ~p~n", [Pid]),
|
||||
Pid ! {route, From, To, Packet}
|
||||
end
|
||||
?DEBUG("processing broadcast to full JID: ~p", [Packet]),
|
||||
{U, S, R} = jid:tolower(To),
|
||||
Mod = get_sm_backend(S),
|
||||
case online(Mod:get_sessions(U, S, R)) of
|
||||
[] ->
|
||||
?DEBUG("dropping broadcast to unavailable resourse: ~p", [Packet]);
|
||||
Ss ->
|
||||
Session = lists:max(Ss),
|
||||
Pid = element(2, Session#session.sid),
|
||||
?DEBUG("sending to process ~p: ~p", [Pid, Packet]),
|
||||
Pid ! {route, From, To, Packet}
|
||||
end;
|
||||
do_route(From, To, Packet) ->
|
||||
?DEBUG("session manager~n\tfrom ~p~n\tto ~p~n\tpacket "
|
||||
"~P~n",
|
||||
[From, To, Packet, 8]),
|
||||
do_route(From, To, #presence{type = T, status = Status} = Packet)
|
||||
when T == subscribe; T == subscribed; T == unsubscribe; T == unsubscribed ->
|
||||
?DEBUG("processing subscription:~n~s", [xmpp:pp(Packet)]),
|
||||
#jid{user = User, server = Server,
|
||||
luser = LUser, lserver = LServer, lresource = LResource} = To,
|
||||
Lang = xmpp:get_lang(Packet),
|
||||
case LResource of
|
||||
<<"">> ->
|
||||
case Packet of
|
||||
#presence{type = T, status = Status} ->
|
||||
{Pass, _Subsc} = case T of
|
||||
subscribe ->
|
||||
Reason = xmpp:get_text(Status),
|
||||
{is_privacy_allow(From, To, Packet)
|
||||
andalso
|
||||
ejabberd_hooks:run_fold(roster_in_subscription,
|
||||
LServer,
|
||||
false,
|
||||
[User, Server,
|
||||
From,
|
||||
subscribe,
|
||||
Reason]),
|
||||
true};
|
||||
subscribed ->
|
||||
{is_privacy_allow(From, To, Packet)
|
||||
andalso
|
||||
ejabberd_hooks:run_fold(roster_in_subscription,
|
||||
LServer,
|
||||
false,
|
||||
[User, Server,
|
||||
From,
|
||||
subscribed,
|
||||
<<"">>]),
|
||||
true};
|
||||
unsubscribe ->
|
||||
{is_privacy_allow(From, To, Packet)
|
||||
andalso
|
||||
ejabberd_hooks:run_fold(roster_in_subscription,
|
||||
LServer,
|
||||
false,
|
||||
[User, Server,
|
||||
From,
|
||||
unsubscribe,
|
||||
<<"">>]),
|
||||
true};
|
||||
unsubscribed ->
|
||||
{is_privacy_allow(From, To, Packet)
|
||||
andalso
|
||||
ejabberd_hooks:run_fold(roster_in_subscription,
|
||||
LServer,
|
||||
false,
|
||||
[User, Server,
|
||||
From,
|
||||
unsubscribed,
|
||||
<<"">>]),
|
||||
true};
|
||||
_ -> {true, false}
|
||||
end,
|
||||
if Pass ->
|
||||
PResources = get_user_present_resources(LUser, LServer),
|
||||
lists:foreach(fun ({_, R}) ->
|
||||
do_route(From,
|
||||
jid:replace_resource(To,
|
||||
R),
|
||||
Packet)
|
||||
end,
|
||||
PResources);
|
||||
true -> ok
|
||||
end;
|
||||
#message{type = T} when T == chat; T == headline; T == normal ->
|
||||
route_message(From, To, Packet, T);
|
||||
#message{type = groupchat} ->
|
||||
ErrTxt = <<"User session not found">>,
|
||||
Err = xmpp:make_error(
|
||||
Packet, xmpp:err_service_unavailable(ErrTxt, Lang)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
#iq{} -> process_iq(From, To, Packet);
|
||||
_ -> ok
|
||||
end;
|
||||
_ ->
|
||||
Mod = get_sm_backend(LServer),
|
||||
case online(Mod:get_sessions(LUser, LServer, LResource)) of
|
||||
[] ->
|
||||
case Packet of
|
||||
#message{type = T} when T == chat; T == normal ->
|
||||
route_message(From, To, Packet, T);
|
||||
#message{type = groupchat} ->
|
||||
ErrTxt = <<"User session not found">>,
|
||||
Err = xmpp:make_error(
|
||||
Packet,
|
||||
xmpp:err_service_unavailable(ErrTxt, Lang)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
#iq{type = T} when T == get; T == set ->
|
||||
ErrTxt = <<"User session not found">>,
|
||||
Err = xmpp:make_error(
|
||||
Packet,
|
||||
xmpp:err_service_unavailable(ErrTxt, Lang)),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
_ -> ?DEBUG("packet dropped~n", [])
|
||||
end;
|
||||
Ss ->
|
||||
Session = lists:max(Ss),
|
||||
Pid = element(2, Session#session.sid),
|
||||
?DEBUG("sending to process ~p~n", [Pid]),
|
||||
Pid ! {route, From, To, Packet}
|
||||
end
|
||||
luser = LUser, lserver = LServer} = To,
|
||||
Reason = if T == subscribe -> xmpp:get_text(Status);
|
||||
true -> <<"">>
|
||||
end,
|
||||
case is_privacy_allow(From, To, Packet) andalso
|
||||
ejabberd_hooks:run_fold(
|
||||
roster_in_subscription,
|
||||
LServer, false,
|
||||
[User, Server, From, T, Reason]) of
|
||||
true ->
|
||||
Mod = get_sm_backend(LServer),
|
||||
lists:foreach(
|
||||
fun(#session{sid = SID, usr = {_, _, R},
|
||||
priority = Prio}) when is_integer(Prio) ->
|
||||
Pid = element(2, SID),
|
||||
?DEBUG("sending to process ~p:~n~s",
|
||||
[Pid, xmpp:pp(Packet)]),
|
||||
Pid ! {route, From, jid:replace_resource(To, R), Packet};
|
||||
(_) ->
|
||||
ok
|
||||
end, online(Mod:get_sessions(LUser, LServer)));
|
||||
false ->
|
||||
ok
|
||||
end;
|
||||
do_route(From, #jid{lresource = <<"">>} = To, #presence{} = Packet) ->
|
||||
?DEBUG("processing presence to bare JID:~n~s", [xmpp:pp(Packet)]),
|
||||
{LUser, LServer, _} = jid:tolower(To),
|
||||
lists:foreach(
|
||||
fun({_, R}) ->
|
||||
do_route(From, jid:replace_resource(To, R), Packet)
|
||||
end, get_user_present_resources(LUser, LServer));
|
||||
do_route(From, #jid{lresource = <<"">>} = To, #message{type = T} = Packet) ->
|
||||
?DEBUG("processing message to bare JID:~n~s", [xmpp:pp(Packet)]),
|
||||
if T == chat; T == headline; T == normal ->
|
||||
route_message(From, To, Packet, T);
|
||||
true ->
|
||||
Lang = xmpp:get_lang(Packet),
|
||||
ErrTxt = <<"User session not found">>,
|
||||
Err = xmpp:err_service_unavailable(ErrTxt, Lang),
|
||||
ejabberd_router:route_error(To, From, Packet, Err)
|
||||
end;
|
||||
do_route(From, #jid{lresource = <<"">>} = To, #iq{} = Packet) ->
|
||||
?DEBUG("processing IQ to bare JID:~n~s", [xmpp:pp(Packet)]),
|
||||
process_iq(From, To, Packet);
|
||||
do_route(From, To, Packet) ->
|
||||
?DEBUG("processing packet to full JID:~n~s", [xmpp:pp(Packet)]),
|
||||
{LUser, LServer, LResource} = jid:tolower(To),
|
||||
Mod = get_sm_backend(LServer),
|
||||
case online(Mod:get_sessions(LUser, LServer, LResource)) of
|
||||
[] ->
|
||||
case Packet of
|
||||
#message{type = T} when T == chat; T == normal ->
|
||||
route_message(From, To, Packet, T);
|
||||
#presence{} ->
|
||||
?DEBUG("dropping presence to unavalable resource:~n~s",
|
||||
[xmpp:pp(Packet)]);
|
||||
_ ->
|
||||
Lang = xmpp:get_lang(Packet),
|
||||
ErrTxt = <<"User session not found">>,
|
||||
Err = xmpp:err_service_unavailable(ErrTxt, Lang),
|
||||
ejabberd_router:route_error(To, From, Packet, Err)
|
||||
end;
|
||||
Ss ->
|
||||
Session = lists:max(Ss),
|
||||
Pid = element(2, Session#session.sid),
|
||||
?DEBUG("sending to process ~p:~n~s", [Pid, xmpp:pp(Packet)]),
|
||||
Pid ! {route, From, To, Packet}
|
||||
end.
|
||||
|
||||
%% The default list applies to the user as a whole,
|
||||
|
@ -142,21 +142,54 @@ depends(_Host, _Opts) ->
|
||||
process_iq(#iq{from = #jid{luser = <<"">>},
|
||||
to = #jid{resource = <<"">>}} = IQ) ->
|
||||
process_iq_manager(IQ);
|
||||
process_iq(#iq{from = #jid{luser = U, lserver = S},
|
||||
to = #jid{luser = U, lserver = S}} = IQ) ->
|
||||
process_local_iq(IQ);
|
||||
process_iq(#iq{lang = Lang} = IQ) ->
|
||||
Txt = <<"Query to another users is forbidden">>,
|
||||
xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)).
|
||||
|
||||
process_iq(#iq{from = From, lang = Lang} = IQ) ->
|
||||
#jid{lserver = LServer} = From,
|
||||
case lists:member(LServer, ?MYHOSTS) of
|
||||
true -> process_local_iq(IQ);
|
||||
_ ->
|
||||
Txt = <<"The query is only allowed from local users">>,
|
||||
xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang))
|
||||
end.
|
||||
|
||||
process_local_iq(#iq{type = Type} = IQ) ->
|
||||
case Type of
|
||||
set -> try_process_iq_set(IQ);
|
||||
get -> process_iq_get(IQ)
|
||||
end.
|
||||
process_local_iq(#iq{type = set,lang = Lang,
|
||||
sub_els = [#roster_query{
|
||||
items = [#roster_item{ask = Ask}]}]} = IQ)
|
||||
when Ask /= undefined ->
|
||||
Txt = <<"Possessing 'ask' attribute is not allowed by RFC6121">>,
|
||||
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
|
||||
process_local_iq(#iq{type = set, from = From, lang = Lang,
|
||||
sub_els = [#roster_query{
|
||||
items = [#roster_item{} = Item]}]} = IQ) ->
|
||||
case has_duplicated_groups(Item#roster_item.groups) of
|
||||
true ->
|
||||
Txt = <<"Duplicated groups are not allowed by RFC6121">>,
|
||||
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
|
||||
false ->
|
||||
#jid{server = Server} = From,
|
||||
Access = gen_mod:get_module_opt(Server, ?MODULE,
|
||||
access, fun(A) -> A end, all),
|
||||
case acl:match_rule(Server, Access, From) of
|
||||
deny ->
|
||||
Txt = <<"Denied by ACL">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||
allow ->
|
||||
process_iq_set(IQ)
|
||||
end
|
||||
end;
|
||||
process_local_iq(#iq{type = set, lang = Lang,
|
||||
sub_els = [#roster_query{items = [_|_]}]} = IQ) ->
|
||||
Txt = <<"Multiple <item/> elements are not allowed by RFC6121">>,
|
||||
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
|
||||
process_local_iq(#iq{type = get, lang = Lang,
|
||||
sub_els = [#roster_query{items = Items}]} = IQ) ->
|
||||
case Items of
|
||||
[] ->
|
||||
process_iq_get(IQ);
|
||||
[_|_] ->
|
||||
Txt = <<"The query must not contain <item/> elements">>,
|
||||
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang))
|
||||
end;
|
||||
process_local_iq(#iq{lang = Lang} = IQ) ->
|
||||
Txt = <<"No module is handling this query">>,
|
||||
xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
|
||||
|
||||
roster_hash(Items) ->
|
||||
p1_sha:sha(term_to_binary(lists:sort([R#roster{groups =
|
||||
@ -315,11 +348,18 @@ encode_item(Item) ->
|
||||
end,
|
||||
groups = Item#roster.groups}.
|
||||
|
||||
decode_item(#roster_item{subscription = remove} = Item, R, _) ->
|
||||
R#roster{jid = jid:tolower(Item#roster_item.jid),
|
||||
name = <<"">>,
|
||||
subscription = remove,
|
||||
ask = none,
|
||||
groups = [],
|
||||
askmessage = <<"">>,
|
||||
xs = []};
|
||||
decode_item(Item, R, Managed) ->
|
||||
R#roster{jid = jid:tolower(Item#roster_item.jid),
|
||||
name = Item#roster_item.name,
|
||||
subscription = case Item#roster_item.subscription of
|
||||
remove -> remove;
|
||||
Sub when Managed -> Sub;
|
||||
_ -> R#roster.subscription
|
||||
end,
|
||||
@ -329,17 +369,6 @@ get_roster_by_jid_t(LUser, LServer, LJID) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:get_roster_by_jid(LUser, LServer, LJID).
|
||||
|
||||
try_process_iq_set(#iq{from = From, lang = Lang} = IQ) ->
|
||||
#jid{server = Server} = From,
|
||||
Access = gen_mod:get_module_opt(Server, ?MODULE, access, fun(A) -> A end, all),
|
||||
case acl:match_rule(Server, Access, From) of
|
||||
deny ->
|
||||
Txt = <<"Denied by ACL">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||
allow ->
|
||||
process_iq_set(IQ)
|
||||
end.
|
||||
|
||||
process_iq_set(#iq{from = From, to = To, id = Id,
|
||||
sub_els = [#roster_query{items = QueryItems}]} = IQ) ->
|
||||
Managed = is_managed_from_id(Id),
|
||||
@ -515,8 +544,7 @@ process_subscription(Direction, User, Server, JID1,
|
||||
{Subscription, Pending} ->
|
||||
NewItem = Item#roster{subscription = Subscription,
|
||||
ask = Pending,
|
||||
askmessage =
|
||||
iolist_to_binary(AskMessage)},
|
||||
askmessage = AskMessage},
|
||||
roster_subscribe_t(LUser, LServer, LJID, NewItem),
|
||||
case roster_version_on_db(LServer) of
|
||||
true -> write_roster_version_t(LUser, LServer);
|
||||
@ -730,10 +758,8 @@ del_roster_t(LUser, LServer, LJID) ->
|
||||
Mod:del_roster(LUser, LServer, LJID).
|
||||
|
||||
process_item_set_t(LUser, LServer, #roster_item{jid = JID1} = QueryItem) ->
|
||||
JID = {JID1#jid.user, JID1#jid.server,
|
||||
JID1#jid.resource},
|
||||
LJID = {JID1#jid.luser, JID1#jid.lserver,
|
||||
JID1#jid.lresource},
|
||||
JID = {JID1#jid.user, JID1#jid.server, <<>>},
|
||||
LJID = {JID1#jid.luser, JID1#jid.lserver, <<>>},
|
||||
Item = #roster{usj = {LUser, LServer, LJID},
|
||||
us = {LUser, LServer}, jid = JID},
|
||||
Item2 = decode_item(QueryItem, Item, _Managed = true),
|
||||
@ -1046,6 +1072,10 @@ is_managed_from_id(<<"roster-remotely-managed">>) ->
|
||||
is_managed_from_id(_Id) ->
|
||||
false.
|
||||
|
||||
has_duplicated_groups(Groups) ->
|
||||
GroupsPrep = lists:usort([jid:resourceprep(G) || G <- Groups]),
|
||||
not (length(GroupsPrep) == length(Groups)).
|
||||
|
||||
export(LServer) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:export(LServer).
|
||||
|
@ -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),
|
||||
|
527
test/roster_tests.erl
Normal file
527
test/roster_tests.erl
Normal file
@ -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).
|
@ -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).
|
||||
|
Loading…
Reference in New Issue
Block a user