1: %%%===================================================================
    2: %%% @copyright (C) 2015, Erlang Solutions Ltd.
    3: %%% @doc Suite for testing pubsub features as described in XEP-0060
    4: %%% @end
    5: %%%===================================================================
    6: 
    7: -module(pubsub_SUITE).
    8: 
    9: -include_lib("escalus/include/escalus.hrl").
   10: -include_lib("common_test/include/ct.hrl").
   11: -include_lib("escalus/include/escalus_xmlns.hrl").
   12: -include_lib("exml/include/exml.hrl").
   13: -include_lib("exml/include/exml_stream.hrl").
   14: -include_lib("eunit/include/eunit.hrl").
   15: 
   16: -export([suite/0, all/0, groups/0]).
   17: -export([init_per_suite/1, end_per_suite/1,
   18:          init_per_group/2, end_per_group/2,
   19:          init_per_testcase/2, end_per_testcase/2]).
   20: 
   21: -export([
   22:          discover_features_test/1,
   23:          discover_service_features_test/1,
   24:          discover_sm_features_test/1,
   25:          discover_nodes_test/1,
   26:          create_delete_node_test/1,
   27:          subscribe_unsubscribe_test/1,
   28:          subscribe_options_test/1,
   29:          subscribe_options_deliver_option_test/1,
   30:          subscribe_options_separate_request_test/1,
   31:          publish_test/1,
   32:          publish_with_max_items_test/1,
   33:          publish_with_existing_id_test/1,
   34:          notify_test/1,
   35:          request_all_items_test/1,
   36:          request_particular_item_test/1,
   37:          retract_test/1,
   38:          retract_when_user_goes_offline_test/1,
   39:          purge_all_items_test/1,
   40:          publish_only_retract_items_scope_test/1
   41:         ]).
   42: 
   43: -export([
   44:          max_subscriptions_test/1
   45:         ]).
   46: 
   47: -export([
   48:          retrieve_configuration_test/1,
   49:          set_configuration_test/1,
   50:          notify_config_test/1,
   51:          disable_notifications_test/1,
   52:          disable_payload_test/1,
   53:          disable_persist_items_test/1,
   54:          notify_only_available_users_test/1,
   55:          notify_unavailable_user_test/1,
   56:          send_last_published_item_test/1,
   57:          send_last_published_item_no_items_test/1
   58:         ]).
   59: 
   60: -export([
   61:          get_affiliations_test/1,
   62:          add_publisher_and_member_test/1,
   63:          swap_owners_test/1,
   64:          deny_no_owner_test/1
   65:         ]).
   66: 
   67: -export([
   68:          retrieve_user_subscriptions_test/1,
   69:          retrieve_node_subscriptions_test/1,
   70:          modify_node_subscriptions_test/1,
   71:          process_subscription_requests_test/1,
   72:          retrieve_pending_subscription_requests_test/1
   73:         ]).
   74: 
   75: -export([
   76:          create_delete_collection_test/1,
   77:          subscribe_unsubscribe_collection_test/1,
   78:          collection_delete_makes_leaf_parentless/1,
   79:          notify_collection_test/1,
   80:          notify_collection_leaf_and_item_test/1,
   81:          notify_collection_bare_jid_test/1,
   82:          notify_collection_and_leaf_test/1,
   83:          notify_collection_and_leaf_same_user_test/1,
   84:          notify_collections_with_same_leaf_test/1,
   85:          notify_nested_collections_test/1,
   86:          retrieve_subscriptions_collection_test/1,
   87:          discover_top_level_nodes_test/1,
   88:          discover_child_nodes_test/1,
   89:          request_all_items_leaf_test/1
   90:         ]).
   91: 
   92: -export([
   93:          disable_notifications_leaf_test/1,
   94:          disable_payload_leaf_test/1,
   95:          disable_persist_items_leaf_test/1
   96:         ]).
   97: 
   98: -export([
   99:          debug_get_items_test/1,
  100:          debug_get_item_test/1
  101:         ]).
  102: 
  103: -export([
  104:          can_create_node_with_existing_parent_path/1,
  105:          cant_create_node_with_missing_parent_path/1,
  106:          disco_node_children_by_path_prefix/1,
  107:          deleting_parent_path_deletes_children/1
  108:         ]).
  109: 
  110: %% Disabled tests - broken support in mod_pubsub
  111: -export([
  112:          disable_payload_and_persist_test/1,
  113:          disable_delivery_test/1
  114:         ]).
  115: 
  116: -export([
  117:          get_item_with_publisher_option_test/1,
  118:          receive_item_notification_with_publisher_option_test/1
  119:         ]).
  120: -import(pubsub_tools, [pubsub_node/0,
  121:                        domain/0,
  122:                        node_addr/0,
  123:                        encode_group_name/2,
  124:                        decode_group_name/1]).
  125: -import(distributed_helper, [mim/0,
  126:                              require_rpc_nodes/1,
  127:                              subhost_pattern/1,
  128:                              rpc/4]).
  129: 
  130: %%--------------------------------------------------------------------
  131: %% Suite configuration
  132: %%--------------------------------------------------------------------
  133: 
  134: suite() ->
  135:     require_rpc_nodes([mim]) ++ escalus:suite().
  136: 
  137: all() ->
  138:     [{group, GN} || {GN, _, _} <- groups()].
  139: 
  140: groups() ->
  141:     lists:flatmap(
  142:       fun(NodeTree) ->
  143:               [ {encode_group_name(BaseGroup, NodeTree), Opts, Cases}
  144:                 || {BaseGroup, Opts, Cases} <- base_groups(),
  145:                    group_is_compatible(BaseGroup, NodeTree) ]
  146:       end, [<<"dag">>, <<"tree">>]).
  147: 
  148: % nodetree_tree doesn't support collections by XEP
  149: % It uses implicit collections by path in nodes' names
  150: group_is_compatible(collection, <<"tree">>) -> false;
  151: group_is_compatible(collection_config, <<"tree">>) -> false;
  152: group_is_compatible(hometree_specific, OnlyNodetreeTree) -> OnlyNodetreeTree =:= <<"tree">>;
  153: group_is_compatible(_, _) -> true.
  154: 
  155: base_groups() ->
  156:     G = [{basic, [parallel], basic_tests()},
  157:          {service_config, [parallel], service_config_tests()},
  158:          {node_config, [parallel], node_config_tests()},
  159:          {node_affiliations, [parallel], node_affiliations_tests()},
  160:          {manage_subscriptions, [parallel], manage_subscriptions_tests()},
  161:          {collection, [sequence], collection_tests()},
  162:          {collection_config, [parallel], collection_config_tests()},
  163:          {debug_calls, [parallel], debug_calls_tests()},
  164:          {pubsub_item_publisher_option, [parallel], pubsub_item_publisher_option_tests()},
  165:          {hometree_specific, [sequence], hometree_specific_tests()},
  166:          {last_item_cache, [parallel], last_item_cache_tests()}
  167:         ],
  168:     ct_helper:repeat_all_until_all_ok(G).
  169: 
  170: basic_tests() ->
  171:     [
  172:      discover_features_test,
  173:      discover_service_features_test,
  174:      discover_sm_features_test,
  175:      discover_nodes_test,
  176:      create_delete_node_test,
  177:      subscribe_unsubscribe_test,
  178:      subscribe_options_test,
  179:      subscribe_options_deliver_option_test,
  180:      subscribe_options_separate_request_test,
  181:      publish_test,
  182:      publish_with_max_items_test,
  183:      publish_with_existing_id_test,
  184:      notify_test,
  185:      request_all_items_test,
  186:      request_particular_item_test,
  187:      retract_test,
  188:      retract_when_user_goes_offline_test,
  189:      purge_all_items_test,
  190:      publish_only_retract_items_scope_test
  191:     ].
  192: 
  193: service_config_tests() ->
  194:     [
  195:      max_subscriptions_test
  196:     ].
  197: 
  198: node_config_tests() ->
  199:     [
  200:      retrieve_configuration_test,
  201:      set_configuration_test,
  202:      notify_config_test,
  203:      disable_notifications_test,
  204:      disable_payload_test,
  205:      disable_persist_items_test,
  206:      notify_only_available_users_test,
  207:      notify_unavailable_user_test,
  208:      send_last_published_item_test
  209:     ].
  210: 
  211: node_affiliations_tests() ->
  212:     [
  213:      get_affiliations_test,
  214:      add_publisher_and_member_test,
  215:      swap_owners_test,
  216:      deny_no_owner_test
  217:     ].
  218: 
  219: manage_subscriptions_tests() ->
  220:     [
  221:      retrieve_user_subscriptions_test,
  222:      retrieve_node_subscriptions_test,
  223:      modify_node_subscriptions_test,
  224:      process_subscription_requests_test,
  225:      retrieve_pending_subscription_requests_test
  226:     ].
  227: 
  228: collection_tests() ->
  229:     [
  230:      create_delete_collection_test,
  231:      subscribe_unsubscribe_collection_test,
  232:      collection_delete_makes_leaf_parentless,
  233:      notify_collection_test,
  234:      notify_collection_leaf_and_item_test,
  235:      notify_collection_bare_jid_test,
  236:      notify_collection_and_leaf_test,
  237:      notify_collection_and_leaf_same_user_test,
  238:      notify_collections_with_same_leaf_test,
  239:      notify_nested_collections_test,
  240:      retrieve_subscriptions_collection_test,
  241:      discover_top_level_nodes_test,
  242:      discover_child_nodes_test,
  243:      request_all_items_leaf_test
  244:     ].
  245: 
  246: collection_config_tests() ->
  247:     [
  248:      disable_notifications_leaf_test,
  249:      disable_payload_leaf_test,
  250:      disable_persist_items_leaf_test
  251:     ].
  252: 
  253: debug_calls_tests() ->
  254:     [
  255:      debug_get_items_test,
  256:      debug_get_item_test
  257:     ].
  258: 
  259: pubsub_item_publisher_option_tests() ->
  260:     [
  261:      get_item_with_publisher_option_test,
  262:      receive_item_notification_with_publisher_option_test
  263:     ].
  264: 
  265: hometree_specific_tests() ->
  266:     [
  267:      can_create_node_with_existing_parent_path,
  268:      cant_create_node_with_missing_parent_path,
  269:      disco_node_children_by_path_prefix,
  270:      deleting_parent_path_deletes_children
  271:     ].
  272: 
  273: last_item_cache_tests() ->
  274:     [
  275:      send_last_published_item_test,
  276:      send_last_published_item_no_items_test,
  277:      purge_all_items_test
  278:     ].
  279: %%--------------------------------------------------------------------
  280: %% Init & teardown
  281: %%--------------------------------------------------------------------
  282: 
  283: init_per_suite(Config) ->
  284:     escalus:init_per_suite(Config).
  285: 
  286: end_per_suite(Config) ->
  287:     escalus_fresh:clean(),
  288:     escalus:end_per_suite(Config).
  289: 
  290: init_per_group(ComplexName, Config) ->
  291:     DecodedGroupName = decode_group_name(ComplexName),
  292:     ExtraOptions = extra_options_by_group_name(DecodedGroupName),
  293:     Config2 = dynamic_modules:save_modules(domain(), Config),
  294:     dynamic_modules:ensure_modules(domain(), required_modules(ExtraOptions)),
  295:     Config2.
  296: 
  297: extra_options_by_group_name(#{ node_tree := NodeTree,
  298:                                base_name := pubsub_item_publisher_option }) ->
  299:     [{nodetree, NodeTree},
  300:      {plugins, [plugin_by_nodetree(NodeTree)]},
  301:      {item_publisher, true}];
  302: extra_options_by_group_name(#{ node_tree := NodeTree,
  303:                                base_name := hometree_specific }) ->
  304:     [{nodetree, NodeTree},
  305:      {plugins, [<<"hometree">>]}];
  306: extra_options_by_group_name(#{ node_tree := NodeTree,
  307:                                base_name := last_item_cache}) ->
  308:     [{nodetree, NodeTree},
  309:      {plugins, [plugin_by_nodetree(NodeTree)]},
  310:      {last_item_cache, mongoose_helper:mnesia_or_rdbms_backend()}];
  311: extra_options_by_group_name(#{ node_tree := NodeTree }) ->
  312:     [{nodetree, NodeTree},
  313:      {plugins, [plugin_by_nodetree(NodeTree)]}].
  314: 
  315: plugin_by_nodetree(<<"dag">>) -> <<"dag">>;
  316: plugin_by_nodetree(<<"tree">>) -> <<"flat">>.
  317: 
  318: end_per_group(_GroupName, Config) ->
  319:     dynamic_modules:restore_modules(Config).
  320: 
  321: init_per_testcase(notify_unavailable_user_test, _Config) ->
  322:     {skip, "mod_offline does not store events"};
  323: init_per_testcase(max_subscriptions_test, Config) ->
  324:     MaxSubs = lookup_service_option(domain(), max_subscriptions_node),
  325:     set_service_option(domain(), max_subscriptions_node, 1),
  326:     init_per_testcase(generic, [{max_subscriptions_node, MaxSubs} | Config]);
  327: init_per_testcase(_TestName, Config) ->
  328:     escalus:init_per_testcase(_TestName, Config).
  329: 
  330: end_per_testcase(max_subscriptions_test, Config1) ->
  331:     {value, {_, OldMaxSubs}, Config2} = lists:keytake(max_subscriptions_node, 1, Config1),
  332:     set_service_option(domain(), max_subscriptions_node, OldMaxSubs),
  333:     end_per_testcase(generic, Config2);
  334: end_per_testcase(TestName, Config) ->
  335:     escalus:end_per_testcase(TestName, Config).
  336: 
  337: %%--------------------------------------------------------------------
  338: %% Test cases for XEP-0060
  339: %% Comments in test cases refer to sections is the XEP
  340: %%--------------------------------------------------------------------
  341: 
  342: %%--------------------------------------------------------------------
  343: %% Main PubSub cases
  344: %%--------------------------------------------------------------------
  345: 
  346: discover_features_test(Config) ->
  347:     escalus:fresh_story(
  348:       Config,
  349:       [{alice, 1}],
  350:       fun(Alice) ->
  351:               Server = escalus_client:server(Alice),
  352:               escalus:send(Alice, escalus_stanza:disco_info(Server)),
  353:               Stanza = escalus:wait_for_stanza(Alice),
  354:               escalus:assert(has_feature, [?NS_PUBSUB], Stanza)
  355:       end).
  356: 
  357: discover_service_features_test(Config) ->
  358:     escalus:fresh_story(
  359:       Config,
  360:       [{alice, 1}],
  361:       fun(Alice) ->
  362:               escalus:send(Alice, escalus_stanza:disco_info(node_addr())),
  363:               Stanza = escalus:wait_for_stanza(Alice),
  364:               escalus:assert(has_identity, [<<"pubsub">>, <<"service">>], Stanza),
  365:               escalus:assert(has_feature, [?NS_PUBSUB], Stanza)
  366:       end).
  367: 
  368: discover_sm_features_test(Config) ->
  369:     escalus:fresh_story(Config, [{alice, 1}],
  370:         fun(Alice) ->
  371:                 AliceJid = escalus_client:short_jid(Alice),
  372:                 escalus:send(Alice, escalus_stanza:disco_info(AliceJid)),
  373:                 Stanza = escalus:wait_for_stanza(Alice),
  374:                 %% The feature shouldn't be present when PEP is disabled
  375:                 ?assertNot(escalus_pred:has_feature(?NS_PUBSUB, Stanza)),
  376:                 escalus:assert(is_stanza_from, [AliceJid], Stanza)
  377:         end).
  378: 
  379: discover_nodes_test(Config) ->
  380:     escalus:fresh_story(
  381:       Config,
  382:       [{alice, 1}, {bob, 1}],
  383:       fun(Alice, Bob) ->
  384:               %% Request:  5.2 Ex.9  Entity asks service for all first-level nodes
  385:               %% Response:     Ex.10 Service returns all first-level nodes
  386:               %% it shouldn't contain the Node which will be created in a moment
  387:               {_, NodeName} = Node = pubsub_node(),
  388:               pubsub_tools:discover_nodes(Bob, node_addr(), [{expected_result, [{no, NodeName}]}]),
  389: 
  390:               pubsub_tools:create_node(Alice, Node, []),
  391:               pubsub_tools:discover_nodes(Bob, node_addr(), [{expected_result, [NodeName]}]),
  392: 
  393:               {_, NodeName2} = Node2 = pubsub_node(),
  394:               pubsub_tools:create_node(Alice, Node2, []),
  395:               pubsub_tools:discover_nodes(
  396:                 Bob, node_addr(), [{expected_result, [NodeName, NodeName2]}]),
  397: 
  398:               pubsub_tools:delete_node(Alice, Node, []),
  399:               pubsub_tools:delete_node(Alice, Node2, [])
  400:       end).
  401: 
  402: create_delete_node_test(Config) ->
  403:     escalus:fresh_story(
  404:       Config,
  405:       [{alice, 1}],
  406:       fun(Alice) ->
  407:               %% Request:  8.1.2 Ex.132 create node with (default) open access model
  408:               %% Response:       Ex.134 success
  409:               %%                        Note: contains node ID although XEP does not require this
  410:               Node = pubsub_node(),
  411:               pubsub_tools:create_node(Alice, Node, []),
  412: 
  413:               %% Request:  8.4.1 Ex.155 owner deletes a node
  414:               %% Response:       Ex.157 success
  415:               pubsub_tools:delete_node(Alice, Node, [])
  416:       end).
  417: 
  418: subscribe_unsubscribe_test(Config) ->
  419:     escalus:fresh_story(
  420:       Config,
  421:       [{alice, 1}, {bob, 1}],
  422:       fun(Alice, Bob) ->
  423:               Node = pubsub_node(),
  424:               pubsub_tools:create_node(Alice, Node, []),
  425: 
  426:               %% Request:  6.1.1 Ex.32 entity subscribes to a node
  427:               %% Response: 6.1.2 Ex.33 success (with subscription ID)
  428:               pubsub_tools:subscribe(Bob, Node, []),
  429: 
  430:               %% Request:  6.2.1 Ex.51 unsubscribe from a node
  431:               %% Response: 6.2.2 Ex.52 success
  432:               pubsub_tools:unsubscribe(Bob, Node, []),
  433: 
  434:               %% Check subscriptions without resources
  435:               pubsub_tools:subscribe(Bob, Node, [{jid_type, bare}]),
  436:               pubsub_tools:unsubscribe(Bob, Node, [{jid_type, bare}]),
  437: 
  438:               pubsub_tools:delete_node(Alice, Node, [])
  439:       end).
  440: 
  441: subscribe_options_test(Config) ->
  442:     escalus:fresh_story(
  443:       Config,
  444:       [{alice, 1}, {bob, 1}, {geralt, 1}],
  445:       fun(Alice, Bob, Geralt) ->
  446:               {_, NodeName} = Node = pubsub_node(),
  447:               pubsub_tools:create_node(Alice, Node, []),
  448: 
  449:               %% 6.3.4.2 Example 62. No such subscriber
  450:               [ pubsub_tools:get_subscription_options(Client, {node_addr(), NodeName},
  451:                                                       [{expected_error_type, <<"modify">>}])
  452:                 || Client <- [Alice, Bob, Geralt] ],
  453: 
  454:               GeraltOpts = [{<<"pubsub#deliver">>, <<"true">>}],
  455:               BobOpts = [{<<"pubsub#deliver">>, <<"false">>}],
  456:               pubsub_tools:subscribe(Geralt, Node, [{config, GeraltOpts}]),
  457:               pubsub_tools:subscribe(Bob, Node, [{config, BobOpts}]),
  458: 
  459:               %% 6.3.2 Example 59. Subscriber requests subscription options form
  460:               pubsub_tools:get_subscription_options(Geralt, {node_addr(), NodeName},
  461:                                                     [{expected_result, GeraltOpts}]),
  462:               pubsub_tools:get_subscription_options(Bob, {node_addr(), NodeName},
  463:                                                     [{expected_result, BobOpts}]),
  464: 
  465:               pubsub_tools:delete_node(Alice, Node, [])
  466:       end).
  467: 
  468: subscribe_options_deliver_option_test(Config) ->
  469:     escalus:fresh_story(
  470:       Config,
  471:       [{alice, 1}, {bob, 1}, {geralt, 1}],
  472:       fun(Alice, Bob, Geralt) ->
  473:               Node = pubsub_node(),
  474:               pubsub_tools:create_node(Alice, Node, []),
  475: 
  476:               pubsub_tools:subscribe(Geralt, Node, [{config, [{<<"pubsub#deliver">>, <<"true">>}]}]),
  477:               pubsub_tools:subscribe(Bob, Node, [{config, [{<<"pubsub#deliver">>, <<"false">>}]}]),
  478: 
  479:               pubsub_tools:publish(Alice, <<"item1">>, Node, []),
  480: 
  481:               %% Geralt should receive a notification
  482:               pubsub_tools:receive_item_notification(Geralt, <<"item1">>, Node, [{expected_result, true}]),
  483:               %% Bob should not receive a notification
  484:               [] = escalus:wait_for_stanzas(Bob, 1, 5000),
  485: 
  486:               pubsub_tools:delete_node(Alice, Node, [])
  487:       end).
  488: 
  489: subscribe_options_separate_request_test(Config) ->
  490:     escalus:fresh_story(
  491:       Config,
  492:       [{alice, 1}, {bob, 1}],
  493:       fun(Alice, Bob) ->
  494:               Clients = [Alice, Bob],
  495:               OptionAfterUpdate = {<<"pubsub#deliver">>, <<"false">>},
  496:               {_, NodeName} = Node = pubsub_node(),
  497:               pubsub_tools:create_node(Alice, Node, []),
  498: 
  499:               pubsub_tools:subscribe(Bob, Node, [{config, [{<<"pubsub#deliver">>, <<"true">>}]}]),
  500:               pubsub_tools:subscribe(Alice, Node, []),
  501: 
  502:               %% 6.3.5 Example 68. Subscriber submits completed options form
  503:               [ pubsub_tools:upsert_subscription_options(
  504:                 Client,
  505:                 {node_addr(), NodeName},
  506:                 [{subscription_options,[OptionAfterUpdate]}, {receive_response, true}])
  507:               || Client <- Clients ],
  508: 
  509:               %% 6.3.2 Example 59. Subscriber requests subscription options form
  510:               [ pubsub_tools:get_subscription_options(Client, {node_addr(), NodeName},
  511:                                                     [{expected_result, [OptionAfterUpdate]}])
  512:               || Client <- Clients ],
  513: 
  514:               pubsub_tools:delete_node(Alice, Node, [])
  515:       end).
  516: 
  517: publish_test(Config) ->
  518:     escalus:fresh_story(
  519:       Config,
  520:       [{alice, 1}],
  521:       fun(Alice) ->
  522:               %% Auto-create enabled by default
  523: 
  524:               %% Request:  7.1.1 Ex.99  publish an item with an ItemID
  525:               %% Response: 7.1.2 Ex.100 success
  526:               Node = pubsub_node(),
  527:               pubsub_tools:publish(Alice, <<"item1">>, Node, []),
  528: 
  529:               pubsub_tools:delete_node(Alice, Node, [])
  530:       end).
  531: 
  532: publish_with_max_items_test(Config) ->
  533:     escalus:fresh_story(
  534:       Config,
  535:       [{alice, 1}, {bob, 1}],
  536:       fun(Alice, Bob) ->
  537:               Node = pubsub_node(),
  538:               NodeConfig = [{<<"pubsub#max_items">>, <<"1">>},
  539:                             {<<"pubsub#notify_retract">>, <<"1">>}],
  540:               pubsub_tools:create_node(Alice, Node, [{config, NodeConfig}]),
  541: 
  542:               pubsub_tools:publish(Alice, <<"item1">>, Node, []),
  543: 
  544:               pubsub_tools:subscribe(Bob, Node, []),
  545: 
  546:               %% mod_pubsub:broadcast_step/1 ensures that a publish notification for a new item
  547:               %% would always arrive before a retraction notification for an old item
  548:               pubsub_tools:publish(Alice, <<"item2">>, Node, []),
  549:               pubsub_tools:receive_item_notification(Bob, <<"item2">>, Node, []),
  550:               verify_item_retract(Node, <<"item1">>, escalus:wait_for_stanza(Bob)),
  551: 
  552:               pubsub_tools:delete_node(Alice, Node, [])
  553:       end).
  554: 
  555: publish_with_existing_id_test(Config) ->
  556:     escalus:fresh_story(
  557:       Config,
  558:       [{alice, 1}],
  559:       fun(Alice) ->
  560:               %% Auto-create enabled by default
  561: 
  562:               %% Request:  7.1.1 Ex.99  publish an item with an ItemID
  563:               %% Response: 7.1.2 Ex.100 success
  564:               Node = pubsub_node(),
  565:               pubsub_tools:publish(Alice, <<"item1">>, Node, []),
  566: 
  567:               pubsub_tools:get_item(Alice, Node, <<"item1">>, [{expected_result, [<<"item1">>]}]),
  568: 
  569:               %% Publish an item with the same id in order to update it
  570:               NewEl = #xmlel{name = <<"entry">>, children = [#xmlel{name = <<"new_entry">>}]},
  571:               pubsub_tools:publish(Alice, <<"item1">>, Node, [{with_payload, NewEl}]),
  572:               pubsub_tools:get_item(Alice, Node, <<"item1">>, [{expected_result,
  573:                                                                 [#{id => <<"item1">>,
  574:                                                                    entry => NewEl}]}]),
  575: 
  576: 
  577:               pubsub_tools:delete_node(Alice, Node, [])
  578: 
  579:       end).
  580: 
  581: notify_test(Config) ->
  582:     escalus:fresh_story(
  583:       Config,
  584:       [{alice, 1}, {bob, 2}, {geralt, 2}],
  585:       fun(Alice, Bob1, Bob2, Geralt1, Geralt2) ->
  586:               Node = pubsub_node(),
  587: 
  588:               % It's a quick win for confirming happy path for this option
  589:               % TODO: Extract into separate test case
  590:               NodeConfig = [{<<"pubsub#presence_based_delivery">>, <<"1">>}],
  591: 
  592:               pubsub_tools:create_node(Alice, Node, [{config, NodeConfig}]),
  593:               pubsub_tools:subscribe(Bob1, Node, []),
  594:               pubsub_tools:subscribe(Geralt1, Node, [{jid_type, bare}]),
  595:               pubsub_tools:publish(Alice, <<"item1">>, Node, []),
  596: 
  597:               %% 7.1.2.1 Ex.101 notification with payload
  598:               %%                Note: message has type 'headline' by default
  599: 
  600:               %% Bob subscribed with resource
  601:               pubsub_tools:receive_item_notification(Bob1, <<"item1">>, Node, []),
  602:               escalus_assert:has_no_stanzas(Bob2),
  603: 
  604:               %% Geralt subscribed without resource
  605:               pubsub_tools:receive_item_notification(Geralt1, <<"item1">>, Node, []),
  606:               pubsub_tools:receive_item_notification(Geralt2, <<"item1">>, Node, []),
  607: 
  608:               pubsub_tools:delete_node(Alice, Node, [])
  609:       end).
  610: 
  611: request_all_items_test(Config) ->
  612:     escalus:fresh_story(
  613:       Config,
  614:       [{alice, 1}, {bob, 1}],
  615:       fun(Alice, Bob) ->
  616:               Node = pubsub_node(),
  617:               pubsub_tools:create_node(Alice, Node, []),
  618:               pubsub_tools:publish(Alice, <<"item1">>, Node, []),
  619:               pubsub_tools:publish(Alice, <<"item2">>, Node, []),
  620: 
  621:               %% Request:  6.5.2 Ex.78 subscriber requests all items
  622:               %% Response: 6.5.3 Ex.79 service returns all items
  623:               pubsub_tools:get_all_items(Bob, Node,
  624:                                          [{expected_result, [<<"item2">>, <<"item1">>]}]),
  625:               %% TODO check ordering (although XEP does not specify this)
  626: 
  627:               pubsub_tools:delete_node(Alice, Node, [])
  628:       end).
  629: 
  630: request_particular_item_test(Config) ->
  631:     escalus:fresh_story(
  632:       Config,
  633:       [{alice, 1}, {bob, 1}],
  634:       fun(Alice, Bob) ->
  635:               Node = pubsub_node(),
  636:               pubsub_tools:create_node(Alice, Node, []),
  637:               pubsub_tools:publish(Alice, <<"item1">>, Node, []),
  638:               pubsub_tools:publish(Alice, <<"item2">>, Node, []),
  639: 
  640:               %% Request:  6.5.8 Ex.78 subscriber requests a particular items
  641:               pubsub_tools:get_item(Bob, Node, <<"item1">>, [{expected_result, [<<"item1">>]}]),
  642: 
  643:               pubsub_tools:delete_node(Alice, Node, [])
  644: 
  645:       end).
  646: 
  647: retract_test(Config) ->
  648:     escalus:fresh_story(
  649:       Config,
  650:       [{alice, 1}, {bob, 1}],
  651:       fun(Alice, Bob) ->
  652:               Node = pubsub_node(),
  653:               pubsub_tools:create_node(Alice, Node, []),
  654:               pubsub_tools:publish(Alice, <<"item1">>, Node, []),
  655:               pubsub_tools:publish(Alice, <<"item2">>, Node, []),
  656: 
  657:               %% Request:  7.2.1 Ex.115 Entity deletes an item from a node
  658:               %% Response: 7.2.2 Ex.116 Service replies with success
  659:               pubsub_tools:retract_item(Alice, Node, <<"item1">>, []),
  660:               pubsub_tools:get_all_items(Bob, Node, [{expected_result, [<<"item2">>]}]),
  661: 
  662:               %% Request:  7.2.1 Ex.115 Entity deletes an item from a node
  663:               %% Response: 7.2.2 Ex.116 Service replies with success
  664:               %% Notification: 7.2.2.1 Ex.117 Subscribers are notified of deletion
  665:               pubsub_tools:set_configuration(Alice, Node,
  666:                                              [{<<"pubsub#notify_retract">>, <<"1">>}], []),
  667:               pubsub_tools:subscribe(Bob, Node, []),
  668:               pubsub_tools:retract_item(Alice, Node, <<"item2">>, []),
  669:               verify_item_retract(Node, <<"item2">>, escalus:wait_for_stanza(Bob)),
  670:               pubsub_tools:get_all_items(Bob, Node, [{expected_result, []}]),
  671: 
  672:               pubsub_tools:delete_node(Alice, Node, [])
  673:       end).
  674: 
  675: retract_when_user_goes_offline_test(Config) ->
  676:     escalus:fresh_story(
  677:       Config,
  678:       [{alice, 1}, {bob, 1}],
  679:       fun(Alice, Bob) ->
  680:               Node = pubsub_node(),
  681:               NodeConfig = [{<<"pubsub#purge_offline">>, <<"1">>},
  682:                             {<<"pubsub#publish_model">>, <<"open">>}],
  683:               pubsub_tools:create_node(Alice, Node, [{config, NodeConfig}]),
  684: 
  685:               pubsub_tools:publish(Alice, <<"item1">>, Node, []),
  686:               pubsub_tools:publish(Bob, <<"item2">>, Node, []),
  687:               pubsub_tools:get_all_items(Alice, Node,
  688:                                          [{expected_result, [<<"item2">>, <<"item1">>]}]),
  689: 
  690:               mongoose_helper:logout_user(Config, Bob),
  691: 
  692:               pubsub_tools:get_all_items(Alice, Node, [{expected_result, [<<"item1">>]}]),
  693: 
  694:               pubsub_tools:delete_node(Alice, Node, [])
  695:       end).
  696: 
  697: purge_all_items_test(Config) ->
  698:     escalus:fresh_story(
  699:       Config,
  700:       [{alice, 1}, {bob, 1}],
  701:       fun(Alice, Bob) ->
  702:               Node = pubsub_node(),
  703:               pubsub_tools:create_node(Alice, Node, []),
  704:               pubsub_tools:publish(Alice, <<"item1">>, Node, []),
  705:               pubsub_tools:publish(Alice, <<"item2">>, Node, []),
  706: 
  707:               %% Response: 8.5.3.2 Ex.165 insufficient privileges
  708:               pubsub_tools:purge_all_items(Bob, Node, [{expected_error_type, <<"auth">>}]),
  709: 
  710:               pubsub_tools:get_all_items(Bob, Node,
  711:                                          [{expected_result, [<<"item2">>, <<"item1">>]}]),
  712: 
  713:               %% Request:  8.5.1 Ex.161 owner purges all items from node
  714:               %% Response: 8.5.2 Ex.162 success
  715:               pubsub_tools:purge_all_items(Alice, Node, []),
  716: 
  717:               pubsub_tools:get_all_items(Bob, Node, [{expected_result, []}]),
  718: 
  719:               pubsub_tools:delete_node(Alice, Node, [])
  720:       end).
  721: 
  722: publish_only_retract_items_scope_test(Config) ->
  723:     escalus:fresh_story(
  724:       Config,
  725:       [{alice, 1}, {bob, 1}],
  726:       fun(Alice, Bob) ->
  727:                 Node = pubsub_node(),
  728:                 pubsub_tools:create_node(Alice, Node, []),
  729:                 AffChange = [{Bob, <<"publish-only">>}],
  730:                 pubsub_tools:set_affiliations(Alice, Node, AffChange, []),
  731: 
  732: 
  733:                 pubsub_tools:publish(Bob, <<"item1">>, Node, []),
  734:                 pubsub_tools:publish(Alice, <<"item2">>, Node, []),
  735: 
  736:                 %% Request:  7.2.1 Ex.116 publish-only sends a retract request for his own item
  737:                 %% Response: 7.2.2 Ex.117 success
  738:                 pubsub_tools:retract_item(Bob, Node, <<"item1">>, []),
  739: 
  740:                 %% Request:  7.2.1 Ex.116 publish-only sends a retract request for someone's else item
  741:                 %% Response: 7.2.3.1 Ex.120 insufficient privileges
  742:                 pubsub_tools:retract_item(Bob, Node, <<"item2">>, [{expected_error_type, <<"auth">>}]),
  743:                 pubsub_tools:get_all_items(Alice, Node, [{expected_result, [<<"item2">>]}]),
  744: 
  745:                 pubsub_tools:delete_node(Alice, Node, [])
  746:       end).
  747: 
  748: 
  749: %%--------------------------------------------------------------------
  750: %% Service config
  751: %%--------------------------------------------------------------------
  752: 
  753: max_subscriptions_test(Config) ->
  754:     escalus:fresh_story(
  755:       Config,
  756:       [{alice, 1}, {bob, 1}],
  757:       fun(Alice, Bob) ->
  758:               Node = pubsub_node(),
  759:               pubsub_tools:create_node(Alice, Node, []),
  760: 
  761:               pubsub_tools:subscribe(Alice, Node, []),
  762:               IQError = pubsub_tools:subscribe(Bob, Node, [{expected_error_type, <<"cancel">>}]),
  763:               is_not_allowed_and_closed(IQError),
  764: 
  765:               pubsub_tools:delete_node(Alice, Node, [])
  766:       end).
  767: 
  768: 
  769: %%--------------------------------------------------------------------
  770: %% Node configuration
  771: %%--------------------------------------------------------------------
  772: 
  773: retrieve_configuration_test(Config) ->
  774:     escalus:fresh_story(
  775:       Config,
  776:       [{alice, 1}],
  777:       fun(Alice) ->
  778:               Node = pubsub_node(),
  779:               pubsub_tools:create_node(Alice, Node, []),
  780: 
  781:               NodeConfig = pubsub_tools:get_configuration(Alice, Node, []),
  782:               verify_config_fields(NodeConfig),
  783: 
  784:               pubsub_tools:delete_node(Alice, Node, [])
  785:       end).
  786: 
  787: set_configuration_test(Config) ->
  788:     escalus:fresh_story(
  789:       Config,
  790:       [{alice, 1}],
  791:       fun(Alice) ->
  792:               Node = pubsub_node(),
  793:               pubsub_tools:create_node(Alice, Node, []),
  794: 
  795:               ValidNodeConfig = node_config_for_test(),
  796:               pubsub_tools:set_configuration(Alice, Node, ValidNodeConfig,
  797:                                              [{response_timeout, 10000}]),
  798:               pubsub_tools:get_configuration(Alice, Node, [{expected_result, ValidNodeConfig}]),
  799: 
  800:               pubsub_tools:delete_node(Alice, Node, [])
  801:       end).
  802: 
  803: notify_config_test(Config) ->
  804:     escalus:fresh_story(
  805:       Config,
  806:       [{alice, 1}, {bob, 1}],
  807:       fun(Alice, Bob) ->
  808:               Node = pubsub_node(),
  809:               pubsub_tools:create_node(
  810:                 Alice, Node, [{config, [{<<"pubsub#notify_config">>, <<"1">>}]}]),
  811:               pubsub_tools:subscribe(Bob, Node, []),
  812: 
  813:               ConfigChange = [{<<"pubsub#title">>, <<"newtitle">>}],
  814:               pubsub_tools:set_configuration(Alice, Node, ConfigChange, []),
  815:               verify_config_event(Node, ConfigChange, escalus:wait_for_stanza(Bob)),
  816: 
  817:               pubsub_tools:delete_node(Alice, Node, [])
  818:       end).
  819: 
  820: disable_notifications_test(Config) ->
  821:     escalus:fresh_story(
  822:       Config,
  823:       [{alice, 1}, {bob, 1}],
  824:       fun(Alice, Bob) ->
  825:               NodeConfig = [{<<"pubsub#deliver_notifications">>, <<"false">>}],
  826:               Node = pubsub_node(),
  827:               pubsub_tools:create_node(Alice, Node, [{config, NodeConfig}]),
  828: 
  829:               pubsub_tools:subscribe(Bob, Node, []),
  830:               pubsub_tools:publish(Alice, <<"item1">>, Node, []),
  831: 
  832:               %% Notifications disabled
  833:               escalus_assert:has_no_stanzas(Bob),
  834: 
  835:               pubsub_tools:delete_node(Alice, Node, [])
  836:       end).
  837: 
  838: disable_payload_test(Config) ->
  839:     escalus:fresh_story(
  840:       Config,
  841:       [{alice, 1}, {bob, 1}],
  842:       fun(Alice, Bob) ->
  843:               %% Notification-Only Persistent Node, see 4.3, table 4
  844:               NodeConfig = [{<<"pubsub#deliver_payloads">>, <<"false">>}],
  845:               Node = pubsub_node(),
  846:               pubsub_tools:create_node(Alice, Node, [{config, NodeConfig}]),
  847: 
  848:               pubsub_tools:subscribe(Bob, Node, []),
  849:               pubsub_tools:publish(Alice, <<"item1">>, Node, []),
  850: 
  851:               %% Payloads disabled
  852:               pubsub_tools:receive_item_notification(Bob, <<"item1">>,
  853:                                                      Node, [{with_payload, false}]),
  854: 
  855:               pubsub_tools:delete_node(Alice, Node, [])
  856:       end).
  857: 
  858: disable_persist_items_test(Config) ->
  859:     escalus:fresh_story(
  860:       Config,
  861:       [{alice, 1}, {bob, 1}],
  862:       fun(Alice, Bob) ->
  863:               %% Payload-Included Transient Node, see 4.3, table 4
  864:               NodeConfig = [{<<"pubsub#persist_items">>, <<"false">>}],
  865:               Node = pubsub_node(),
  866:               pubsub_tools:create_node(Alice, Node, [{config, NodeConfig}]),
  867: 
  868:               pubsub_tools:subscribe(Bob, Node, []),
  869:               pubsub_tools:publish(Alice, <<"item1">>, Node, []),
  870: 
  871:               %% Notifications should work
  872:               pubsub_tools:receive_item_notification(Bob, <<"item1">>, Node, []),
  873: 
  874:               %% No items should be stored
  875:               pubsub_tools:get_all_items(Bob, Node, [{expected_result, []}]),
  876: 
  877:               pubsub_tools:delete_node(Alice, Node, [])
  878:       end).
  879: 
  880: notify_only_available_users_test(Config) ->
  881:     escalus:fresh_story(
  882:       Config,
  883:       [{alice, 1}, {bob, 1}],
  884:       fun(Alice, Bob) ->
  885:               %% Node notifies only available users
  886:               NodeConfig = [{<<"pubsub#presence_based_delivery">>, <<"true">>}],
  887:               Node = pubsub_node(),
  888:               pubsub_tools:create_node(Alice, Node, [{config, NodeConfig}]),
  889: 
  890:               pubsub_tools:subscribe(Bob, Node, [{jid_type, bare}]),
  891: 
  892:               push_helper:become_unavailable(Bob),
  893: 
  894:               %% Item from node 2 not received (blocked by resource-based delivery)
  895:               pubsub_tools:publish(Alice, <<"item2">>, Node, []),
  896:               escalus_assert:has_no_stanzas(Bob),
  897: 
  898:               pubsub_tools:delete_node(Alice, Node, [])
  899:       end).
  900: 
  901: notify_unavailable_user_test(Config) ->
  902:     escalus:fresh_story(
  903:       Config,
  904:       [{alice, 1}, {bob, 1}],
  905:       fun(Alice, Bob) ->
  906:               Node = pubsub_node(),
  907:               pubsub_tools:create_node(Alice, Node, []),
  908: 
  909:               pubsub_tools:subscribe(Bob, Node, [{jid_type, bare}]),
  910: 
  911:               push_helper:become_unavailable(Bob),
  912: 
  913:               %% Receive item from node 1 (also make sure the presence is processed)
  914:               pubsub_tools:publish(Alice, <<"item1">>, Node, []),
  915: 
  916:               escalus_assert:has_no_stanzas(Bob),
  917:               escalus:send(Bob, escalus_stanza:presence(<<"available">>)),
  918:               pubsub_tools:receive_item_notification(Bob, <<"item1">>, Node, []),
  919: 
  920:               pubsub_tools:delete_node(Alice, Node, [])
  921:       end).
  922: 
  923: send_last_published_item_test(Config) ->
  924:     escalus:fresh_story(
  925:       Config,
  926:       [{alice, 1}, {bob, 1}],
  927:       fun(Alice, Bob) ->
  928:               %% Request:  8.1.3 Ex.136 Request a new node with non-default configuration
  929:               %% Response:       Ex.137 Service replies with success
  930:               NodeConfig = [{<<"pubsub#send_last_published_item">>, <<"on_sub_and_presence">>}],
  931:               Node = pubsub_node(),
  932:               pubsub_tools:create_node(Alice, Node, [{config, NodeConfig}]),
  933: 
  934:               pubsub_tools:publish(Alice, <<"item1">>, Node, []),
  935:               pubsub_tools:publish(Alice, <<"item2">>, Node, []),
  936: 
  937:               %% Note: when Bob subscribes, the last item (item2) is sent to him
  938:               %%       6.1.7 Ex.50 service sends last published item
  939:               %%       This is sent BEFORE the response iq stanza
  940:               pubsub_tools:subscribe(Bob, Node, [{receive_response, false}]),
  941:               pubsub_tools:receive_item_notification(Bob, <<"item2">>, Node, []),
  942:               pubsub_tools:receive_subscribe_response(Bob, Node, []),
  943: 
  944:               pubsub_tools:delete_node(Alice, Node, [])
  945:       end).
  946: 
  947: send_last_published_item_no_items_test(Config) ->
  948:     escalus:fresh_story(
  949:       Config,
  950:       [{alice, 1}, {bob, 1}],
  951:       fun(Alice, Bob) ->
  952:               NodeConfig = [{<<"pubsub#send_last_published_item">>, <<"on_sub_and_presence">>}],
  953:               Node = pubsub_node(),
  954:               pubsub_tools:create_node(Alice, Node, [{config, NodeConfig}]),
  955: 
  956:               %% Note: when Bob subscribes, the last item would is sent to him
  957:               pubsub_tools:subscribe(Bob, Node, [{receive_response, false}]),
  958:               escalus_assert:has_no_stanzas(Bob),
  959:               pubsub_tools:delete_node(Alice, Node, [])
  960:       end).
  961: 
  962: 
  963: 
  964: %%--------------------------------------------------------------------
  965: %% Node affiliations management
  966: %%--------------------------------------------------------------------
  967: 
  968: get_affiliations_test(Config) ->
  969:     escalus:fresh_story(
  970:       Config,
  971:       [{alice, 1}],
  972:       fun(Alice) ->
  973:               Node = pubsub_node(),
  974:               pubsub_tools:create_node(Alice, Node, []),
  975: 
  976:               verify_affiliations(pubsub_tools:get_affiliations(Alice, Node, []),
  977:                                   [{Alice, <<"owner">>}]),
  978: 
  979:               pubsub_tools:delete_node(Alice, Node, [])
  980:       end).
  981: 
  982: add_publisher_and_member_test(Config) ->
  983:     escalus:fresh_story(
  984:       Config,
  985:       [{alice, 1}, {bob, 1}, {kate, 1}],
  986:       fun(Alice, Bob, Kate) ->
  987:               Node = pubsub_node(),
  988:               NodeConfig = [{<<"pubsub#access_model">>, <<"whitelist">>},
  989:                             {<<"pubsub#publish_model">>, <<"publishers">>}],
  990:               pubsub_tools:create_node(Alice, Node, [{config, NodeConfig}]),
  991: 
  992:               pubsub_tools:publish(Bob, <<"item1">>, Node, [{expected_error_type, <<"auth">>}]),
  993:               IQError = pubsub_tools:subscribe(Kate, Node, [{expected_error_type, <<"cancel">>}]),
  994:               is_not_allowed_and_closed(IQError),
  995: 
  996:               AffChange = [{Bob, <<"publisher">>}, {Kate, <<"member">>}],
  997:               pubsub_tools:set_affiliations(Alice, Node, AffChange, []),
  998: 
  999:               pubsub_tools:publish(Kate, <<"nope">>, Node, [{expected_error_type, <<"auth">>}]),
 1000:               pubsub_tools:subscribe(Kate, Node, []),
 1001:               pubsub_tools:publish(Bob, <<"item1">>, Node, []),
 1002:               pubsub_tools:receive_item_notification(Kate, <<"item1">>, Node, []),
 1003: 
 1004:               pubsub_tools:delete_node(Alice, Node, [])
 1005:       end).
 1006: 
 1007: swap_owners_test(Config) ->
 1008:     escalus:fresh_story(
 1009:       Config,
 1010:       [{alice, 1}, {bob, 1}],
 1011:       fun(Alice, Bob) ->
 1012:               Node = pubsub_node(),
 1013:               pubsub_tools:create_node(Alice, Node, []),
 1014: 
 1015:               AffChange = [{Bob, <<"owner">>}, {Alice, <<"none">>}],
 1016:               pubsub_tools:set_affiliations(Alice, Node, AffChange, []),
 1017: 
 1018:               pubsub_tools:get_affiliations(Alice, Node, [{expected_error_type, <<"auth">>}]),
 1019:               verify_affiliations(pubsub_tools:get_affiliations(Bob, Node, []),
 1020:                                   [{Bob, <<"owner">>}]),
 1021: 
 1022:               pubsub_tools:delete_node(Bob, Node, [])
 1023:       end).
 1024: 
 1025: deny_no_owner_test(Config) ->
 1026:     escalus:fresh_story(
 1027:       Config,
 1028:       [{alice, 1}],
 1029:       fun(Alice) ->
 1030:               Node = pubsub_node(),
 1031:               pubsub_tools:create_node(Alice, Node, []),
 1032: 
 1033:               AffChange = [{Alice, <<"member">>}],
 1034:               IQError = pubsub_tools:set_affiliations(Alice, Node, AffChange,
 1035:                                                       [{expected_error_type, <<"modify">>}]),
 1036:               verify_returned_affiliation(IQError, Alice, <<"owner">>),
 1037: 
 1038:               verify_affiliations(pubsub_tools:get_affiliations(Alice, Node, []),
 1039:                                   [{Alice, <<"owner">>}]),
 1040: 
 1041:               pubsub_tools:delete_node(Alice, Node, [])
 1042:       end).
 1043: 
 1044: %%--------------------------------------------------------------------
 1045: %% Subscriptions management
 1046: %%--------------------------------------------------------------------
 1047: 
 1048: retrieve_user_subscriptions_test(Config) ->
 1049:     escalus:fresh_story(
 1050:       Config,
 1051:       [{alice, 1}, {bob, 1}],
 1052:       fun(Alice, Bob) ->
 1053:               %% Request:  5.6 Ex.20 Retrieve Subscriptions
 1054:               %% Response:     Ex.22 No Subscriptions
 1055:               pubsub_tools:get_user_subscriptions(Bob, node_addr(), [{expected_result, []}]),
 1056: 
 1057:               {_, NodeName} = Node = pubsub_node(),
 1058:               pubsub_tools:create_node(Alice, Node, []),
 1059:               pubsub_tools:subscribe(Bob, Node, []),
 1060: 
 1061:               %% Ex. 21 Service returns subscriptions
 1062:               Sub = [{NodeName, <<"subscribed">>}],
 1063:               pubsub_tools:get_user_subscriptions(Bob, node_addr(), [{expected_result, Sub}]),
 1064: 
 1065:               {_, NodeName2} = Node2 = pubsub_node(),
 1066:               pubsub_tools:create_node(Alice, Node2, []),
 1067:               pubsub_tools:subscribe(Bob, Node2, []),
 1068: 
 1069:               %% Ex. 21 Service returns subscriptions
 1070:               Subs = [{NodeName, <<"subscribed">>}, {NodeName2, <<"subscribed">>}],
 1071:               pubsub_tools:get_user_subscriptions(Bob, node_addr(), [{expected_result, Subs}]),
 1072: 
 1073:               %% Owner not subscribed automatically
 1074:               pubsub_tools:get_user_subscriptions(Alice, node_addr(), [{expected_result, []}]),
 1075: 
 1076:               pubsub_tools:delete_node(Alice, Node, []),
 1077:               pubsub_tools:delete_node(Alice, Node2, [])
 1078:       end).
 1079: 
 1080: retrieve_node_subscriptions_test(Config) ->
 1081:     escalus:fresh_story(
 1082:       Config,
 1083:       [{alice, 1}, {bob, 1}, {geralt, 1}],
 1084:       fun(Alice, Bob, Geralt) ->
 1085:               Node = pubsub_node(),
 1086:               pubsub_tools:create_node(Alice, Node, []),
 1087: 
 1088:               %% Request:  8.8.1.1 Ex.182 Owner requests all subscriptions
 1089:               %% Response: 8.8.1.2 Ex.183 Service returns list of subscriptions (empty yet)
 1090:               pubsub_tools:get_node_subscriptions(Alice, Node, [{expected_result, []}]),
 1091: 
 1092:               %% Response: 8.8.1.3 Ex.185 Entity is not an owner
 1093:               pubsub_tools:get_node_subscriptions(Bob, Node, [{expected_error_type, <<"auth">>}]),
 1094: 
 1095:               pubsub_tools:subscribe(Bob, Node, []),
 1096:               pubsub_tools:subscribe(Geralt, Node, [{jid_type, bare}]),
 1097: 
 1098:               NodeSubs = [{Bob, full, <<"subscribed">>}, {Geralt, bare, <<"subscribed">>}],
 1099:               pubsub_tools:get_node_subscriptions(Alice, Node, [{expected_result, NodeSubs}]),
 1100: 
 1101:               pubsub_tools:delete_node(Alice, Node, [])
 1102:       end).
 1103: 
 1104: modify_node_subscriptions_test(Config) ->
 1105:     escalus:fresh_story(
 1106:       Config,
 1107:       [{alice, 1}, {bob, 1}, {geralt, 1}],
 1108:       fun(Alice, Bob, Geralt) ->
 1109:               Node = pubsub_node(),
 1110:               pubsub_tools:create_node(Alice, Node, []),
 1111: 
 1112:               %% Request:  8.8.2.1 Ex.187 Owner modifies subscriptions
 1113:               %% Response: 8.8.2.2 Ex.183 Service responds with success
 1114:               pubsub_tools:modify_node_subscriptions(
 1115:                 Alice, [{Bob, full, <<"subscribed">>},
 1116:                         {Geralt, bare, <<"subscribed">>}], Node, []),
 1117: 
 1118:               %% 8.8.4 Ex.194 Notify subscribers
 1119:               pubsub_tools:receive_subscription_notification(Bob, <<"subscribed">>, Node, []),
 1120:               pubsub_tools:receive_subscription_notification(Geralt, <<"subscribed">>,
 1121:                                                              Node, [{jid_type, bare}]),
 1122: 
 1123:               Subs = [{Bob, full, <<"subscribed">>}, {Geralt, bare, <<"subscribed">>}],
 1124:               pubsub_tools:get_node_subscriptions(Alice, Node, [{expected_result, Subs}]),
 1125: 
 1126:               %% Response: 8.8.2.3 Ex.190 Entity is not an owner
 1127:               pubsub_tools:modify_node_subscriptions(Bob, [{Geralt, full, <<"subscribed">>}], Node,
 1128:                                                      [{expected_error_type, <<"auth">>}]),
 1129: 
 1130:               %% Remove Bob, add Geralt's full JID
 1131:               pubsub_tools:modify_node_subscriptions(
 1132:                 Alice, [{Bob, full, <<"none">>},
 1133:                         {Geralt, full, <<"subscribed">>}], Node, []),
 1134: 
 1135:               pubsub_tools:receive_subscription_notification(Bob, <<"none">>, Node, []),
 1136:               pubsub_tools:receive_subscription_notification(Geralt, <<"subscribed">>, Node, []),
 1137: 
 1138:               ModSubs = [{Geralt, bare, <<"subscribed">>}, {Geralt, full, <<"subscribed">>}],
 1139:               pubsub_tools:get_node_subscriptions(Alice, Node, [{expected_result, ModSubs}]),
 1140: 
 1141:               pubsub_tools:delete_node(Alice, Node, [])
 1142:       end).
 1143: 
 1144: process_subscription_requests_test(Config) ->
 1145:     escalus:fresh_story(
 1146:       Config,
 1147:       [{alice, 1}, {bob, 1}, {kate, 1}],
 1148:       fun(Alice, Bob, Kate) ->
 1149:               Node = pubsub_node(),
 1150:               NodeConfig = [{<<"pubsub#access_model">>, <<"authorize">>}],
 1151:               pubsub_tools:create_node(Alice, Node, [{config, NodeConfig}]),
 1152: 
 1153:               pubsub_tools:subscribe(Bob, Node, [{subscription, <<"pending">>}]),
 1154:               BobsRequest = pubsub_tools:receive_subscription_request(Alice, Bob, Node, []),
 1155:               pubsub_tools:subscribe(Kate, Node, [{subscription, <<"pending">>}]),
 1156:               KatesRequest = pubsub_tools:receive_subscription_request(Alice, Kate, Node, []),
 1157: 
 1158:               pubsub_tools:submit_subscription_response(Alice, BobsRequest, Node, true, []),
 1159:               pubsub_tools:receive_subscription_notification(Bob, <<"subscribed">>, Node, []),
 1160:               pubsub_tools:submit_subscription_response(Alice, KatesRequest, Node, false, []),
 1161:               pubsub_tools:receive_subscription_notification(Kate, <<"none">>, Node, []),
 1162: 
 1163:               pubsub_tools:publish(Alice, <<"item1">>, Node, []),
 1164:               pubsub_tools:receive_item_notification(Bob, <<"item1">>, Node, []),
 1165:               [] = escalus:peek_stanzas(Kate),
 1166: 
 1167:               pubsub_tools:delete_node(Alice, Node, [])
 1168:       end).
 1169: 
 1170: retrieve_pending_subscription_requests_test(Config) ->
 1171:     escalus:fresh_story(
 1172:       Config,
 1173:       [{alice, 1}, {bob, 1}, {kate, 1}],
 1174:       fun(Alice, Bob, Kate) ->
 1175:               {NodeAddr, NodeName} = Node = pubsub_node(),
 1176:               NodeConfig = [{<<"pubsub#access_model">>, <<"authorize">>}],
 1177:               pubsub_tools:create_node(Alice, Node, [{config, NodeConfig}]),
 1178: 
 1179:               pubsub_tools:subscribe(Bob, Node, [{subscription, <<"pending">>}]),
 1180:               pubsub_tools:receive_subscription_request(Alice, Bob, Node, []),
 1181:               pubsub_tools:subscribe(Kate, Node, [{subscription, <<"pending">>}]),
 1182:               pubsub_tools:receive_subscription_request(Alice, Kate, Node, []),
 1183: 
 1184:               pubsub_tools:get_pending_subscriptions(Alice, NodeAddr, [NodeName], []),
 1185: 
 1186:               %% TODO: XEP requires IQ result to come before the requests
 1187:               Request = pubsub_tools:get_pending_subscriptions(Alice, Node,
 1188:                                                                [{receive_response, false}]),
 1189:               pubsub_tools:receive_subscription_requests(Alice, [Bob, Kate], Node, []),
 1190:               IQRes = escalus:wait_for_stanza(Alice),
 1191:               escalus:assert(is_iq_result, [Request], IQRes),
 1192: 
 1193:               pubsub_tools:delete_node(Alice, Node, [])
 1194:       end).
 1195: 
 1196: %%--------------------------------------------------------------------
 1197: %% Test cases for XEP-0248
 1198: %% Comments in test cases refer to sections is the XEP
 1199: %%--------------------------------------------------------------------
 1200: 
 1201: pubsub_leaf_name() -> pubsub_tools:rand_name(<<"leaf">>).
 1202: pubsub_leaf() -> {node_addr(), pubsub_leaf_name()}.
 1203: 
 1204: create_delete_collection_test(Config) ->
 1205:     escalus:fresh_story(
 1206:       Config,
 1207:       [{alice, 1}],
 1208:       fun(Alice) ->
 1209:               %% Request:  7.1.1 Ex.18 create collection node
 1210:               %% Response:       Ex.19 success
 1211:               %%                        Note: contains node ID although XEP does not require this
 1212:               CollectionConfig = [{<<"pubsub#node_type">>, <<"collection">>}],
 1213:               Node = pubsub_node(),
 1214:               pubsub_tools:create_node(Alice, Node, [{config, CollectionConfig}]),
 1215: 
 1216:               %% Request:  7.3.1 Ex.30 delete collection node
 1217:               %% Response: 7.3.2 Ex.31 success
 1218:               pubsub_tools:delete_node(Alice, Node, [])
 1219:       end).
 1220: 
 1221: subscribe_unsubscribe_collection_test(Config) ->
 1222:     escalus:fresh_story(
 1223:       Config,
 1224:       [{alice, 1}, {bob, 1}],
 1225:       fun(Alice, Bob) ->
 1226:               CollectionConfig = [{<<"pubsub#node_type">>, <<"collection">>}],
 1227:               Node = pubsub_node(),
 1228:               pubsub_tools:create_node(Alice, Node, [{config, CollectionConfig}]),
 1229: 
 1230:               %% Request:  6.1.1 Ex.10 subscribe (no configuration)
 1231:               %% Response: 6.1.2 Ex.12 success
 1232:               pubsub_tools:subscribe(Bob, Node, []),
 1233: 
 1234:               %% Same as XEP-0060
 1235:               pubsub_tools:unsubscribe(Bob, Node, []),
 1236: 
 1237:               pubsub_tools:delete_node(Alice, Node, [])
 1238:       end).
 1239: 
 1240: collection_delete_makes_leaf_parentless(Config) ->
 1241:     escalus:fresh_story(
 1242:       Config,
 1243:       [{alice, 1}],
 1244:       fun(Alice) ->
 1245:               CollectionConfig = [{<<"pubsub#node_type">>, <<"collection">>}],
 1246:               {_, NodeName} = Node = pubsub_node(),
 1247:               pubsub_tools:create_node(Alice, Node, [{config, CollectionConfig}]),
 1248: 
 1249:               %% XEP-0060, 8.1.2, see 16.4.4 for config details
 1250:               NodeConfig = [{<<"pubsub#collection">>, NodeName}],
 1251:               Leaf = pubsub_leaf(),
 1252:               pubsub_tools:create_node(Alice, Leaf, [{config, NodeConfig}]),
 1253: 
 1254:               pubsub_tools:delete_node(Alice, Node, []),
 1255: 
 1256:               % Leaf becomes an orphan
 1257:               NewNodeConfig = pubsub_tools:get_configuration(Alice, Leaf, []),
 1258:               {_, _, []} = lists:keyfind(<<"pubsub#collection">>, 1, NewNodeConfig)
 1259:       end).
 1260: 
 1261: notify_collection_test(Config) ->
 1262:     escalus:fresh_story(
 1263:       Config,
 1264:       [{alice, 1}, {bob, 1}],
 1265:       fun(Alice, Bob) ->
 1266:               CollectionConfig = [{<<"pubsub#node_type">>, <<"collection">>}],
 1267:               {_, NodeName} = Node = pubsub_node(),
 1268:               pubsub_tools:create_node(Alice, Node, [{config, CollectionConfig}]),
 1269: 
 1270:               NodeConfig = [{<<"pubsub#collection">>, NodeName}],
 1271:               Leaf = pubsub_leaf(),
 1272:               pubsub_tools:create_node(Alice, Leaf, [{config, NodeConfig}]),
 1273:               Leaf2 = pubsub_leaf(),
 1274:               pubsub_tools:create_node(Alice, Leaf2, [{config, NodeConfig}]),
 1275:               pubsub_tools:subscribe(Bob, Node, []),
 1276: 
 1277:               %% Publish to leaf nodes, Bob should get notifications
 1278:               %% 5.3.1.1 Ex.5 Subscriber receives a publish notification from a collection
 1279:               pubsub_tools:publish(Alice, <<"item1">>, Leaf, []),
 1280:               pubsub_tools:receive_item_notification(Bob, <<"item1">>, Leaf, [{collection, Node}]),
 1281:               pubsub_tools:publish(Alice, <<"item2">>, Leaf2, []),
 1282:               pubsub_tools:receive_item_notification(Bob, <<"item2">>, Leaf2, [{collection, Node}]),
 1283: 
 1284:               pubsub_tools:delete_node(Alice, Leaf, []),
 1285:               pubsub_tools:delete_node(Alice, Leaf2, []),
 1286:               pubsub_tools:delete_node(Alice, Node, [])
 1287:       end).
 1288: 
 1289: notify_collection_leaf_and_item_test(Config) ->
 1290:     escalus:fresh_story(
 1291:       Config,
 1292:       [{alice, 1}, {bob, 1}],
 1293:       fun(Alice, Bob) ->
 1294:               CollectionConfig = [{<<"pubsub#node_type">>, <<"collection">>}],
 1295:               {_, NodeName} = Node = pubsub_node(),
 1296:               pubsub_tools:create_node(Alice, Node, [{config, CollectionConfig}]),
 1297: 
 1298:               %% Subscribe before creating the leaf node
 1299:               pubsub_tools:subscribe(Bob, Node, []),
 1300:               NodeConfig = [{<<"pubsub#collection">>, NodeName}],
 1301:               Leaf = pubsub_leaf(),
 1302:               pubsub_tools:create_node(Alice, Leaf, [{config, NodeConfig}]),
 1303: 
 1304:               %% Bob should get a notification for the leaf node creation
 1305:               %% 5.3.1.2 Ex.6 Subscriber receives a creation notification from a collection
 1306:               pubsub_tools:receive_node_creation_notification(Bob, Leaf, []),
 1307: 
 1308:               %% Publish to leaf node, Bob should get notified
 1309:               pubsub_tools:publish(Alice, <<"item1">>, Leaf, []),
 1310:               pubsub_tools:receive_item_notification(Bob, <<"item1">>, Leaf, [{collection, Node}]),
 1311: 
 1312:               pubsub_tools:delete_node(Alice, Leaf, []),
 1313:               pubsub_tools:delete_node(Alice, Node, [])
 1314:       end).
 1315: 
 1316: notify_collection_bare_jid_test(Config) ->
 1317:     escalus:fresh_story(
 1318:       Config,
 1319:       [{alice, 1}, {bob, 2}, {geralt, 2}],
 1320:       fun(Alice, Bob1, Bob2, Geralt1, Geralt2) ->
 1321:               CollectionConfig = [{<<"pubsub#node_type">>, <<"collection">>}],
 1322:               {_, NodeName} = Node = pubsub_node(),
 1323:               pubsub_tools:create_node(Alice, Node, [{config, CollectionConfig}]),
 1324: 
 1325:               NodeConfig = [{<<"pubsub#collection">>, NodeName}],
 1326:               Leaf = pubsub_leaf(),
 1327:               pubsub_tools:create_node(Alice, Leaf, [{config, NodeConfig}]),
 1328:               pubsub_tools:subscribe(Bob1, Node, []),
 1329:               pubsub_tools:subscribe(Geralt1, Node, [{jid_type, bare}]),
 1330:               pubsub_tools:publish(Alice, <<"item1">>, Leaf, []),
 1331: 
 1332:               %% Bob subscribed with resource
 1333:               pubsub_tools:receive_item_notification(Bob1, <<"item1">>, Leaf, [{collection, Node}]),
 1334:               escalus_assert:has_no_stanzas(Bob2),
 1335: 
 1336:               %% Geralt subscribed without resource
 1337:               pubsub_tools:receive_item_notification(Geralt1, <<"item1">>, Leaf,
 1338:                                                      [{collection, Node}]),
 1339:               pubsub_tools:receive_item_notification(Geralt2, <<"item1">>, Leaf,
 1340:                                                      [{collection, Node}]),
 1341: 
 1342:               pubsub_tools:delete_node(Alice, Leaf, []),
 1343:               pubsub_tools:delete_node(Alice, Node, [])
 1344:       end).
 1345: 
 1346: notify_collection_and_leaf_test(Config) ->
 1347:     escalus:fresh_story(
 1348:       Config,
 1349:       [{alice, 1}, {bob, 1}, {geralt, 1}],
 1350:       fun(Alice, Bob, Geralt) ->
 1351:               CollectionConfig = [{<<"pubsub#node_type">>, <<"collection">>}],
 1352:               {_, NodeName} = Node = pubsub_node(),
 1353:               pubsub_tools:create_node(Alice, Node, [{config, CollectionConfig}]),
 1354: 
 1355:               NodeConfig = [{<<"pubsub#collection">>, NodeName}],
 1356:               Leaf = pubsub_leaf(),
 1357:               pubsub_tools:create_node(Alice, Leaf, [{config, NodeConfig}]),
 1358:               pubsub_tools:subscribe(Bob, Node, []),
 1359:               pubsub_tools:subscribe(Geralt, Leaf, []),
 1360: 
 1361:               %% Publish to leaf nodes, Bob and Geralt should get notifications
 1362:               pubsub_tools:publish(Alice, <<"item1">>, Leaf, []),
 1363:               pubsub_tools:receive_item_notification(Bob, <<"item1">>, Leaf,
 1364:                                                      [{collection, Node}]),
 1365:               pubsub_tools:receive_item_notification(Geralt, <<"item1">>, Leaf,
 1366:                                                      [no_collection_shim]),
 1367: 
 1368:               pubsub_tools:delete_node(Alice, Leaf, []),
 1369:               pubsub_tools:delete_node(Alice, Node, [])
 1370:       end).
 1371: 
 1372: notify_collection_and_leaf_same_user_test(Config) ->
 1373:     escalus:fresh_story(
 1374:       Config,
 1375:       [{alice, 1}, {bob, 1}],
 1376:       fun(Alice, Bob) ->
 1377:               CollectionConfig = [{<<"pubsub#node_type">>, <<"collection">>}],
 1378:               {_, NodeName} = Node = pubsub_node(),
 1379:               pubsub_tools:create_node(Alice, Node, [{config, CollectionConfig}]),
 1380: 
 1381:               NodeConfig = [{<<"pubsub#collection">>, NodeName}],
 1382:               Leaf = pubsub_leaf(),
 1383:               pubsub_tools:create_node(Alice, Leaf, [{config, NodeConfig}]),
 1384:               pubsub_tools:subscribe(Bob, Node, []),
 1385:               pubsub_tools:subscribe(Bob, Leaf, []),
 1386: 
 1387:               %% Bob should get only one notification
 1388:               pubsub_tools:publish(Alice, <<"item1">>, Leaf, []),
 1389:               pubsub_tools:receive_item_notification(Bob, <<"item1">>, Leaf, [{collection, Node}]),
 1390:               escalus_assert:has_no_stanzas(Bob),
 1391: 
 1392:               pubsub_tools:delete_node(Alice, Leaf, []),
 1393:               pubsub_tools:delete_node(Alice, Node, [])
 1394:       end).
 1395: 
 1396: notify_collections_with_same_leaf_test(Config) ->
 1397:     escalus:fresh_story(
 1398:       Config,
 1399:       [{alice, 1}, {bob, 1}, {geralt, 1}],
 1400:       fun(Alice, Bob, Geralt) ->
 1401:               CollectionConfig = [{<<"pubsub#node_type">>, <<"collection">>}],
 1402:               {_, CollectionName1} = Collection1 = pubsub_node(),
 1403:               pubsub_tools:create_node(Alice, Collection1, [{config, CollectionConfig}]),
 1404:               {_, CollectionName2} = Collection2 = pubsub_node(),
 1405:               pubsub_tools:create_node(Alice, Collection2, [{config, CollectionConfig}]),
 1406: 
 1407:               LeafConfig = [{<<"pubsub#collection">>, <<"text-multi">>,
 1408:                              [CollectionName1, CollectionName2]}],
 1409:               Leaf = pubsub_leaf(),
 1410:               pubsub_tools:create_node(Alice, Leaf, [{config, LeafConfig}]),
 1411:               pubsub_tools:subscribe(Bob, Collection1, []),
 1412:               pubsub_tools:subscribe(Geralt, Collection2, []),
 1413: 
 1414:               %% Publish to leaf node, Bob and Geralt should get notifications
 1415:               pubsub_tools:publish(Alice, <<"item1">>, Leaf, []),
 1416:               pubsub_tools:receive_item_notification(Bob, <<"item1">>, Leaf,
 1417:                                                      [{collection, Collection1}]),
 1418:               pubsub_tools:receive_item_notification(Geralt, <<"item1">>, Leaf,
 1419:                                                      [{collection, Collection2}]),
 1420: 
 1421:               pubsub_tools:delete_node(Alice, Leaf, []),
 1422:               pubsub_tools:delete_node(Alice, Collection1, []),
 1423:               pubsub_tools:delete_node(Alice, Collection2, [])
 1424:       end).
 1425: 
 1426: notify_nested_collections_test(Config) ->
 1427:     escalus:fresh_story(
 1428:       Config,
 1429:       [{alice, 1}, {bob, 1}, {geralt, 1}],
 1430:       fun(Alice, Bob, Geralt) ->
 1431:               TopCollectionConfig = [{<<"pubsub#node_type">>, <<"collection">>}],
 1432:               {_, TopCollectionName} = TopCollection = pubsub_node(),
 1433:               pubsub_tools:create_node(Alice, TopCollection, [{config, TopCollectionConfig}]),
 1434: 
 1435:               MiddleCollectionConfig = [
 1436:                                         {<<"pubsub#node_type">>, <<"collection">>},
 1437:                                         {<<"pubsub#collection">>, TopCollectionName}
 1438:                                        ],
 1439:               {_, MiddleCollectionName} = MiddleCollection = pubsub_node(),
 1440:               pubsub_tools:create_node(Alice, MiddleCollection, [{config, MiddleCollectionConfig}]),
 1441: 
 1442:               LeafConfig = [{<<"pubsub#collection">>, MiddleCollectionName}],
 1443:               Leaf = pubsub_leaf(),
 1444:               pubsub_tools:create_node(Alice, Leaf, [{config, LeafConfig}]),
 1445:               pubsub_tools:subscribe(Bob, MiddleCollection, []),
 1446:               pubsub_tools:subscribe(Geralt, TopCollection, []),
 1447: 
 1448:               %% Publish to leaf node, Bob and Geralt should get notifications
 1449:               pubsub_tools:publish(Alice, <<"item1">>, Leaf, []),
 1450:               pubsub_tools:receive_item_notification(Bob, <<"item1">>, Leaf,
 1451:                                                      [{collection, MiddleCollection}]),
 1452:               pubsub_tools:receive_item_notification(Geralt, <<"item1">>, Leaf,
 1453:                                                      [{collection, TopCollection}]),
 1454: 
 1455:               pubsub_tools:delete_node(Alice, Leaf, []),
 1456:               pubsub_tools:delete_node(Alice, MiddleCollection, []),
 1457:               pubsub_tools:delete_node(Alice, TopCollection, [])
 1458:       end).
 1459: 
 1460: retrieve_subscriptions_collection_test(Config) ->
 1461:     escalus:fresh_story(
 1462:       Config,
 1463:       [{alice, 1}, {bob, 1}],
 1464:       fun(Alice, Bob) ->
 1465:               CollectionConfig = [{<<"pubsub#node_type">>, <<"collection">>}],
 1466:               {_, NodeName} = Node = pubsub_node(),
 1467:               pubsub_tools:create_node(Alice, Node, [{config, CollectionConfig}]),
 1468: 
 1469:               NodeConfig = [{<<"pubsub#collection">>, NodeName}],
 1470:               {_, LeafName} = Leaf = pubsub_leaf(),
 1471:               pubsub_tools:create_node(Alice, Leaf, [{config, NodeConfig}]),
 1472:               Leaf2 = pubsub_leaf(),
 1473:               pubsub_tools:create_node(Alice, Leaf2, [{config, NodeConfig}]),
 1474:               pubsub_tools:subscribe(Bob, Node, []),
 1475:               pubsub_tools:subscribe(Bob, Leaf, []),
 1476: 
 1477:               % Only the nodes for which subscriptions were made should be returned
 1478:               Subs = [{LeafName, <<"subscribed">>}, {NodeName, <<"subscribed">>}],
 1479:               pubsub_tools:get_user_subscriptions(Bob, node_addr(), [{expected_result, Subs}]),
 1480: 
 1481:               pubsub_tools:delete_node(Alice, Leaf, []),
 1482:               pubsub_tools:delete_node(Alice, Leaf2, []),
 1483:               pubsub_tools:delete_node(Alice, Node, [])
 1484:       end).
 1485: 
 1486: discover_top_level_nodes_test(Config) ->
 1487:     escalus:fresh_story(
 1488:       Config,
 1489:       [{alice, 1}, {bob, 1}],
 1490:       fun(Alice, Bob) ->
 1491:               % This one is visible at top level
 1492: 
 1493:               CollectionConfig = [{<<"pubsub#node_type">>, <<"collection">>}],
 1494:               {_, NodeName} = Node = pubsub_node(),
 1495:               pubsub_tools:create_node(Alice, Node, [{config, CollectionConfig}]),
 1496: 
 1497:               % This one is not
 1498: 
 1499:               LeafConfig = [{<<"pubsub#collection">>, NodeName}],
 1500:               {_, LeafName} = Leaf = pubsub_leaf(),
 1501:               pubsub_tools:create_node(Alice, Leaf, [{config, LeafConfig}]),
 1502: 
 1503:               % This one is visible, as it is not associated with any collection
 1504:               {_, CollectionlessName} = Collectionless = pubsub_node(),
 1505:               pubsub_tools:create_node(Alice, Collectionless, []),
 1506: 
 1507:               %% Discover top-level nodes, only the collection expected
 1508:               pubsub_tools:discover_nodes(Bob, node_addr(),
 1509:                                           [{expected_result, [NodeName, CollectionlessName,
 1510:                                                               {no, LeafName}]}]),
 1511: 
 1512:               pubsub_tools:delete_node(Alice, Leaf, []),
 1513:               pubsub_tools:delete_node(Alice, Node, []),
 1514:               pubsub_tools:delete_node(Alice, Collectionless, [])
 1515:       end).
 1516: 
 1517: discover_child_nodes_test(Config) ->
 1518:     escalus:fresh_story(
 1519:       Config,
 1520:       [{alice, 1}, {bob, 1}],
 1521:       fun(Alice, Bob) ->
 1522:               %% Try to get children of a non-existing node
 1523:               {_, NodeName} = Node = pubsub_node(),
 1524:               pubsub_tools:discover_nodes(Bob, Node, [{expected_error_type, <<"cancel">>}]),
 1525: 
 1526:               CollectionConfig = [{<<"pubsub#node_type">>, <<"collection">>}],
 1527:               pubsub_tools:create_node(Alice, Node, [{config, CollectionConfig}]),
 1528: 
 1529:               pubsub_tools:discover_nodes(Bob, Node, [{expected_result, [{no, NodeName}]}]),
 1530: 
 1531:               NodeConfig = [{<<"pubsub#collection">>, NodeName}],
 1532:               {_, LeafName} = Leaf = pubsub_leaf(),
 1533:               pubsub_tools:create_node(Alice, Leaf, [{config, NodeConfig}]),
 1534:               {_, LeafName2} = Leaf2 = pubsub_leaf(),
 1535:               pubsub_tools:create_node(Alice, Leaf2, [{config, NodeConfig}]),
 1536: 
 1537:               %% Request:  5.2.1 Ex.11 Entity requests child nodes
 1538:               %% Response: 5.2.2 Ex.12 Service returns child nodes
 1539:               pubsub_tools:discover_nodes(Bob, Node, [{expected_result, [LeafName, LeafName2]}]),
 1540: 
 1541:               pubsub_tools:delete_node(Alice, Leaf, []),
 1542:               pubsub_tools:delete_node(Alice, Leaf2, []),
 1543:               pubsub_tools:delete_node(Alice, Node, [])
 1544:       end).
 1545: 
 1546: request_all_items_leaf_test(Config) ->
 1547:     escalus:fresh_story(
 1548:       Config,
 1549:       [{alice, 1}, {bob, 1}],
 1550:       fun(Alice, Bob) ->
 1551:               CollectionConfig = [{<<"pubsub#node_type">>, <<"collection">>}],
 1552:               {_, NodeName} = Node = pubsub_node(),
 1553:               pubsub_tools:create_node(Alice, Node, [{config, CollectionConfig}]),
 1554: 
 1555:               NodeConfig = [{<<"pubsub#collection">>, NodeName}],
 1556:               Leaf = pubsub_leaf(),
 1557:               pubsub_tools:create_node(Alice, Leaf, [{config, NodeConfig}]),
 1558:               Leaf2 = pubsub_leaf(),
 1559:               pubsub_tools:create_node(Alice, Leaf2, [{config, NodeConfig}]),
 1560: 
 1561:               pubsub_tools:publish(Alice, <<"item1">>, Leaf, []),
 1562:               pubsub_tools:publish(Alice, <<"item2">>, Leaf2, []),
 1563: 
 1564:               %% Request items from leaf nodes - as described in XEP-0060
 1565:               pubsub_tools:get_all_items(Bob, Leaf, [{expected_result, [<<"item1">>]}]),
 1566:               pubsub_tools:get_all_items(Bob, Leaf2, [{expected_result, [<<"item2">>]}]),
 1567: 
 1568:               %% NOTE: This is not implemented yet
 1569:               %% Request:  6.2.1 Ex.15 Subscriber requests all items on a collection
 1570:               %% Response: 6.2.2 Ex.16 Service returns items on leaf nodes
 1571:               %%pubsub_tools:get_all_items(Bob, Node,
 1572:               %%                           [{expected_result, [<<"item2">>, <<"item1">>]}]),
 1573: 
 1574:               pubsub_tools:delete_node(Alice, Leaf, []),
 1575:               pubsub_tools:delete_node(Alice, Leaf2, []),
 1576:               pubsub_tools:delete_node(Alice, Node, [])
 1577:       end).
 1578: 
 1579: %%--------------------------------------------------------------------
 1580: %% Collections config
 1581: %%--------------------------------------------------------------------
 1582: 
 1583: disable_notifications_leaf_test(Config) ->
 1584:     escalus:fresh_story(
 1585:       Config,
 1586:       [{alice, 1}, {bob, 1}],
 1587:       fun(Alice, Bob) ->
 1588:               CollectionConfig = [{<<"pubsub#node_type">>, <<"collection">>}],
 1589:               {_, NodeName} = Node = pubsub_node(),
 1590:               pubsub_tools:create_node(Alice, Node, [{config, CollectionConfig}]),
 1591: 
 1592:               NodeConfig = [{<<"pubsub#deliver_notifications">>, <<"false">>},
 1593:                             {<<"pubsub#collection">>, NodeName}],
 1594:               Leaf = pubsub_leaf(),
 1595:               pubsub_tools:create_node(Alice, Leaf, [{config, NodeConfig}]),
 1596: 
 1597:               pubsub_tools:subscribe(Bob, Node, []),
 1598:               pubsub_tools:publish(Alice, <<"item1">>, Leaf, []),
 1599: 
 1600:               %% Notifications disabled
 1601:               escalus_assert:has_no_stanzas(Bob),
 1602: 
 1603:               pubsub_tools:delete_node(Alice, Leaf, []),
 1604:               pubsub_tools:delete_node(Alice, Node, [])
 1605:       end).
 1606: 
 1607: disable_payload_leaf_test(Config) ->
 1608:     escalus:fresh_story(
 1609:       Config,
 1610:       [{alice, 1}, {bob, 1}],
 1611:       fun(Alice, Bob) ->
 1612:               CollectionConfig = [{<<"pubsub#node_type">>, <<"collection">>}],
 1613:               {_, NodeName} = Node = pubsub_node(),
 1614:               pubsub_tools:create_node(Alice, Node, [{config, CollectionConfig}]),
 1615: 
 1616:               NodeConfig = [{<<"pubsub#deliver_payloads">>, <<"false">>},
 1617:                             {<<"pubsub#collection">>, NodeName}],
 1618:               Leaf = pubsub_leaf(),
 1619:               pubsub_tools:create_node(Alice, Leaf, [{config, NodeConfig}]),
 1620: 
 1621:               pubsub_tools:subscribe(Bob, Node, []),
 1622:               pubsub_tools:publish(Alice, <<"item1">>, Leaf, []),
 1623: 
 1624:               %% Payloads disabled
 1625:               pubsub_tools:receive_item_notification(Bob, <<"item1">>, Leaf,
 1626:                                                      [{with_payload, false}, {collection, Node}]),
 1627: 
 1628:               pubsub_tools:delete_node(Alice, Leaf, []),
 1629:               pubsub_tools:delete_node(Alice, Node, [])
 1630:       end).
 1631: 
 1632: disable_persist_items_leaf_test(Config) ->
 1633:     escalus:fresh_story(
 1634:       Config,
 1635:       [{alice, 1}, {bob, 1}],
 1636:       fun(Alice, Bob) ->
 1637:               CollectionConfig = [{<<"pubsub#node_type">>, <<"collection">>}],
 1638:               {_, NodeName} = Node = pubsub_node(),
 1639:               pubsub_tools:create_node(Alice, Node, [{config, CollectionConfig}]),
 1640: 
 1641:               NodeConfig = [{<<"pubsub#persist_items">>, <<"false">>},
 1642:                             {<<"pubsub#collection">>, NodeName}],
 1643:               Leaf = pubsub_leaf(),
 1644:               pubsub_tools:create_node(Alice, Leaf, [{config, NodeConfig}]),
 1645: 
 1646:               pubsub_tools:subscribe(Bob, Node, []),
 1647:               pubsub_tools:publish(Alice, <<"item1">>, Leaf, []),
 1648: 
 1649:               %% Notifications should work
 1650:               pubsub_tools:receive_item_notification(Bob, <<"item1">>, Leaf, [{collection, Node}]),
 1651: 
 1652:               %% No items should be stored
 1653:               pubsub_tools:get_all_items(Bob, Leaf, [{expected_result, []}]),
 1654: 
 1655:               pubsub_tools:delete_node(Alice, Leaf, []),
 1656:               pubsub_tools:delete_node(Alice, Node, [])
 1657:       end).
 1658: 
 1659: %%--------------------------------------------------------------------
 1660: %% Debug calls tests
 1661: %%--------------------------------------------------------------------
 1662: 
 1663: debug_get_items_test(Config) ->
 1664:     escalus:fresh_story(
 1665:       Config,
 1666:       [{alice, 1}],
 1667:       fun(Alice) ->
 1668:               {NodeAddr, NodeName} = Node = pubsub_node(),
 1669:               pubsub_tools:create_node(Alice, Node, []),
 1670:               pubsub_tools:publish(Alice, <<"item1">>, Node, []),
 1671:               pubsub_tools:publish(Alice, <<"item2">>, Node, []),
 1672: 
 1673:               Items = rpc(mim(), mod_pubsub, get_items, [NodeAddr, NodeName]),
 1674:               % We won't bother with importing records etc...
 1675:               2 = length(Items),
 1676: 
 1677:               {error, _} = rpc(mim(), mod_pubsub, get_items, [NodeAddr, <<"no_such_node_here">>]),
 1678: 
 1679:               pubsub_tools:delete_node(Alice, Node, [])
 1680:       end).
 1681: 
 1682: debug_get_item_test(Config) ->
 1683:     escalus:fresh_story(
 1684:       Config,
 1685:       [{alice, 1}],
 1686:       fun(Alice) ->
 1687:               {NodeAddr, NodeName} = Node = pubsub_node(),
 1688:               pubsub_tools:create_node(Alice, Node, []),
 1689:               pubsub_tools:publish(Alice, <<"item1">>, Node, []),
 1690:               pubsub_tools:publish(Alice, <<"item2">>, Node, []),
 1691: 
 1692:               Item = rpc(mim(), mod_pubsub, get_item, [NodeAddr, NodeName, <<"item2">>]),
 1693:               % We won't bother with importing records etc...
 1694:               {<<"item2">>, _} = element(2, Item),
 1695: 
 1696:               {error, _} = rpc(mim(), mod_pubsub, get_item, [NodeAddr, NodeName, <<"itemX">>]),
 1697: 
 1698:               pubsub_tools:delete_node(Alice, Node, [])
 1699:       end).
 1700: %%--------------------------------------------------------------------
 1701: %% Tests for unsupported features  - excluded from suite
 1702: %%--------------------------------------------------------------------
 1703: 
 1704: disable_payload_and_persist_test(Config) ->
 1705:     escalus:fresh_story(
 1706:       Config,
 1707:       [{alice, 1}, {bob, 1}],
 1708:       fun(Alice, Bob) ->
 1709:               %% Notification-Only Transient Node, see 4.3, table 4
 1710:               NodeConfig = [{<<"pubsub#deliver_payloads">>, <<"false">>},
 1711:                             {<<"pubsub#persist_items">>, <<"false">>}],
 1712:               Node = pubsub_node(),
 1713:               pubsub_tools:create_node(Alice, Node, [{config, NodeConfig}]),
 1714: 
 1715:               pubsub_tools:subscribe(Bob, Node, []),
 1716: 
 1717:               %% Response  7.1.3 Ex.112 attempt to publish payload to transient notification node
 1718:               %%                   Expected error of type 'modify'
 1719:               pubsub_tools:publish(Alice, <<"item1">>, Node,
 1720:                                    [{expected_error_type, <<"modify">>}]),
 1721: 
 1722:               %% Publish without payload should succeed
 1723:               pubsub_tools:publish(Alice, <<"item2">>, Node, [{with_payload, false}]),
 1724: 
 1725:               %% Notifications should work
 1726:               pubsub_tools:receive_item_notification(Bob, <<"item1">>, Node, []),
 1727: 
 1728:               %% No items should be stored
 1729:               pubsub_tools:get_all_items(Bob, Node, [{expected_result, []}]),
 1730: 
 1731:               %% No more notifications
 1732:               escalus_assert:has_no_stanzas(Bob),
 1733: 
 1734:               pubsub_tools:delete_node(Alice, Node, [])
 1735:       end).
 1736: 
 1737: disable_delivery_test(Config) ->
 1738:     escalus:fresh_story(
 1739:       Config,
 1740:       [{alice, 1}, {bob, 1}],
 1741:       fun(Alice, Bob) ->
 1742:               Node = pubsub_node(),
 1743:               pubsub_tools:create_node(Alice, Node, []),
 1744: 
 1745:               %% Request: 6.3.7 Ex.71 Subscribe and configure
 1746:               %%                Ex.72 Success
 1747:               SubscrConfig = [{<<"pubsub#deliver">>, <<"false">>}],
 1748:               pubsub_tools:subscribe(Bob, Node, [{config, SubscrConfig}]),
 1749: 
 1750:               pubsub_tools:publish(Alice, <<"item1">>, Node, []),
 1751: 
 1752:               %% Notifications disabled
 1753:               escalus_assert:has_no_stanzas(Bob),
 1754: 
 1755:               pubsub_tools:delete_node(Alice, Node, [])
 1756:       end).
 1757: %%-----------------------------------------------------------------
 1758: %% pubsub_item_publisher_option
 1759: %%-----------------------------------------------------------------
 1760: 
 1761: get_item_with_publisher_option_test(Config) ->
 1762:     escalus:fresh_story(
 1763:       Config,
 1764:       [{alice, 1}],
 1765:       fun(Alice) ->
 1766:               Node = pubsub_node(),
 1767:               pubsub_tools:create_node(Alice, Node, []),
 1768: 
 1769:               pubsub_tools:publish(Alice, <<"item1">>, Node, []),
 1770: 
 1771:               PublisherJID =  escalus_utils:jid_to_lower(escalus_client:full_jid(Alice)),
 1772:               pubsub_tools:get_item(Alice, Node, <<"item1">>,
 1773:                                     [{expected_result, [#{id => <<"item1">>,
 1774:                                                           publisher => PublisherJID}]}]),
 1775:               pubsub_tools:delete_node(Alice, Node, [])
 1776:       end).
 1777: 
 1778: receive_item_notification_with_publisher_option_test(Config) ->
 1779:     escalus:fresh_story(
 1780:       Config,
 1781:       [{alice, 1}, {bob, 1}],
 1782:       fun(Alice, Bob) ->
 1783:               Node = pubsub_node(),
 1784:               pubsub_tools:create_node(Alice, Node, []),
 1785: 
 1786:               pubsub_tools:subscribe(Bob, Node, []),
 1787:               pubsub_tools:publish(Alice, <<"item1">>, Node, []),
 1788: 
 1789: 
 1790:               PublisherJID =  escalus_utils:jid_to_lower(escalus_client:full_jid(Alice)),
 1791:               pubsub_tools:receive_item_notification(Bob, #{id => <<"item1">>,
 1792:                                                             publisher => PublisherJID}, Node, []),
 1793: 
 1794:               pubsub_tools:delete_node(Alice, Node, [])
 1795:       end).
 1796: 
 1797: %%-----------------------------------------------------------------
 1798: %% hometree - specific
 1799: %%-----------------------------------------------------------------
 1800: 
 1801: can_create_node_with_existing_parent_path(Config) ->
 1802:     escalus:fresh_story(
 1803:       Config,
 1804:       [{alice, 1}],
 1805:       fun(Alice) ->
 1806:               {Parent, Node} = path_node_and_parent(Alice, pubsub_node()),
 1807:               pubsub_tools:create_node(Alice, Parent, []),
 1808:               pubsub_tools:create_node(Alice, Node, []),
 1809: 
 1810:               pubsub_tools:delete_node(Alice, Node, []),
 1811:               pubsub_tools:delete_node(Alice, Parent, [])
 1812:       end).
 1813: 
 1814: cant_create_node_with_missing_parent_path(Config) ->
 1815:     escalus:fresh_story(
 1816:       Config,
 1817:       [{alice, 1}],
 1818:       fun(Alice) ->
 1819:               {_Parent, Node} = path_node_and_parent(Alice, pubsub_node()),
 1820:               pubsub_tools:create_node(Alice, Node, [{expected_error_type, <<"auth">>}])
 1821:       end).
 1822: 
 1823: disco_node_children_by_path_prefix(Config) ->
 1824:     escalus:fresh_story(
 1825:       Config,
 1826:       [{alice, 1}, {bob, 1}],
 1827:       fun(Alice, Bob) ->
 1828:               %% Try to get children of a non-existing node
 1829:               {Parent, {_, NodeName} = Node} = path_node_and_parent(Alice, pubsub_node()),
 1830:               pubsub_tools:discover_nodes(Bob, Parent, [{expected_error_type, <<"cancel">>}]),
 1831: 
 1832:               pubsub_tools:create_node(Alice, Parent, []),
 1833: 
 1834:               pubsub_tools:discover_nodes(Bob, Parent, [{expected_result, []}]),
 1835: 
 1836:               pubsub_tools:create_node(Alice, Node, []),
 1837: 
 1838:               %% Request:  5.2.1 Ex.11 Entity requests child nodes
 1839:               %% Response: 5.2.2 Ex.12 Service returns child nodes
 1840:               pubsub_tools:discover_nodes(Bob, Parent, [{expected_result, [NodeName]}]),
 1841: 
 1842:               pubsub_tools:delete_node(Alice, Node, []),
 1843:               pubsub_tools:delete_node(Alice, Parent, [])
 1844:       end).
 1845: 
 1846: deleting_parent_path_deletes_children(Config) ->
 1847:     escalus:fresh_story(
 1848:       Config,
 1849:       [{alice, 1}],
 1850:       fun(Alice) ->
 1851:               {{_, ParentName} = Parent, {_, NodeName} = Node}
 1852:               = path_node_and_parent(Alice, pubsub_node()),
 1853: 
 1854:               pubsub_tools:create_node(Alice, Parent, []),
 1855:               pubsub_tools:create_node(Alice, Node, []),
 1856: 
 1857:               pubsub_tools:delete_node(Alice, Parent, []),
 1858: 
 1859:               pubsub_tools:discover_nodes(Alice, node_addr(),
 1860:                                           [{expected_result, [{no, ParentName}, {no, NodeName}]}]),
 1861:               pubsub_tools:discover_nodes(Alice, Node, [{expected_error_type, <<"cancel">>}])
 1862:       end).
 1863: 
 1864: %%-----------------------------------------------------------------
 1865: %% Helpers
 1866: %%-----------------------------------------------------------------
 1867: 
 1868: path_node_and_parent(Client, {NodeAddr, NodeName}) ->
 1869:     %% TODO: Add proper JID stringprepping to escalus!!!
 1870:     JID = escalus_ejabberd:rpc(jid, from_binary, [escalus_client:short_jid(Client)]),
 1871:     {LUser, LServer, _} = escalus_ejabberd:rpc(jid, to_lower, [JID]),
 1872:     Prefix = <<"/home/", LServer/binary, "/", LUser/binary>>,
 1873:     {{NodeAddr, Prefix}, {NodeAddr, <<Prefix/binary, "/", NodeName/binary>>}}.
 1874: 
 1875: required_modules(ExtraOpts) ->
 1876:     [{mod_pubsub, [
 1877:                    {backend, mongoose_helper:mnesia_or_rdbms_backend()},
 1878:                    {host, subhost_pattern("pubsub.@HOST@")}
 1879:                    | ExtraOpts
 1880:                   ]}].
 1881: 
 1882: verify_config_fields(NodeConfig) ->
 1883:     ValidFields = [
 1884:                    {<<"FORM_TYPE">>, <<"hidden">>},
 1885:                    {<<"pubsub#title">>, <<"text-single">>},
 1886:                    {<<"pubsub#deliver_notifications">>, <<"boolean">>},
 1887:                    {<<"pubsub#deliver_payloads">>, <<"boolean">>},
 1888:                    {<<"pubsub#notify_config">>, <<"boolean">>},
 1889:                    {<<"pubsub#notify_delete">>, <<"boolean">>},
 1890:                    {<<"pubsub#notify_retract">>, <<"boolean">>},
 1891: % not supported yet                   {<<"pubsub#notify_sub">>, <<"boolean">>},
 1892:                    {<<"pubsub#persist_items">>, <<"boolean">>},
 1893:                    {<<"pubsub#max_items">>, <<"text-single">>},
 1894: % not supported yet                   {<<"pubsub#item_expire">>, <<"text-single">>},
 1895:                    {<<"pubsub#subscribe">>, <<"boolean">>},
 1896:                    {<<"pubsub#access_model">>, <<"list-single">>},
 1897:                    {<<"pubsub#roster_groups_allowed">>, <<"list-multi">>},
 1898:                    {<<"pubsub#publish_model">>, <<"list-single">>},
 1899:                    {<<"pubsub#purge_offline">>, <<"boolean">>},
 1900:                    {<<"pubsub#max_payload_size">>, <<"text-single">>},
 1901:                    {<<"pubsub#send_last_published_item">>, <<"list-single">>},
 1902:                    {<<"pubsub#presence_based_delivery">>, <<"boolean">>},
 1903:                    {<<"pubsub#notification_type">>, <<"list-single">>},
 1904:                    {<<"pubsub#type">>, <<"text-single">>},
 1905: % not supported yet                   {<<"pubsub#dataform_xslt">>, <<"text-single">>}
 1906: % not supported yet                   {<<"pubsub#node_type">>, undef},
 1907: % not supported yet                   {<<"pubsub#children">>, undef},
 1908:                    {<<"pubsub#collection">>, <<"text-multi">>}
 1909:                   ],
 1910:     [] =
 1911:     lists:foldl(fun({Var, Type}, Fields) ->
 1912:                         {{value, {_, Type, _}, NewFields}, _}
 1913:                         = {lists:keytake(Var, 1, Fields), Var},
 1914:                         NewFields
 1915:                 end, NodeConfig, ValidFields).
 1916: 
 1917: node_config_for_test() ->
 1918:     [
 1919:      {<<"pubsub#title">>, <<"TARDIS">>},
 1920:      {<<"pubsub#deliver_notifications">>, <<"1">>},
 1921:      {<<"pubsub#deliver_payloads">>, <<"1">>},
 1922:      {<<"pubsub#notify_config">>, <<"0">>},
 1923:      {<<"pubsub#notify_delete">>, <<"1">>},
 1924:      {<<"pubsub#notify_retract">>, <<"1">>},
 1925:      % Not supported yet                   {<<"pubsub#notify_sub">>, <<"boolean">>},
 1926:      {<<"pubsub#persist_items">>, <<"0">>},
 1927:      {<<"pubsub#max_items">>, <<"10">>},
 1928:      % Not supported yet: {<<"pubsub#item_expire">>, <<"text-single">>},
 1929:      {<<"pubsub#subscribe">>, <<"0">>},
 1930:      {<<"pubsub#access_model">>, <<"presence">>},
 1931:      % TODO: Verify with test case: {<<"pubsub#roster_groups_allowed">>, <<"list-multi">>},
 1932:      {<<"pubsub#publish_model">>, <<"publishers">>},
 1933:      {<<"pubsub#purge_offline">>, <<"1">>},
 1934:      {<<"pubsub#max_payload_size">>, <<"24601">>},
 1935:      {<<"pubsub#send_last_published_item">>, <<"on_sub">>},
 1936:      {<<"pubsub#presence_based_delivery">>, <<"1">>},
 1937:      {<<"pubsub#notification_type">>, <<"normal">>},
 1938:      {<<"pubsub#type">>, <<"urn:mim">>}
 1939:      % Not supported yet: {<<"pubsub#dataform_xslt">>, <<"text-single">>}
 1940:      % Not supported yet: {<<"pubsub#node_type">>, undef},
 1941:      % Not supported yet: {<<"pubsub#children">>, undef},
 1942:      % Covered by collection tests: {<<"pubsub#collection">>, <<"text-multi">>}
 1943:     ].
 1944: 
 1945: verify_item_retract({NodeAddr, NodeName}, ItemId, Stanza) ->
 1946:     escalus:assert(is_message, Stanza),
 1947:     NodeAddr = exml_query:attr(Stanza, <<"from">>),
 1948: 
 1949:     [#xmlel{ attrs = [{<<"xmlns">>, ?NS_PUBSUB_EVENT}] } = Event]
 1950:     = exml_query:subelements(Stanza, <<"event">>),
 1951: 
 1952:     [#xmlel{ attrs = [{<<"node">>, NodeName}] } = Items]
 1953:     = exml_query:subelements(Event, <<"items">>),
 1954: 
 1955:     [#xmlel{ attrs = [{<<"id">>, ItemId}] }] = exml_query:subelements(Items, <<"retract">>).
 1956: 
 1957: verify_config_event({NodeAddr, NodeName}, ConfigChange, Stanza) ->
 1958:     escalus:assert(is_message, Stanza),
 1959:     NodeAddr = exml_query:attr(Stanza, <<"from">>),
 1960: 
 1961:     [#xmlel{ attrs = [{<<"xmlns">>, ?NS_PUBSUB_EVENT}] } = Event]
 1962:     = exml_query:subelements(Stanza, <<"event">>),
 1963: 
 1964:     [#xmlel{ attrs = [{<<"node">>, NodeName}] } = ConfigEl]
 1965:     = exml_query:subelements(Event, <<"configuration">>),
 1966: 
 1967:     Fields = exml_query:paths(ConfigEl, [{element, <<"x">>},
 1968:                                          {element, <<"field">>}]),
 1969: 
 1970:     Opts = [ {exml_query:attr(F, <<"var">>),
 1971:               exml_query:path(F, [{element, <<"value">>}, cdata])} || F <- Fields ],
 1972: 
 1973:     true = lists:all(fun({K, V}) ->
 1974:                              {K, V} =:= lists:keyfind(K, 1, Opts)
 1975:                      end, ConfigChange).
 1976: 
 1977: verify_affiliations(Affiliations, ValidAffiliations) ->
 1978:     NormalisedValidAffiliations
 1979:     = lists:sort([ {escalus_utils:jid_to_lower(escalus_client:short_jid(Client)), Aff}
 1980:                    || {Client, Aff} <- ValidAffiliations ]),
 1981:     NormalisedValidAffiliations = lists:sort(Affiliations).
 1982: 
 1983: verify_returned_affiliation(IQError, User, Aff) ->
 1984:     UserJid = escalus_utils:jid_to_lower(escalus_utils:get_short_jid(User)),
 1985:     QPath = [{element, <<"pubsub">>},
 1986:              {element, <<"affiliations">>},
 1987:              {element, <<"affiliation">>}],
 1988:     [AffEl] = exml_query:paths(IQError, QPath),
 1989:     UserJid = exml_query:attr(AffEl, <<"jid">>),
 1990:     Aff = exml_query:attr(AffEl, <<"affiliation">>).
 1991: 
 1992: is_not_allowed_and_closed(IQError) ->
 1993:     ?NS_STANZA_ERRORS = exml_query:path(IQError, [{element, <<"error">>},
 1994:                                                   {element, <<"not-allowed">>},
 1995:                                                   {attr, <<"xmlns">>}]),
 1996:     ?NS_PUBSUB_ERRORS = exml_query:path(IQError, [{element, <<"error">>},
 1997:                                                   {element, <<"closed-node">>},
 1998:                                                   {attr, <<"xmlns">>}]).
 1999: 
 2000: %% TODO: Functions below will most probably fail when mod_pubsub gets some nice refactoring!
 2001: 
 2002: set_service_option(Host, Key, Val) ->
 2003:     true = rpc(mim(), ets, insert, [service_tab_name(Host), {Key, Val}]).
 2004: 
 2005: lookup_service_option(Host, Key) ->
 2006:     [{_, Val}] = rpc(mim(), ets, lookup, [service_tab_name(Host), Key]),
 2007:     Val.
 2008: 
 2009: service_tab_name(Host) ->
 2010:     rpc(mim(), gen_mod, get_module_proc, [Host, config]).