%%%------------------------------------------------------------------- %%% Author : Evgeny Khramtsov %%% Created : 16 Nov 2016 by Evgeny Khramtsov %%% %%% %%% ejabberd, Copyright (C) 2002-2021 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(pubsub_tests). %% API -compile(export_all). -import(suite, [pubsub_jid/1, send_recv/2, get_features/2, disconnect/1, put_event/2, get_event/1, wait_for_master/1, wait_for_slave/1, recv_message/1, my_jid/1, send/2, recv_presence/1, recv/1]). -include("suite.hrl"). -include_lib("stdlib/include/assert.hrl"). %%%=================================================================== %%% API %%%=================================================================== %%%=================================================================== %%% Single user tests %%%=================================================================== single_cases() -> {pubsub_single, [sequence], [single_test(test_features), single_test(test_vcard), single_test(test_create), single_test(test_configure), single_test(test_delete), single_test(test_get_affiliations), single_test(test_get_subscriptions), single_test(test_create_instant), single_test(test_default), single_test(test_create_configure), single_test(test_publish), single_test(test_auto_create), single_test(test_get_items), single_test(test_delete_item), single_test(test_purge), single_test(test_subscribe), single_test(test_subscribe_max_item_1), single_test(test_unsubscribe)]}. test_features(Config) -> PJID = pubsub_jid(Config), AllFeatures = sets:from_list(get_features(Config, PJID)), NeededFeatures = sets:from_list( [?NS_PUBSUB, ?PUBSUB("access-open"), ?PUBSUB("access-authorize"), ?PUBSUB("create-nodes"), ?PUBSUB("instant-nodes"), ?PUBSUB("config-node"), ?PUBSUB("retrieve-default"), ?PUBSUB("create-and-configure"), ?PUBSUB("publish"), ?PUBSUB("auto-create"), ?PUBSUB("retrieve-items"), ?PUBSUB("delete-items"), ?PUBSUB("subscribe"), ?PUBSUB("retrieve-affiliations"), ?PUBSUB("modify-affiliations"), ?PUBSUB("retrieve-subscriptions"), ?PUBSUB("manage-subscriptions"), ?PUBSUB("purge-nodes"), ?PUBSUB("delete-nodes")]), true = sets:is_subset(NeededFeatures, AllFeatures), disconnect(Config). test_vcard(Config) -> JID = pubsub_jid(Config), ct:comment("Retrieving vCard from ~s", [jid:encode(JID)]), VCard = mod_pubsub_opt:vcard(?config(server, Config)), #iq{type = result, sub_els = [VCard]} = send_recv(Config, #iq{type = get, to = JID, sub_els = [#vcard_temp{}]}), disconnect(Config). test_create(Config) -> Node = ?config(pubsub_node, Config), Node = create_node(Config, Node), disconnect(Config). test_create_instant(Config) -> Node = create_node(Config, <<>>), delete_node(Config, Node), disconnect(Config). test_configure(Config) -> Node = ?config(pubsub_node, Config), NodeTitle = ?config(pubsub_node_title, Config), NodeConfig = get_node_config(Config, Node), MyNodeConfig = set_opts(NodeConfig, [{title, NodeTitle}]), set_node_config(Config, Node, MyNodeConfig), NewNodeConfig = get_node_config(Config, Node), NodeTitle = proplists:get_value(title, NewNodeConfig, <<>>), disconnect(Config). test_default(Config) -> get_default_node_config(Config), disconnect(Config). test_create_configure(Config) -> NodeTitle = ?config(pubsub_node_title, Config), DefaultNodeConfig = get_default_node_config(Config), CustomNodeConfig = set_opts(DefaultNodeConfig, [{title, NodeTitle}]), Node = create_node(Config, <<>>, CustomNodeConfig), NodeConfig = get_node_config(Config, Node), NodeTitle = proplists:get_value(title, NodeConfig, <<>>), delete_node(Config, Node), disconnect(Config). test_publish(Config) -> Node = create_node(Config, <<>>), publish_item(Config, Node), delete_node(Config, Node), disconnect(Config). test_auto_create(Config) -> Node = p1_rand:get_string(), publish_item(Config, Node), delete_node(Config, Node), disconnect(Config). test_get_items(Config) -> Node = create_node(Config, <<>>), ItemsIn = [publish_item(Config, Node) || _ <- lists:seq(1, 5)], ItemsOut = get_items(Config, Node), true = [I || #ps_item{id = I} <- lists:sort(ItemsIn)] == [I || #ps_item{id = I} <- lists:sort(ItemsOut)], delete_node(Config, Node), disconnect(Config). test_delete_item(Config) -> Node = create_node(Config, <<>>), #ps_item{id = I} = publish_item(Config, Node), [#ps_item{id = I}] = get_items(Config, Node), delete_item(Config, Node, I), [] = get_items(Config, Node), delete_node(Config, Node), disconnect(Config). test_subscribe(Config) -> Node = create_node(Config, <<>>), #ps_subscription{type = subscribed} = subscribe_node(Config, Node), [#ps_subscription{node = Node}] = get_subscriptions(Config), delete_node(Config, Node), disconnect(Config). test_subscribe_max_item_1(Config) -> DefaultNodeConfig = get_default_node_config(Config), CustomNodeConfig = set_opts(DefaultNodeConfig, [{max_items, 1}]), Node = create_node(Config, <<>>, CustomNodeConfig), #ps_subscription{type = subscribed} = subscribe_node(Config, Node), [#ps_subscription{node = Node}] = get_subscriptions(Config), delete_node(Config, Node), disconnect(Config). test_unsubscribe(Config) -> Node = create_node(Config, <<>>), subscribe_node(Config, Node), [#ps_subscription{node = Node}] = get_subscriptions(Config), unsubscribe_node(Config, Node), [] = get_subscriptions(Config), delete_node(Config, Node), disconnect(Config). test_get_affiliations(Config) -> Nodes = lists:sort([create_node(Config, <<>>) || _ <- lists:seq(1, 5)]), Affs = get_affiliations(Config), ?assertEqual(Nodes, lists:sort([Node || #ps_affiliation{node = Node, type = owner} <- Affs])), [delete_node(Config, Node) || Node <- Nodes], disconnect(Config). test_get_subscriptions(Config) -> Nodes = lists:sort([create_node(Config, <<>>) || _ <- lists:seq(1, 5)]), [subscribe_node(Config, Node) || Node <- Nodes], Subs = get_subscriptions(Config), ?assertEqual(Nodes, lists:sort([Node || #ps_subscription{node = Node} <- Subs])), [delete_node(Config, Node) || Node <- Nodes], disconnect(Config). test_purge(Config) -> Node = create_node(Config, <<>>), ItemsIn = [publish_item(Config, Node) || _ <- lists:seq(1, 5)], ItemsOut = get_items(Config, Node), true = [I || #ps_item{id = I} <- lists:sort(ItemsIn)] == [I || #ps_item{id = I} <- lists:sort(ItemsOut)], purge_node(Config, Node), [] = get_items(Config, Node), delete_node(Config, Node), disconnect(Config). test_delete(Config) -> Node = ?config(pubsub_node, Config), delete_node(Config, Node), disconnect(Config). %%%=================================================================== %%% Master-slave tests %%%=================================================================== master_slave_cases() -> {pubsub_master_slave, [sequence], [master_slave_test(publish), master_slave_test(subscriptions), master_slave_test(affiliations), master_slave_test(authorize)]}. publish_master(Config) -> Node = create_node(Config, <<>>), put_event(Config, Node), ready = get_event(Config), #ps_item{id = ID} = publish_item(Config, Node), #ps_item{id = ID} = get_event(Config), delete_node(Config, Node), disconnect(Config). publish_slave(Config) -> Node = get_event(Config), subscribe_node(Config, Node), put_event(Config, ready), #message{ sub_els = [#ps_event{ items = #ps_items{node = Node, items = [Item]}}]} = recv_message(Config), put_event(Config, Item), disconnect(Config). subscriptions_master(Config) -> Peer = ?config(slave, Config), Node = ?config(pubsub_node, Config), Node = create_node(Config, Node), [] = get_subscriptions(Config, Node), wait_for_slave(Config), lists:foreach( fun(Type) -> ok = set_subscriptions(Config, Node, [{Peer, Type}]), #ps_item{} = publish_item(Config, Node), case get_subscriptions(Config, Node) of [] when Type == none; Type == pending -> ok; [#ps_subscription{jid = Peer, type = Type}] -> ok end end, [subscribed, unconfigured, pending, none]), delete_node(Config, Node), disconnect(Config). subscriptions_slave(Config) -> wait_for_master(Config), MyJID = my_jid(Config), Node = ?config(pubsub_node, Config), lists:foreach( fun(subscribed = Type) -> ?recv2(#message{ sub_els = [#ps_event{ subscription = #ps_subscription{ node = Node, jid = MyJID, type = Type}}]}, #message{sub_els = [#ps_event{}]}); (Type) -> #message{ sub_els = [#ps_event{ subscription = #ps_subscription{ node = Node, jid = MyJID, type = Type}}]} = recv_message(Config) end, [subscribed, unconfigured, pending, none]), disconnect(Config). affiliations_master(Config) -> Peer = ?config(slave, Config), BarePeer = jid:remove_resource(Peer), lists:foreach( fun(Aff) -> Node = <<(atom_to_binary(Aff, utf8))/binary, $-, (p1_rand:get_string())/binary>>, create_node(Config, Node, default_node_config(Config)), #ps_item{id = I} = publish_item(Config, Node), ok = set_affiliations(Config, Node, [{Peer, Aff}]), Affs = get_affiliations(Config, Node), case lists:keyfind(BarePeer, #ps_affiliation.jid, Affs) of false when Aff == none -> ok; #ps_affiliation{type = Aff} -> ok end, put_event(Config, {Aff, Node, I}), wait_for_slave(Config), delete_node(Config, Node) end, [outcast, none, member, publish_only, publisher, owner]), put_event(Config, disconnect), disconnect(Config). affiliations_slave(Config) -> affiliations_slave(Config, get_event(Config)). affiliations_slave(Config, {outcast, Node, ItemID}) -> #stanza_error{reason = 'forbidden'} = subscribe_node(Config, Node), #stanza_error{} = unsubscribe_node(Config, Node), #stanza_error{reason = 'forbidden'} = get_items(Config, Node), #stanza_error{reason = 'forbidden'} = publish_item(Config, Node), #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID), #stanza_error{reason = 'forbidden'} = purge_node(Config, Node), #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node), #stanza_error{reason = 'forbidden'} = set_node_config(Config, Node, default_node_config(Config)), #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node), #stanza_error{reason = 'forbidden'} = set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]), #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node), #stanza_error{reason = 'forbidden'} = set_affiliations(Config, Node, [{?config(master, Config), outcast}, {my_jid(Config), owner}]), #stanza_error{reason = 'forbidden'} = delete_node(Config, Node), wait_for_master(Config), affiliations_slave(Config, get_event(Config)); affiliations_slave(Config, {none, Node, ItemID}) -> #ps_subscription{type = subscribed} = subscribe_node(Config, Node), ok = unsubscribe_node(Config, Node), %% This violates the affiliation char from section 4.1 [_|_] = get_items(Config, Node), #stanza_error{reason = 'forbidden'} = publish_item(Config, Node), #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID), #stanza_error{reason = 'forbidden'} = purge_node(Config, Node), #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node), #stanza_error{reason = 'forbidden'} = set_node_config(Config, Node, default_node_config(Config)), #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node), #stanza_error{reason = 'forbidden'} = set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]), #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node), #stanza_error{reason = 'forbidden'} = set_affiliations(Config, Node, [{?config(master, Config), outcast}, {my_jid(Config), owner}]), #stanza_error{reason = 'forbidden'} = delete_node(Config, Node), wait_for_master(Config), affiliations_slave(Config, get_event(Config)); affiliations_slave(Config, {member, Node, ItemID}) -> #ps_subscription{type = subscribed} = subscribe_node(Config, Node), ok = unsubscribe_node(Config, Node), [_|_] = get_items(Config, Node), #stanza_error{reason = 'forbidden'} = publish_item(Config, Node), #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID), #stanza_error{reason = 'forbidden'} = purge_node(Config, Node), #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node), #stanza_error{reason = 'forbidden'} = set_node_config(Config, Node, default_node_config(Config)), #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node), #stanza_error{reason = 'forbidden'} = set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]), #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node), #stanza_error{reason = 'forbidden'} = set_affiliations(Config, Node, [{?config(master, Config), outcast}, {my_jid(Config), owner}]), #stanza_error{reason = 'forbidden'} = delete_node(Config, Node), wait_for_master(Config), affiliations_slave(Config, get_event(Config)); affiliations_slave(Config, {publish_only, Node, ItemID}) -> #stanza_error{reason = 'forbidden'} = subscribe_node(Config, Node), #stanza_error{} = unsubscribe_node(Config, Node), #stanza_error{reason = 'forbidden'} = get_items(Config, Node), #ps_item{id = _MyItemID} = publish_item(Config, Node), %% BUG: This should be fixed %% ?match(ok, delete_item(Config, Node, MyItemID)), #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID), #stanza_error{reason = 'forbidden'} = purge_node(Config, Node), #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node), #stanza_error{reason = 'forbidden'} = set_node_config(Config, Node, default_node_config(Config)), #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node), #stanza_error{reason = 'forbidden'} = set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]), #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node), #stanza_error{reason = 'forbidden'} = set_affiliations(Config, Node, [{?config(master, Config), outcast}, {my_jid(Config), owner}]), #stanza_error{reason = 'forbidden'} = delete_node(Config, Node), wait_for_master(Config), affiliations_slave(Config, get_event(Config)); affiliations_slave(Config, {publisher, Node, _ItemID}) -> #ps_subscription{type = subscribed} = subscribe_node(Config, Node), ok = unsubscribe_node(Config, Node), [_|_] = get_items(Config, Node), #ps_item{id = MyItemID} = publish_item(Config, Node), ok = delete_item(Config, Node, MyItemID), %% BUG: this should be fixed %% #stanza_error{reason = 'forbidden'} = delete_item(Config, Node, ItemID), #stanza_error{reason = 'forbidden'} = purge_node(Config, Node), #stanza_error{reason = 'forbidden'} = get_node_config(Config, Node), #stanza_error{reason = 'forbidden'} = set_node_config(Config, Node, default_node_config(Config)), #stanza_error{reason = 'forbidden'} = get_subscriptions(Config, Node), #stanza_error{reason = 'forbidden'} = set_subscriptions(Config, Node, [{my_jid(Config), subscribed}]), #stanza_error{reason = 'forbidden'} = get_affiliations(Config, Node), #stanza_error{reason = 'forbidden'} = set_affiliations(Config, Node, [{?config(master, Config), outcast}, {my_jid(Config), owner}]), #stanza_error{reason = 'forbidden'} = delete_node(Config, Node), wait_for_master(Config), affiliations_slave(Config, get_event(Config)); affiliations_slave(Config, {owner, Node, ItemID}) -> MyJID = my_jid(Config), Peer = ?config(master, Config), #ps_subscription{type = subscribed} = subscribe_node(Config, Node), ok = unsubscribe_node(Config, Node), [_|_] = get_items(Config, Node), #ps_item{id = MyItemID} = publish_item(Config, Node), ok = delete_item(Config, Node, MyItemID), ok = delete_item(Config, Node, ItemID), ok = purge_node(Config, Node), [_|_] = get_node_config(Config, Node), ok = set_node_config(Config, Node, default_node_config(Config)), ok = set_subscriptions(Config, Node, []), [] = get_subscriptions(Config, Node), ok = set_affiliations(Config, Node, [{Peer, outcast}, {MyJID, owner}]), [_, _] = get_affiliations(Config, Node), ok = delete_node(Config, Node), wait_for_master(Config), affiliations_slave(Config, get_event(Config)); affiliations_slave(Config, disconnect) -> disconnect(Config). authorize_master(Config) -> send(Config, #presence{}), #presence{} = recv_presence(Config), Peer = ?config(slave, Config), PJID = pubsub_jid(Config), NodeConfig = set_opts(default_node_config(Config), [{access_model, authorize}]), Node = ?config(pubsub_node, Config), Node = create_node(Config, Node, NodeConfig), wait_for_slave(Config), #message{sub_els = [#xdata{fields = F1}]} = recv_message(Config), C1 = pubsub_subscribe_authorization:decode(F1), Node = proplists:get_value(node, C1), Peer = proplists:get_value(subscriber_jid, C1), %% Deny it at first Deny = #xdata{type = submit, fields = pubsub_subscribe_authorization:encode( [{node, Node}, {subscriber_jid, Peer}, {allow, false}])}, send(Config, #message{to = PJID, sub_els = [Deny]}), %% We should not have any subscriptions [] = get_subscriptions(Config, Node), wait_for_slave(Config), #message{sub_els = [#xdata{fields = F2}]} = recv_message(Config), C2 = pubsub_subscribe_authorization:decode(F2), Node = proplists:get_value(node, C2), Peer = proplists:get_value(subscriber_jid, C2), %% Now we accept is as the peer is very insisting ;) Approve = #xdata{type = submit, fields = pubsub_subscribe_authorization:encode( [{node, Node}, {subscriber_jid, Peer}, {allow, true}])}, send(Config, #message{to = PJID, sub_els = [Approve]}), wait_for_slave(Config), delete_node(Config, Node), disconnect(Config). authorize_slave(Config) -> Node = ?config(pubsub_node, Config), MyJID = my_jid(Config), wait_for_master(Config), #ps_subscription{type = pending} = subscribe_node(Config, Node), %% We're denied at first #message{ sub_els = [#ps_event{ subscription = #ps_subscription{type = none, jid = MyJID}}]} = recv_message(Config), wait_for_master(Config), #ps_subscription{type = pending} = subscribe_node(Config, Node), %% Now much better! #message{ sub_els = [#ps_event{ subscription = #ps_subscription{type = subscribed, jid = MyJID}}]} = recv_message(Config), wait_for_master(Config), disconnect(Config). %%%=================================================================== %%% Internal functions %%%=================================================================== single_test(T) -> list_to_atom("pubsub_" ++ atom_to_list(T)). master_slave_test(T) -> {list_to_atom("pubsub_" ++ atom_to_list(T)), [parallel], [list_to_atom("pubsub_" ++ atom_to_list(T) ++ "_master"), list_to_atom("pubsub_" ++ atom_to_list(T) ++ "_slave")]}. set_opts(Config, Options) -> lists:foldl( fun({Opt, Val}, Acc) -> lists:keystore(Opt, 1, Acc, {Opt, Val}) end, Config, Options). create_node(Config, Node) -> create_node(Config, Node, undefined). create_node(Config, Node, Options) -> PJID = pubsub_jid(Config), NodeConfig = if is_list(Options) -> #xdata{type = submit, fields = pubsub_node_config:encode(Options)}; true -> undefined end, case send_recv(Config, #iq{type = set, to = PJID, sub_els = [#pubsub{create = Node, configure = {<<>>, NodeConfig}}]}) of #iq{type = result, sub_els = [#pubsub{create = NewNode}]} -> NewNode; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. delete_node(Config, Node) -> PJID = pubsub_jid(Config), case send_recv(Config, #iq{type = set, to = PJID, sub_els = [#pubsub_owner{delete = {Node, <<>>}}]}) of #iq{type = result, sub_els = []} -> ok; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. purge_node(Config, Node) -> PJID = pubsub_jid(Config), case send_recv(Config, #iq{type = set, to = PJID, sub_els = [#pubsub_owner{purge = Node}]}) of #iq{type = result, sub_els = []} -> ok; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. get_default_node_config(Config) -> PJID = pubsub_jid(Config), case send_recv(Config, #iq{type = get, to = PJID, sub_els = [#pubsub_owner{default = {<<>>, undefined}}]}) of #iq{type = result, sub_els = [#pubsub_owner{default = {<<>>, NodeConfig}}]} -> pubsub_node_config:decode(NodeConfig#xdata.fields); #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. get_node_config(Config, Node) -> PJID = pubsub_jid(Config), case send_recv(Config, #iq{type = get, to = PJID, sub_els = [#pubsub_owner{configure = {Node, undefined}}]}) of #iq{type = result, sub_els = [#pubsub_owner{configure = {Node, NodeConfig}}]} -> pubsub_node_config:decode(NodeConfig#xdata.fields); #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. set_node_config(Config, Node, Options) -> PJID = pubsub_jid(Config), NodeConfig = #xdata{type = submit, fields = pubsub_node_config:encode(Options)}, case send_recv(Config, #iq{type = set, to = PJID, sub_els = [#pubsub_owner{configure = {Node, NodeConfig}}]}) of #iq{type = result, sub_els = []} -> ok; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. publish_item(Config, Node) -> PJID = pubsub_jid(Config), ItemID = p1_rand:get_string(), Item = #ps_item{id = ItemID, sub_els = [xmpp:encode(#presence{id = ItemID})]}, case send_recv(Config, #iq{type = set, to = PJID, sub_els = [#pubsub{publish = #ps_publish{ node = Node, items = [Item]}}]}) of #iq{type = result, sub_els = [#pubsub{publish = #ps_publish{ node = Node, items = [#ps_item{id = ItemID}]}}]} -> Item; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. get_items(Config, Node) -> PJID = pubsub_jid(Config), case send_recv(Config, #iq{type = get, to = PJID, sub_els = [#pubsub{items = #ps_items{node = Node}}]}) of #iq{type = result, sub_els = [#pubsub{items = #ps_items{node = Node, items = Items}}]} -> Items; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. delete_item(Config, Node, I) -> PJID = pubsub_jid(Config), case send_recv(Config, #iq{type = set, to = PJID, sub_els = [#pubsub{retract = #ps_retract{ node = Node, items = [#ps_item{id = I}]}}]}) of #iq{type = result, sub_els = []} -> ok; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. subscribe_node(Config, Node) -> PJID = pubsub_jid(Config), MyJID = my_jid(Config), case send_recv(Config, #iq{type = set, to = PJID, sub_els = [#pubsub{subscribe = #ps_subscribe{ node = Node, jid = MyJID}}]}) of #iq{type = result, sub_els = [#pubsub{ subscription = #ps_subscription{ node = Node, jid = MyJID} = Sub}]} -> Sub; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. unsubscribe_node(Config, Node) -> PJID = pubsub_jid(Config), MyJID = my_jid(Config), case send_recv(Config, #iq{type = set, to = PJID, sub_els = [#pubsub{ unsubscribe = #ps_unsubscribe{ node = Node, jid = MyJID}}]}) of #iq{type = result, sub_els = []} -> ok; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. get_affiliations(Config) -> PJID = pubsub_jid(Config), case send_recv(Config, #iq{type = get, to = PJID, sub_els = [#pubsub{affiliations = {<<>>, []}}]}) of #iq{type = result, sub_els = [#pubsub{affiliations = {<<>>, Affs}}]} -> Affs; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. get_affiliations(Config, Node) -> PJID = pubsub_jid(Config), case send_recv(Config, #iq{type = get, to = PJID, sub_els = [#pubsub_owner{affiliations = {Node, []}}]}) of #iq{type = result, sub_els = [#pubsub_owner{affiliations = {Node, Affs}}]} -> Affs; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. set_affiliations(Config, Node, JTs) -> PJID = pubsub_jid(Config), Affs = [#ps_affiliation{jid = J, type = T} || {J, T} <- JTs], case send_recv(Config, #iq{type = set, to = PJID, sub_els = [#pubsub_owner{affiliations = {Node, Affs}}]}) of #iq{type = result, sub_els = []} -> ok; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. get_subscriptions(Config) -> PJID = pubsub_jid(Config), case send_recv(Config, #iq{type = get, to = PJID, sub_els = [#pubsub{subscriptions = {<<>>, []}}]}) of #iq{type = result, sub_els = [#pubsub{subscriptions = {<<>>, Subs}}]} -> Subs; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. get_subscriptions(Config, Node) -> PJID = pubsub_jid(Config), case send_recv(Config, #iq{type = get, to = PJID, sub_els = [#pubsub_owner{subscriptions = {Node, []}}]}) of #iq{type = result, sub_els = [#pubsub_owner{subscriptions = {Node, Subs}}]} -> Subs; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. set_subscriptions(Config, Node, JTs) -> PJID = pubsub_jid(Config), Subs = [#ps_subscription{jid = J, type = T} || {J, T} <- JTs], case send_recv(Config, #iq{type = set, to = PJID, sub_els = [#pubsub_owner{subscriptions = {Node, Subs}}]}) of #iq{type = result, sub_els = []} -> ok; #iq{type = error} = IQ -> xmpp:get_subtag(IQ, #stanza_error{}) end. default_node_config(Config) -> [{title, ?config(pubsub_node_title, Config)}, {notify_delete, false}, {send_last_published_item, never}].