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