%%%------------------------------------------------------------------- %%% Author : Holger Weiss <holger@zedat.fu-berlin.de> %%% Created : 15 Jul 2017 by Holger Weiss <holger@zedat.fu-berlin.de> %%% %%% %%% ejabberd, Copyright (C) 2002-2024 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as %%% published by the Free Software Foundation; either version 2 of the %%% License, or (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU %%% General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License along %%% with this program; if not, write to the Free Software Foundation, Inc., %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% %%%---------------------------------------------------------------------- -module(push_tests). %% API -compile(export_all). -import(suite, [close_socket/1, connect/1, disconnect/1, get_event/1, get_features/2, make_iq_result/1, my_jid/1, put_event/2, recv/1, recv_iq/1, recv_message/1, self_presence/2, send/2, send_recv/2, server_jid/1]). -include("suite.hrl"). -define(PUSH_NODE, <<"d3v1c3">>). -define(PUSH_XDATA_FIELDS, [#xdata_field{var = <<"FORM_TYPE">>, values = [?NS_PUBSUB_PUBLISH_OPTIONS]}, #xdata_field{var = <<"secret">>, values = [<<"c0nf1d3nt14l">>]}]). %%%=================================================================== %%% API %%%=================================================================== %%%=================================================================== %%% Single user tests %%%=================================================================== single_cases() -> {push_single, [sequence], [single_test(feature_enabled), single_test(unsupported_iq)]}. feature_enabled(Config) -> BareMyJID = jid:remove_resource(my_jid(Config)), Features = get_features(Config, BareMyJID), true = lists:member(?NS_PUSH_0, Features), disconnect(Config). unsupported_iq(Config) -> PushJID = my_jid(Config), lists:foreach( fun(SubEl) -> #iq{type = error} = send_recv(Config, #iq{type = get, sub_els = [SubEl]}) end, [#push_enable{jid = PushJID}, #push_disable{jid = PushJID}]), disconnect(Config). %%%=================================================================== %%% Master-slave tests %%%=================================================================== master_slave_cases() -> {push_master_slave, [sequence], [master_slave_test(sm), master_slave_test(offline), master_slave_test(mam)]}. sm_master(Config) -> ct:comment("Waiting for the slave to close the socket"), peer_down = get_event(Config), ct:comment("Waiting a bit in order to test the keepalive feature"), ct:sleep(5000), % Without mod_push_keepalive, the session would time out. ct:comment("Sending message to the slave"), send_test_message(Config), ct:comment("Handling push notification"), handle_notification(Config), ct:comment("Receiving bounced message from the slave"), #message{type = error} = recv_message(Config), ct:comment("Closing the connection"), disconnect(Config). sm_slave(Config) -> ct:comment("Enabling push notifications"), ok = enable_push(Config), ct:comment("Enabling stream management"), ok = enable_sm(Config), ct:comment("Closing the socket"), close_socket(Config). offline_master(Config) -> ct:comment("Waiting for the slave to be ready"), ready = get_event(Config), ct:comment("Sending message to the slave"), send_test_message(Config), % No push notification, slave is online. ct:comment("Waiting for the slave to disconnect"), peer_down = get_event(Config), ct:comment("Sending message to offline storage"), send_test_message(Config), ct:comment("Handling push notification for offline message"), handle_notification(Config), ct:comment("Closing the connection"), disconnect(Config). offline_slave(Config) -> ct:comment("Re-enabling push notifications"), ok = enable_push(Config), ct:comment("Letting the master know that we're ready"), put_event(Config, ready), ct:comment("Receiving message from the master"), recv_test_message(Config), ct:comment("Closing the connection"), disconnect(Config). mam_master(Config) -> ct:comment("Waiting for the slave to be ready"), ready = get_event(Config), ct:comment("Sending message to the slave"), send_test_message(Config), ct:comment("Handling push notification for MAM message"), handle_notification(Config), ct:comment("Closing the connection"), disconnect(Config). mam_slave(Config) -> self_presence(Config, available), ct:comment("Receiving message from offline storage"), recv_test_message(Config), %% Don't re-enable push notifications, otherwise the notification would be %% suppressed while the slave is online. ct:comment("Enabling MAM"), ok = enable_mam(Config), ct:comment("Letting the master know that we're ready"), put_event(Config, ready), ct:comment("Receiving message from the master"), recv_test_message(Config), ct:comment("Waiting for the master to disconnect"), peer_down = get_event(Config), ct:comment("Disabling push notifications"), ok = disable_push(Config), ct:comment("Closing the connection and cleaning up"), clean(disconnect(Config)). %%%=================================================================== %%% Internal functions %%%=================================================================== single_test(T) -> list_to_atom("push_" ++ atom_to_list(T)). master_slave_test(T) -> {list_to_atom("push_" ++ atom_to_list(T)), [parallel], [list_to_atom("push_" ++ atom_to_list(T) ++ "_master"), list_to_atom("push_" ++ atom_to_list(T) ++ "_slave")]}. enable_sm(Config) -> send(Config, #sm_enable{xmlns = ?NS_STREAM_MGMT_3, resume = true}), case recv(Config) of #sm_enabled{resume = true} -> ok; #sm_failed{reason = Reason} -> Reason end. enable_mam(Config) -> case send_recv( Config, #iq{type = set, sub_els = [#mam_prefs{xmlns = ?NS_MAM_1, default = always}]}) of #iq{type = result} -> ok; #iq{type = error} = Err -> xmpp:get_error(Err) end. enable_push(Config) -> %% Usually, the push JID would be a server JID (such as push.example.com). %% We specify the peer's full user JID instead, so the push notifications %% will be sent to the peer. PushJID = ?config(peer, Config), XData = #xdata{type = submit, fields = ?PUSH_XDATA_FIELDS}, case send_recv( Config, #iq{type = set, sub_els = [#push_enable{jid = PushJID, node = ?PUSH_NODE, xdata = XData}]}) of #iq{type = result, sub_els = []} -> ok; #iq{type = error} = Err -> xmpp:get_error(Err) end. disable_push(Config) -> PushJID = ?config(peer, Config), case send_recv( Config, #iq{type = set, sub_els = [#push_disable{jid = PushJID, node = ?PUSH_NODE}]}) of #iq{type = result, sub_els = []} -> ok; #iq{type = error} = Err -> xmpp:get_error(Err) end. send_test_message(Config) -> Peer = ?config(peer, Config), Msg = #message{to = Peer, body = [#text{data = <<"test">>}]}, send(Config, Msg). recv_test_message(Config) -> Peer = ?config(peer, Config), #message{from = Peer, body = [#text{data = <<"test">>}]} = recv_message(Config). handle_notification(Config) -> From = server_jid(Config), Item = #ps_item{sub_els = [xmpp:encode(#push_notification{})]}, Publish = #ps_publish{node = ?PUSH_NODE, items = [Item]}, XData = #xdata{type = submit, fields = ?PUSH_XDATA_FIELDS}, PubSub = #pubsub{publish = Publish, publish_options = XData}, IQ = #iq{type = set, from = From, sub_els = [PubSub]} = recv_iq(Config), send(Config, make_iq_result(IQ)). clean(Config) -> {U, S, _} = jid:tolower(my_jid(Config)), mod_push:remove_user(U, S), mod_mam:remove_user(U, S), Config.