From d6323a7b5e1e96c4738d521432ea5667b11b005e Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Wed, 10 Feb 2016 16:15:43 +0300 Subject: [PATCH] Add tests for XEP-0013 --- Makefile.in | 2 +- test/ejabberd_SUITE.erl | 131 +++++++++++++++++++++++++++ tools/xmpp_codec.erl | 196 +++++++++++++++++++++++++++++++++++++++- tools/xmpp_codec.hrl | 9 +- tools/xmpp_codec.spec | 29 ++++++ 5 files changed, 363 insertions(+), 4 deletions(-) diff --git a/Makefile.in b/Makefile.in index 1b7e5043c..0d9134485 100644 --- a/Makefile.in +++ b/Makefile.in @@ -110,7 +110,7 @@ edoc: spec: $(ERL) -noinput +B -pa ebin -pa deps/*/ebin -eval \ - 'case xml_gen:compile("tools/xmpp_codec.spec") of ok -> halt(0); _ -> halt(1) end.' + 'case fxml_gen:compile("tools/xmpp_codec.spec") of ok -> halt(0); _ -> halt(1) end.' JOIN_PATHS=$(if $(wordlist 2,1000,$(1)),$(firstword $(1))/$(call JOIN_PATHS,$(wordlist 2,1000,$(1))),$(1)) diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl index 543437e08..5054f0984 100644 --- a/test/ejabberd_SUITE.erl +++ b/test/ejabberd_SUITE.erl @@ -214,6 +214,8 @@ db_tests(riak) -> {test_roster_subscribe, [parallel], [roster_subscribe_master, roster_subscribe_slave]}, + {test_flex_offline, [sequence], + [flex_offline_master, flex_offline_slave]}, {test_offline, [sequence], [offline_master, offline_slave]}, {test_muc, [parallel], @@ -245,6 +247,8 @@ db_tests(mnesia) -> {test_roster_subscribe, [parallel], [roster_subscribe_master, roster_subscribe_slave]}, + {test_flex_offline, [sequence], + [flex_offline_master, flex_offline_slave]}, {test_offline, [sequence], [offline_master, offline_slave]}, {test_old_mam, [parallel], @@ -285,6 +289,8 @@ db_tests(_) -> {test_roster_subscribe, [parallel], [roster_subscribe_master, roster_subscribe_slave]}, + {test_flex_offline, [sequence], + [flex_offline_master, flex_offline_slave]}, {test_offline, [sequence], [offline_master, offline_slave]}, {test_old_mam, [parallel], @@ -1433,6 +1439,131 @@ announce_slave(Config) -> send(Config, #message{to = MotdDelJID}), disconnect(Config). +flex_offline_master(Config) -> + Peer = ?config(slave, Config), + LPeer = jlib:jid_remove_resource(Peer), + lists:foreach( + fun(I) -> + Body = jlib:integer_to_binary(I), + send(Config, #message{to = LPeer, + body = [#text{data = Body}], + subject = [#text{data = <<"subject">>}]}) + end, lists:seq(1, 5)), + disconnect(Config). + +flex_offline_slave(Config) -> + MyJID = my_jid(Config), + MyBareJID = jid:remove_resource(MyJID), + Peer = ?config(master, Config), + Peer_s = jid:to_string(Peer), + true = is_feature_advertised(Config, ?NS_FLEX_OFFLINE), + %% Request disco#info + #iq{type = result, + sub_els = [#disco_info{ + node = ?NS_FLEX_OFFLINE, + identities = Ids, + features = Fts, + xdata = [X]}]} = + send_recv(Config, #iq{type = get, + sub_els = [#disco_info{ + node = ?NS_FLEX_OFFLINE}]}), + %% Check if we have correct identities + true = lists:any( + fun(#identity{category = <<"automation">>, + type = <<"message-list">>}) -> true; + (_) -> false + end, Ids), + %% Check if we have needed feature + true = lists:member(?NS_FLEX_OFFLINE, Fts), + %% Check xdata, the 'number_of_messages' should be 5 + #xdata{type = result, + fields = [#xdata_field{type = hidden, + var = <<"FORM_TYPE">>}, + #xdata_field{var = <<"number_of_messages">>, + values = [<<"5">>]}]} = X, + %% Fetch headers, + #iq{type = result, + sub_els = [#disco_items{ + node = ?NS_FLEX_OFFLINE, + items = DiscoItems}]} = + send_recv(Config, #iq{type = get, + sub_els = [#disco_items{ + node = ?NS_FLEX_OFFLINE}]}), + %% Check if headers are correct + Nodes = lists:sort( + lists:map( + fun(#disco_item{jid = J, name = P, node = N}) + when (J == MyBareJID) and (P == Peer_s) -> + N + end, DiscoItems)), + %% Check full fetch + I0 = send(Config, #iq{type = get, sub_els = [#offline{fetch = true}]}), + lists:foreach( + fun({I, N}) -> + Text = jlib:integer_to_binary(I), + ?recv1(#message{body = Body, sub_els = SubEls}), + [#text{data = Text}] = Body, + #offline{items = [#offline_item{node = N}]} = + lists:keyfind(offline, 1, SubEls), + #delay{} = lists:keyfind(delay, 1, SubEls) + end, lists:zip(lists:seq(1, 5), Nodes)), + ?recv1(#iq{type = result, id = I0, sub_els = []}), + %% Fetch 2nd and 4th message + I1 = send(Config, + #iq{type = get, + sub_els = [#offline{ + items = [#offline_item{ + action = view, + node = lists:nth(2, Nodes)}, + #offline_item{ + action = view, + node = lists:nth(4, Nodes)}]}]}), + lists:foreach( + fun({I, N}) -> + Text = jlib:integer_to_binary(I), + ?recv1(#message{body = [#text{data = Text}], sub_els = SubEls}), + #offline{items = [#offline_item{node = N}]} = + lists:keyfind(offline, 1, SubEls) + end, lists:zip([2, 4], [lists:nth(2, Nodes), lists:nth(4, Nodes)])), + ?recv1(#iq{type = result, id = I1, sub_els = []}), + %% Delete 2nd and 4th message + #iq{type = result, sub_els = []} = + send_recv( + Config, + #iq{type = set, + sub_els = [#offline{ + items = [#offline_item{ + action = remove, + node = lists:nth(2, Nodes)}, + #offline_item{ + action = remove, + node = lists:nth(4, Nodes)}]}]}), + %% Check if messages were deleted + #iq{type = result, + sub_els = [#disco_items{ + node = ?NS_FLEX_OFFLINE, + items = RemainedItems}]} = + send_recv(Config, #iq{type = get, + sub_els = [#disco_items{ + node = ?NS_FLEX_OFFLINE}]}), + RemainedNodes = [lists:nth(1, Nodes), + lists:nth(3, Nodes), + lists:nth(5, Nodes)], + RemainedNodes = lists:sort( + lists:map( + fun(#disco_item{node = N}) -> N end, + RemainedItems)), + %% Purge everything left + #iq{type = result, sub_els = []} = + send_recv(Config, #iq{type = set, sub_els = [#offline{purge = true}]}), + %% Check if there is no offline messages + #iq{type = result, + sub_els = [#disco_items{node = ?NS_FLEX_OFFLINE, items = []}]} = + send_recv(Config, #iq{type = get, + sub_els = [#disco_items{ + node = ?NS_FLEX_OFFLINE}]}), + disconnect(Config). + offline_master(Config) -> Peer = ?config(slave, Config), LPeer = jlib:jid_remove_resource(Peer), diff --git a/tools/xmpp_codec.erl b/tools/xmpp_codec.erl index 3adceabc4..917418f55 100644 --- a/tools/xmpp_codec.erl +++ b/tools/xmpp_codec.erl @@ -1,4 +1,4 @@ -%% Created automatically by XML generator (xml_gen.erl) +%% Created automatically by XML generator (fxml_gen.erl) %% Source: xmpp_codec.spec -module(xmpp_codec). @@ -15,6 +15,22 @@ decode(_el) -> decode(_el, []). decode({xmlel, _name, _attrs, _} = _el, Opts) -> IgnoreEls = proplists:get_bool(ignore_els, Opts), case {_name, get_attr(<<"xmlns">>, _attrs)} of + {<<"offline">>, + <<"http://jabber.org/protocol/offline">>} -> + decode_offline(<<"http://jabber.org/protocol/offline">>, + IgnoreEls, _el); + {<<"item">>, + <<"http://jabber.org/protocol/offline">>} -> + decode_offline_item(<<"http://jabber.org/protocol/offline">>, + IgnoreEls, _el); + {<<"fetch">>, + <<"http://jabber.org/protocol/offline">>} -> + decode_offline_fetch(<<"http://jabber.org/protocol/offline">>, + IgnoreEls, _el); + {<<"purge">>, + <<"http://jabber.org/protocol/offline">>} -> + decode_offline_purge(<<"http://jabber.org/protocol/offline">>, + IgnoreEls, _el); {<<"failed">>, <<"urn:xmpp:sm:2">>} -> decode_sm_failed(<<"urn:xmpp:sm:2">>, IgnoreEls, _el); {<<"failed">>, <<"urn:xmpp:sm:3">>} -> @@ -1072,6 +1088,18 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) -> is_known_tag({xmlel, _name, _attrs, _} = _el) -> case {_name, get_attr(<<"xmlns">>, _attrs)} of + {<<"offline">>, + <<"http://jabber.org/protocol/offline">>} -> + true; + {<<"item">>, + <<"http://jabber.org/protocol/offline">>} -> + true; + {<<"fetch">>, + <<"http://jabber.org/protocol/offline">>} -> + true; + {<<"purge">>, + <<"http://jabber.org/protocol/offline">>} -> + true; {<<"failed">>, <<"urn:xmpp:sm:2">>} -> true; {<<"failed">>, <<"urn:xmpp:sm:3">>} -> true; {<<"a">>, <<"urn:xmpp:sm:2">>} -> true; @@ -2124,7 +2152,15 @@ encode({sm_resumed, _, _, _} = Resumed) -> encode({sm_r, _} = R) -> encode_sm_r(R, []); encode({sm_a, _, _} = A) -> encode_sm_a(A, []); encode({sm_failed, _, _} = Failed) -> - encode_sm_failed(Failed, []). + encode_sm_failed(Failed, []); +encode({offline_item, _, _} = Item) -> + encode_offline_item(Item, + [{<<"xmlns">>, + <<"http://jabber.org/protocol/offline">>}]); +encode({offline, _, _, _} = Offline) -> + encode_offline(Offline, + [{<<"xmlns">>, + <<"http://jabber.org/protocol/offline">>}]). get_ns({last, _, _}) -> <<"jabber:iq:last">>; get_ns({version, _, _, _}) -> <<"jabber:iq:version">>; @@ -2319,6 +2355,10 @@ get_ns({carbons_sent, _}) -> <<"urn:xmpp:carbons:2">>; get_ns({feature_csi, _}) -> <<"urn:xmpp:csi:0">>; get_ns({csi, active}) -> <<"urn:xmpp:csi:0">>; get_ns({csi, inactive}) -> <<"urn:xmpp:csi:0">>; +get_ns({offline_item, _, _}) -> + <<"http://jabber.org/protocol/offline">>; +get_ns({offline, _, _, _}) -> + <<"http://jabber.org/protocol/offline">>; get_ns(_) -> <<>>. dec_int(Val) -> dec_int(Val, infinity, infinity). @@ -2522,6 +2562,8 @@ pp(sm_resumed, 3) -> [h, previd, xmlns]; pp(sm_r, 1) -> [xmlns]; pp(sm_a, 2) -> [h, xmlns]; pp(sm_failed, 2) -> [reason, xmlns]; +pp(offline_item, 2) -> [node, action]; +pp(offline, 3) -> [items, purge, fetch]; pp(_, _) -> no. enc_bool(false) -> <<"false">>; @@ -2564,6 +2606,156 @@ dec_tzo(Val) -> M = jlib:binary_to_integer(M1), if H >= -12, H =< 12, M >= 0, M < 60 -> {H, M} end. +decode_offline(__TopXMLNS, __IgnoreEls, + {xmlel, <<"offline">>, _attrs, _els}) -> + {Items, Purge, Fetch} = decode_offline_els(__TopXMLNS, + __IgnoreEls, _els, [], false, + false), + {offline, Items, Purge, Fetch}. + +decode_offline_els(__TopXMLNS, __IgnoreEls, [], Items, + Purge, Fetch) -> + {lists:reverse(Items), Purge, Fetch}; +decode_offline_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"purge">>, _attrs, _} = _el | _els], Items, + Purge, Fetch) -> + _xmlns = get_attr(<<"xmlns">>, _attrs), + if _xmlns == <<>>; _xmlns == __TopXMLNS -> + decode_offline_els(__TopXMLNS, __IgnoreEls, _els, Items, + decode_offline_purge(__TopXMLNS, __IgnoreEls, + _el), + Fetch); + true -> + decode_offline_els(__TopXMLNS, __IgnoreEls, _els, Items, + Purge, Fetch) + end; +decode_offline_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"fetch">>, _attrs, _} = _el | _els], Items, + Purge, Fetch) -> + _xmlns = get_attr(<<"xmlns">>, _attrs), + if _xmlns == <<>>; _xmlns == __TopXMLNS -> + decode_offline_els(__TopXMLNS, __IgnoreEls, _els, Items, + Purge, + decode_offline_fetch(__TopXMLNS, __IgnoreEls, + _el)); + true -> + decode_offline_els(__TopXMLNS, __IgnoreEls, _els, Items, + Purge, Fetch) + end; +decode_offline_els(__TopXMLNS, __IgnoreEls, + [{xmlel, <<"item">>, _attrs, _} = _el | _els], Items, + Purge, Fetch) -> + _xmlns = get_attr(<<"xmlns">>, _attrs), + if _xmlns == <<>>; _xmlns == __TopXMLNS -> + decode_offline_els(__TopXMLNS, __IgnoreEls, _els, + [decode_offline_item(__TopXMLNS, __IgnoreEls, _el) + | Items], + Purge, Fetch); + true -> + decode_offline_els(__TopXMLNS, __IgnoreEls, _els, Items, + Purge, Fetch) + end; +decode_offline_els(__TopXMLNS, __IgnoreEls, [_ | _els], + Items, Purge, Fetch) -> + decode_offline_els(__TopXMLNS, __IgnoreEls, _els, Items, + Purge, Fetch). + +encode_offline({offline, Items, Purge, Fetch}, + _xmlns_attrs) -> + _els = lists:reverse('encode_offline_$items'(Items, + 'encode_offline_$purge'(Purge, + 'encode_offline_$fetch'(Fetch, + [])))), + _attrs = _xmlns_attrs, + {xmlel, <<"offline">>, _attrs, _els}. + +'encode_offline_$items'([], _acc) -> _acc; +'encode_offline_$items'([Items | _els], _acc) -> + 'encode_offline_$items'(_els, + [encode_offline_item(Items, []) | _acc]). + +'encode_offline_$purge'(false, _acc) -> _acc; +'encode_offline_$purge'(Purge, _acc) -> + [encode_offline_purge(Purge, []) | _acc]. + +'encode_offline_$fetch'(false, _acc) -> _acc; +'encode_offline_$fetch'(Fetch, _acc) -> + [encode_offline_fetch(Fetch, []) | _acc]. + +decode_offline_item(__TopXMLNS, __IgnoreEls, + {xmlel, <<"item">>, _attrs, _els}) -> + {Node, Action} = decode_offline_item_attrs(__TopXMLNS, + _attrs, undefined, undefined), + {offline_item, Node, Action}. + +decode_offline_item_attrs(__TopXMLNS, + [{<<"node">>, _val} | _attrs], _Node, Action) -> + decode_offline_item_attrs(__TopXMLNS, _attrs, _val, + Action); +decode_offline_item_attrs(__TopXMLNS, + [{<<"action">>, _val} | _attrs], Node, _Action) -> + decode_offline_item_attrs(__TopXMLNS, _attrs, Node, + _val); +decode_offline_item_attrs(__TopXMLNS, [_ | _attrs], + Node, Action) -> + decode_offline_item_attrs(__TopXMLNS, _attrs, Node, + Action); +decode_offline_item_attrs(__TopXMLNS, [], Node, + Action) -> + {decode_offline_item_attr_node(__TopXMLNS, Node), + decode_offline_item_attr_action(__TopXMLNS, Action)}. + +encode_offline_item({offline_item, Node, Action}, + _xmlns_attrs) -> + _els = [], + _attrs = encode_offline_item_attr_action(Action, + encode_offline_item_attr_node(Node, + _xmlns_attrs)), + {xmlel, <<"item">>, _attrs, _els}. + +decode_offline_item_attr_node(__TopXMLNS, undefined) -> + undefined; +decode_offline_item_attr_node(__TopXMLNS, _val) -> _val. + +encode_offline_item_attr_node(undefined, _acc) -> _acc; +encode_offline_item_attr_node(_val, _acc) -> + [{<<"node">>, _val} | _acc]. + +decode_offline_item_attr_action(__TopXMLNS, + undefined) -> + undefined; +decode_offline_item_attr_action(__TopXMLNS, _val) -> + case catch dec_enum(_val, [view, remove]) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"action">>, <<"item">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_offline_item_attr_action(undefined, _acc) -> + _acc; +encode_offline_item_attr_action(_val, _acc) -> + [{<<"action">>, enc_enum(_val)} | _acc]. + +decode_offline_fetch(__TopXMLNS, __IgnoreEls, + {xmlel, <<"fetch">>, _attrs, _els}) -> + true. + +encode_offline_fetch(true, _xmlns_attrs) -> + _els = [], + _attrs = _xmlns_attrs, + {xmlel, <<"fetch">>, _attrs, _els}. + +decode_offline_purge(__TopXMLNS, __IgnoreEls, + {xmlel, <<"purge">>, _attrs, _els}) -> + true. + +encode_offline_purge(true, _xmlns_attrs) -> + _els = [], + _attrs = _xmlns_attrs, + {xmlel, <<"purge">>, _attrs, _els}. + decode_sm_failed(__TopXMLNS, __IgnoreEls, {xmlel, <<"failed">>, _attrs, _els}) -> Reason = decode_sm_failed_els(__TopXMLNS, __IgnoreEls, diff --git a/tools/xmpp_codec.hrl b/tools/xmpp_codec.hrl index 7996f6a11..1426287bf 100644 --- a/tools/xmpp_codec.hrl +++ b/tools/xmpp_codec.hrl @@ -1,4 +1,4 @@ -%% Created automatically by XML generator (xml_gen.erl) +%% Created automatically by XML generator (fxml_gen.erl) %% Source: xmpp_codec.spec -record(chatstate, {type :: active | composing | gone | inactive | paused}). @@ -424,6 +424,13 @@ features = [] :: [binary()], xdata = [] :: [#xdata{}]}). +-record(offline_item, {node :: binary(), + action :: 'remove' | 'view'}). + +-record(offline, {items = [] :: [#offline_item{}], + purge = false :: boolean(), + fetch = false :: boolean()}). + -record(sasl_mechanisms, {list = [] :: [binary()]}). -record(sm_failed, {reason :: atom() | #gone{} | #redirect{}, diff --git a/tools/xmpp_codec.spec b/tools/xmpp_codec.spec index 8d087c8b6..129a4efba 100644 --- a/tools/xmpp_codec.spec +++ b/tools/xmpp_codec.spec @@ -2398,6 +2398,35 @@ #ref{name = error_unexpected_request, min = 0, max = 1, label = '$reason'}]}). +-xml(offline_purge, + #elem{name = <<"purge">>, + xmlns = <<"http://jabber.org/protocol/offline">>, + result = true}). + +-xml(offline_fetch, + #elem{name = <<"fetch">>, + xmlns = <<"http://jabber.org/protocol/offline">>, + result = true}). + +-xml(offline_item, + #elem{name = <<"item">>, + xmlns = <<"http://jabber.org/protocol/offline">>, + result = {offline_item, '$node', '$action'}, + attrs = [#attr{name = <<"node">>}, + #attr{name = <<"action">>, + dec = {dec_enum, [[view, remove]]}, + enc = {enc_enum, []}}]}). + +-xml(offline, + #elem{name = <<"offline">>, + xmlns = <<"http://jabber.org/protocol/offline">>, + result = {offline, '$items', '$purge', '$fetch'}, + refs = [#ref{name = offline_purge, min = 0, max = 1, + label = '$purge', default = false}, + #ref{name = offline_fetch, min = 0, max = 1, + label = '$fetch', default = false}, + #ref{name = offline_item, min = 0, label = '$items'}]}). + dec_tzo(Val) -> [H1, M1] = str:tokens(Val, <<":">>), H = jlib:binary_to_integer(H1),