1: %%%=================================================================== 2: %%% @copyright (C) 2016, Erlang Solutions Ltd. 3: %%% @doc Suite for testing Personal Eventing Protocol features 4: %%% as described in XEP-0163 5: %%% @end 6: %%%=================================================================== 7: 8: -module(pep_SUITE). 9: 10: -include_lib("escalus/include/escalus.hrl"). 11: -include_lib("common_test/include/ct.hrl"). 12: -include_lib("escalus/include/escalus_xmlns.hrl"). 13: -include_lib("exml/include/exml.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: disco_test/1, 23: disco_sm_test/1, 24: disco_sm_items_test/1, 25: pep_caps_test/1, 26: publish_and_notify_test/1, 27: auto_create_with_publish_options_test/1, 28: publish_options_success_test/1, 29: publish_options_fail_unknown_option_story/1, 30: publish_options_fail_wrong_value_story/1, 31: publish_options_fail_wrong_form/1, 32: send_caps_after_login_test/1, 33: delayed_receive/1, 34: delayed_receive_with_sm/1, 35: h_ok_after_notify_test/1, 36: authorize_access_model/1, 37: unsubscribe_after_presence_unsubscription/1 38: ]). 39: 40: -export([ 41: start_caps_clients/2, 42: send_initial_presence_with_caps/2, 43: add_config_to_create_node_request/1 44: ]). 45: 46: -import(distributed_helper, [mim/0, 47: require_rpc_nodes/1, 48: subhost_pattern/1, 49: rpc/4]). 50: -import(config_parser_helper, [mod_config/2]). 51: -import(domain_helper, [domain/0]). 52: 53: -define(NS_PUBSUB_PUB_OPTIONS, <<"http://jabber.org/protocol/pubsub#publish-options">>). 54: 55: %%-------------------------------------------------------------------- 56: %% Suite configuration 57: %%-------------------------------------------------------------------- 58: 59: all() -> 60: [ 61: {group, pep_tests}, 62: {group, cache_tests} 63: ]. 64: 65: groups() -> 66: [ 67: {pep_tests, [parallel], 68: [ 69: disco_test, 70: disco_sm_test, 71: disco_sm_items_test, 72: pep_caps_test, 73: publish_and_notify_test, 74: auto_create_with_publish_options_test, 75: publish_options_success_test, 76: publish_options_fail_unknown_option_story, 77: publish_options_fail_wrong_value_story, 78: publish_options_fail_wrong_form, 79: send_caps_after_login_test, 80: delayed_receive, 81: delayed_receive_with_sm, 82: h_ok_after_notify_test, 83: authorize_access_model, 84: unsubscribe_after_presence_unsubscription 85: ] 86: }, 87: {cache_tests, [parallel], 88: [ 89: send_caps_after_login_test, 90: delayed_receive, 91: delayed_receive_with_sm, 92: unsubscribe_after_presence_unsubscription 93: ] 94: } 95: ]. 96: 97: suite() -> 98: require_rpc_nodes([mim]) ++ escalus:suite(). 99: 100: %%-------------------------------------------------------------------- 101: %% Init & teardown 102: %%-------------------------------------------------------------------- 103: 104: init_per_suite(Config) -> 105: escalus:init_per_suite(dynamic_modules:save_modules(domain(), Config)). 106: 107: end_per_suite(Config) -> 108: escalus_fresh:clean(), 109: dynamic_modules:restore_modules(Config), 110: escalus:end_per_suite(Config). 111: 112: init_per_group(cache_tests, Config) -> 113: Config0 = dynamic_modules:save_modules(domain(), Config), 114: NewConfig = required_modules(cache_tests), 115: dynamic_modules:ensure_modules(domain(), NewConfig), 116: Config0; 117: 118: init_per_group(_GroupName, Config) -> 119: dynamic_modules:ensure_modules(domain(), required_modules()), 120: Config. 121: 122: end_per_group(_GroupName, Config) -> 123: dynamic_modules:restore_modules(Config). 124: 125: init_per_testcase(TestName, Config) -> 126: escalus:init_per_testcase(TestName, Config). 127: 128: end_per_testcase(TestName, Config) -> 129: escalus:end_per_testcase(TestName, Config). 130: 131: %%-------------------------------------------------------------------- 132: %% Test cases for XEP-0163 133: %% Comments in test cases refer to sections is the XEP 134: %%-------------------------------------------------------------------- 135: 136: %% Group: pep_tests (sequence) 137: 138: disco_test(Config) -> 139: escalus:fresh_story( 140: Config, 141: [{alice, 1}], 142: fun(Alice) -> 143: escalus:send(Alice, escalus_stanza:disco_info(pubsub_tools:node_addr())), 144: Stanza = escalus:wait_for_stanza(Alice), 145: escalus:assert(has_identity, [<<"pubsub">>, <<"service">>], Stanza), 146: escalus:assert(has_identity, [<<"pubsub">>, <<"pep">>], Stanza), 147: escalus:assert(has_feature, [?NS_PUBSUB], Stanza) 148: end). 149: 150: disco_sm_test(Config) -> 151: escalus:fresh_story( 152: Config, 153: [{alice, 1}], 154: fun(Alice) -> 155: AliceJid = escalus_client:short_jid(Alice), 156: escalus:send(Alice, escalus_stanza:disco_info(AliceJid)), 157: Stanza = escalus:wait_for_stanza(Alice), 158: ?assertNot(escalus_pred:has_identity(<<"pubsub">>, <<"service">>, Stanza)), 159: escalus:assert(has_identity, [<<"pubsub">>, <<"pep">>], Stanza), 160: escalus:assert(has_feature, [?NS_PUBSUB], Stanza), 161: escalus:assert(is_stanza_from, [AliceJid], Stanza) 162: end). 163: 164: disco_sm_items_test(Config) -> 165: NodeNS = random_node_ns(), 166: escalus:fresh_story( 167: Config, 168: [{alice, 1}], 169: fun(Alice) -> 170: AliceJid = escalus_client:short_jid(Alice), 171: 172: %% Node not present yet 173: escalus:send(Alice, escalus_stanza:disco_items(AliceJid)), 174: Stanza1 = escalus:wait_for_stanza(Alice), 175: Query1 = exml_query:subelement(Stanza1, <<"query">>), 176: ?assertEqual(undefined, exml_query:subelement_with_attr(Query1, <<"node">>, NodeNS)), 177: escalus:assert(is_stanza_from, [AliceJid], Stanza1), 178: 179: %% Publish an item to trigger node creation 180: pubsub_tools:publish(Alice, <<"item1">>, {pep, NodeNS}, []), 181: 182: %% Node present 183: escalus:send(Alice, escalus_stanza:disco_items(AliceJid)), 184: Stanza2 = escalus:wait_for_stanza(Alice), 185: Query2 = exml_query:subelement(Stanza2, <<"query">>), 186: Item = exml_query:subelement_with_attr(Query2, <<"node">>, NodeNS), 187: ?assertEqual(jid:str_tolower(AliceJid), exml_query:attr(Item, <<"jid">>)), 188: escalus:assert(is_stanza_from, [AliceJid], Stanza2) 189: end). 190: 191: pep_caps_test(Config) -> 192: escalus:fresh_story( 193: Config, 194: [{bob, 1}], 195: fun(Bob) -> 196: NodeNS = random_node_ns(), 197: Caps = caps(NodeNS), 198: 199: %% Send presence with capabilities (chap. 1 ex. 4) 200: send_presence_with_caps(Bob, Caps), 201: receive_presence_with_caps(Bob, Bob, Caps), 202: 203: %% Server does not know the version string, so it requests feature list 204: DiscoRequest = escalus:wait_for_stanza(Bob), 205: %% Client responds with a list of supported features (chap. 1 ex. 5) 206: send_caps_disco_result(Bob, DiscoRequest, NodeNS) 207: end). 208: 209: publish_and_notify_test(Config) -> 210: Config1 = set_caps(Config), 211: escalus:fresh_story_with_config(Config1, [{alice, 1}, {bob, 1}], fun publish_and_notify_story/3). 212: 213: publish_and_notify_story(Config, Alice, Bob) -> 214: NodeNS = ?config(node_ns, Config), 215: escalus_story:make_all_clients_friends([Alice, Bob]), 216: pubsub_tools:publish(Alice, <<"item1">>, {pep, NodeNS}, []), 217: pubsub_tools:receive_item_notification( 218: Bob, <<"item1">>, {escalus_utils:get_short_jid(Alice), NodeNS}, []). 219: 220: auto_create_with_publish_options_test(Config) -> 221: % Given pubsub is configured with pep plugin 222: escalus:fresh_story( 223: Config, 224: [{alice, 1}], 225: fun(Alice) -> 226: 227: % When publishing an item with publish-options 228: Node = {pep, random_node_ns()}, 229: PublishOptions = [{<<"pubsub#access_model">>, <<"open">>}], 230: pubsub_tools:publish_with_options(Alice, <<"item1">>, Node, [], PublishOptions), 231: 232: % Then node configuration contains specified publish-options 233: NodeConfig = pubsub_tools:get_configuration(Alice, Node, []), 234: verify_publish_options(NodeConfig, PublishOptions) 235: end). 236: 237: publish_options_success_test(Config) -> 238: escalus:fresh_story( 239: Config, 240: [{alice, 1}, {bob, 1}], 241: fun(Alice, Bob) -> 242: NodeNS = random_node_ns(), 243: PepNode = make_pep_node_info(Alice, NodeNS), 244: pubsub_tools:create_node(Alice, PepNode, 245: [{modify_request,fun add_config_to_create_node_request/1}]), 246: escalus_story:make_all_clients_friends([Alice, Bob]), 247: PublishOptions = [{<<"pubsub#deliver_payloads">>, <<"1">>}, 248: {<<"pubsub#notify_config">>, <<"0">>}, 249: {<<"pubsub#notify_delete">>, <<"0">>}, 250: {<<"pubsub#purge_offline">>, <<"0">>}, 251: {<<"pubsub#notify_retract">>, <<"0">>}, 252: {<<"pubsub#persist_items">>, <<"1">>}, 253: {<<"pubsub#roster_groups_allowed">>, [<<"friends">>, <<"enemies">>]}, 254: {<<"pubsub#max_items">>, <<"1">>}, 255: {<<"pubsub#subscribe">>, <<"1">>}, 256: {<<"pubsub#access_model">>, <<"presence">>}, 257: {<<"pubsub#publish_model">>, <<"publishers">>}, 258: {<<"pubsub#notification_type">>, <<"headline">>}, 259: {<<"pubsub#max_payload_size">>, <<"60000">>}, 260: {<<"pubsub#send_last_published_item">>, <<"on_sub_and_presence">>}, 261: {<<"pubsub#deliver_notifications">>, <<"1">>}, 262: {<<"pubsub#presence_based_delivery">>, <<"1">>}], 263: Result = publish_with_publish_options(Alice, {pep, NodeNS}, <<"item1">>, PublishOptions), 264: escalus:assert(is_iq_result, Result) 265: end). 266: 267: publish_options_fail_unknown_option_story(Config) -> 268: escalus:fresh_story( 269: Config, 270: [{alice, 1}], 271: fun(Alice) -> 272: NodeNS = random_node_ns(), 273: PepNode = make_pep_node_info(Alice, NodeNS), 274: pubsub_tools:create_node(Alice, PepNode, []), 275: 276: PublishOptions = [{<<"deliver_payloads">>, <<"1">>}], 277: Result = publish_with_publish_options(Alice, {pep, NodeNS}, <<"item1">>, PublishOptions), 278: escalus:assert(is_error, [<<"cancel">>, <<"conflict">>], Result), 279: 280: PublishOptions2 = [{<<"pubsub#not_existing_option">>, <<"1">>}], 281: Result2 = publish_with_publish_options(Alice, {pep, NodeNS}, <<"item1">>, PublishOptions2), 282: escalus:assert(is_error, [<<"cancel">>, <<"conflict">>], Result2) 283: end). 284: 285: publish_options_fail_wrong_value_story(Config) -> 286: escalus:fresh_story( 287: Config, 288: [{alice, 1}], 289: fun(Alice) -> 290: NodeNS = random_node_ns(), 291: PepNode = make_pep_node_info(Alice, NodeNS), 292: pubsub_tools:create_node(Alice, PepNode, 293: [{modify_request,fun add_config_to_create_node_request/1}]), 294: 295: PublishOptions = [{<<"pubsub#deliver_payloads">>, <<"0">>}], 296: Result = publish_with_publish_options(Alice, {pep, NodeNS}, <<"item1">>, PublishOptions), 297: escalus:assert(is_error, [<<"cancel">>, <<"conflict">>], Result), 298: 299: PublishOptions2 = [{<<"pubsub#roster_groups_allowed">>, <<"friends">>}], 300: Result2 = publish_with_publish_options(Alice, {pep, NodeNS}, <<"item1">>, PublishOptions2), 301: escalus:assert(is_error, [<<"cancel">>, <<"conflict">>], Result2) 302: end). 303: 304: publish_options_fail_wrong_form(Config) -> 305: escalus:fresh_story( 306: Config, 307: [{alice, 1}], 308: fun(Alice) -> 309: NodeNS = random_node_ns(), 310: PepNode = make_pep_node_info(Alice, NodeNS), 311: pubsub_tools:create_node(Alice, PepNode, []), 312: PublishOptions = [{<<"deliver_payloads">>, <<"0">>}], 313: Result = publish_with_publish_options(Alice, {pep, NodeNS}, <<"item1">>, PublishOptions, <<"WRONG_NS">>), 314: escalus:assert(is_error, [<<"cancel">>, <<"conflict">>], Result) 315: end). 316: 317: send_caps_after_login_test(Config) -> 318: escalus:fresh_story( 319: Config, 320: [{alice, 1}, {bob, 1}], 321: fun(Alice, Bob) -> 322: NodeNS = random_node_ns(), 323: pubsub_tools:publish(Alice, <<"item2">>, {pep, NodeNS}, []), 324: 325: escalus_story:make_all_clients_friends([Alice, Bob]), 326: 327: Caps = caps(NodeNS), 328: send_presence_with_caps(Bob, Caps), 329: receive_presence_with_caps(Bob, Bob, Caps), 330: receive_presence_with_caps(Alice, Bob, Caps), 331: 332: handle_requested_caps(NodeNS, Bob), 333: 334: Node = {escalus_utils:get_short_jid(Alice), NodeNS}, 335: Check = fun(Message) -> 336: pubsub_tools:check_item_notification(Message, <<"item2">>, Node, []) 337: end, 338: 339: %% Presence subscription triggers PEP last item sending 340: %% and sometimes this async process takes place after caps 341: %% are updated, leading to duplicated notification 342: Check(escalus_client:wait_for_stanza(Bob)), 343: case escalus_client:peek_stanzas(Bob) of 344: [Message2] -> 345: Check(Message2); 346: [] -> 347: ok 348: end 349: end). 350: 351: delayed_receive(Config) -> 352: %% if alice publishes an item and then bob subscribes successfully to her presence 353: %% then bob will receive the item right after final subscription stanzas 354: Config1 = set_caps(Config), 355: escalus:fresh_story_with_config(Config1, [{alice, 1}, {bob, 1}], fun delayed_receive_story/3). 356: 357: delayed_receive_story(Config, Alice, Bob) -> 358: NodeNS = ?config(node_ns, Config), 359: pubsub_tools:publish(Alice, <<"item2">>, {pep, NodeNS}, []), 360: [Message] = make_friends(Bob, Alice), 361: Node = {escalus_utils:get_short_jid(Alice), NodeNS}, 362: pubsub_tools:check_item_notification(Message, <<"item2">>, Node, []), 363: 364: %% Known issue: without a mutual presence subscription Bob will not receive further items 365: pubsub_tools:publish(Alice, <<"item3">>, {pep, NodeNS}, []), 366: [] = escalus_client:wait_for_stanzas(Bob, 1, 500), 367: ok. 368: 369: delayed_receive_with_sm(Config) -> 370: %% Same as delayed_receive but with stream management turned on 371: Config1 = set_caps(Config), 372: escalus:fresh_story_with_config(Config1, [{alice, 1}, {bob, 1}], 373: fun delayed_receive_with_sm_story/3). 374: 375: delayed_receive_with_sm_story(Config, Alice, Bob) -> 376: NodeNS = ?config(node_ns, Config), 377: enable_sm(Alice), 378: enable_sm(Bob), 379: publish_with_sm(Alice, <<"item2">>, {pep, NodeNS}, []), 380: [Message] = make_friends_sm(Bob, Alice), 381: Node = {escalus_utils:get_short_jid(Alice), NodeNS}, 382: pubsub_tools:check_item_notification(Message, <<"item2">>, Node, []). 383: 384: h_ok_after_notify_test(ConfigIn) -> 385: Config = escalus_users:update_userspec(ConfigIn, kate, stream_management, true), 386: Config1 = set_caps(Config), 387: escalus:fresh_story_with_config(Config1, [{alice, 1}, {kate, 1}], 388: fun h_ok_after_notify_story/3). 389: 390: h_ok_after_notify_story(Config, Alice, Kate) -> 391: NodeNS = ?config(node_ns, Config), 392: escalus_story:make_all_clients_friends([Alice, Kate]), 393: 394: pubsub_tools:publish(Alice, <<"item2">>, {pep, NodeNS}, []), 395: Node = {escalus_utils:get_short_jid(Alice), NodeNS}, 396: Check = fun(Message) -> 397: pubsub_tools:check_item_notification(Message, <<"item2">>, Node, []) 398: end, 399: Check(escalus_connection:get_stanza(Kate, item2)), 400: 401: H = escalus_tcp:get_sm_h(Kate#client.rcv_pid), 402: escalus:send(Kate, escalus_stanza:sm_ack(H)), 403: 404: escalus_connection:send(Kate, escalus_stanza:sm_request()), 405: 406: % Presence exchange triggers asynchronous sending of the last published item. 407: % If this happens after item2 is published, Kate will receive it twice. 408: Stanza = escalus_connection:get_stanza(Kate, stream_mgmt_ack_or_item2), 409: case escalus_pred:is_sm_ack(Stanza) of 410: true -> 411: ok; 412: false -> 413: Check(Stanza), 414: escalus:assert(is_sm_ack, escalus_connection:get_stanza(Kate, stream_mgmt_ack)) 415: end. 416: 417: authorize_access_model(Config) -> 418: escalus:fresh_story(Config, 419: [{alice, 1}, {bob, 1}], 420: fun(Alice, Bob) -> 421: NodeNS = random_node_ns(), 422: {NodeAddr, _} = PepNode = make_pep_node_info(Alice, NodeNS), 423: AccessModel = {<<"pubsub#access_model">>, <<"authorize">>}, 424: pubsub_tools:create_node(Alice, PepNode, [{config, [AccessModel]}]), 425: 426: pubsub_tools:subscribe(Bob, PepNode, [{subscription, <<"pending">>}]), 427: BobsRequest = pubsub_tools:receive_subscription_request(Alice, Bob, PepNode, []), 428: 429: %% FIXME: Only one item should be here but node_pep is based on node_flat, which 430: %% is node_dag's ancestor, so this entry gets duplicated because every plugin 431: %% is queried for subscriptions. Nasty fix involves deduplicating entries 432: %% in mod_pubsub:get_subscriptions. The proper fix means not hacking node plugins 433: %% into serving PEP but it's definitely a major change... 434: Subs = [{NodeNS, <<"pending">>}, {NodeNS, <<"pending">>}], 435: pubsub_tools:get_user_subscriptions(Bob, NodeAddr, [{expected_result, Subs}]), 436: 437: pubsub_tools:submit_subscription_response(Alice, BobsRequest, PepNode, true, []), 438: pubsub_tools:receive_subscription_notification(Bob, <<"subscribed">>, PepNode, []), 439: 440: pubsub_tools:publish(Alice, <<"fish">>, {pep, NodeNS}, []), 441: pubsub_tools:receive_item_notification( 442: Bob, <<"fish">>, {escalus_utils:get_short_jid(Alice), NodeNS}, []), 443: 444: pubsub_tools:delete_node(Alice, PepNode, []) 445: end). 446: 447: unsubscribe_after_presence_unsubscription(Config) -> 448: escalus:fresh_story(Config, 449: [{alice, 1}, {bob, 1}], 450: fun(Alice, Bob) -> 451: escalus_story:make_all_clients_friends([Alice, Bob]), 452: 453: NodeNS = random_node_ns(), 454: PepNode = make_pep_node_info(Alice, NodeNS), 455: pubsub_tools:create_node(Alice, PepNode, []), 456: pubsub_tools:subscribe(Bob, PepNode, []), 457: pubsub_tools:publish(Alice, <<"fish">>, {pep, NodeNS}, []), 458: pubsub_tools:receive_item_notification( 459: Bob, <<"fish">>, {escalus_utils:get_short_jid(Alice), NodeNS}, []), 460: 461: BobJid = escalus_utils:get_short_jid(Bob), 462: escalus:send(Alice, escalus_stanza:presence_direct(BobJid, <<"unsubscribed">>)), 463: %% Bob & Alice get roster update, Bob gets presence unsubscribed & unavailable 464: [_, _, _] = escalus:wait_for_stanzas(Bob, 3), 465: _ = escalus:wait_for_stanza(Alice), 466: 467: %% Unsubscription from PEP nodes is implicit 468: pubsub_tools:publish(Alice, <<"salmon">>, {pep, NodeNS}, []), 469: [] = escalus:wait_for_stanzas(Bob, 1, 500), 470: 471: pubsub_tools:delete_node(Alice, PepNode, []) 472: end). 473: 474: %%----------------------------------------------------------------- 475: %% Helpers 476: %%----------------------------------------------------------------- 477: 478: add_config_to_create_node_request(#xmlel{children = [PubsubEl]} = Request) -> 479: Fields = [#{values => [<<"friends">>, <<"enemies">>], var => <<"pubsub#roster_groups_allowed">>}], 480: Form = form_helper:form(#{ns => <<"http://jabber.org/protocol/pubsub#node_config">>, fields => Fields}), 481: ConfigureEl = #xmlel{name = <<"configure">>, children = [Form]}, 482: PubsubEl2 = PubsubEl#xmlel{children = PubsubEl#xmlel.children ++ [ConfigureEl]}, 483: Request#xmlel{children = [PubsubEl2]}. 484: 485: publish_with_publish_options(Client, Node, Content, Options) -> 486: publish_with_publish_options(Client, Node, Content, Options, ?NS_PUBSUB_PUB_OPTIONS). 487: 488: publish_with_publish_options(Client, Node, Content, Options, FormType) -> 489: OptionsEl = #xmlel{name = <<"publish-options">>, 490: children = form(Options, FormType)}, 491: 492: Id = pubsub_tools:id(Client, Node, <<"publish">>), 493: Publish = pubsub_tools:publish_request(Id, Client, Content, Node, Options), 494: #xmlel{children = [#xmlel{} = PubsubEl]} = Publish, 495: NewPubsubEl = PubsubEl#xmlel{children = PubsubEl#xmlel.children ++ [OptionsEl]}, 496: escalus:send(Client, Publish#xmlel{children = [NewPubsubEl]}), 497: escalus:wait_for_stanza(Client). 498: 499: form(FormFields, FormType) -> 500: FieldSpecs = lists:map(fun field_spec/1, FormFields), 501: [form_helper:form(#{fields => FieldSpecs, ns => FormType})]. 502: 503: field_spec({Var, Value}) when is_list(Value) -> #{var => Var, values => Value}; 504: field_spec({Var, Value}) -> #{var => Var, values => [Value]}. 505: 506: required_modules() -> 507: [{mod_caps, config_parser_helper:mod_config_with_auto_backend(mod_caps)}, 508: {mod_pubsub, mod_config(mod_pubsub, #{plugins => [<<"dag">>, <<"pep">>], 509: nodetree => nodetree_dag, 510: backend => mongoose_helper:mnesia_or_rdbms_backend(), 511: pep_mapping => #{}, 512: host => subhost_pattern("pubsub.@HOST@")})}]. 513: required_modules(cache_tests) -> 514: HostType = domain_helper:host_type(), 515: [{mod_caps, config_parser_helper:mod_config_with_auto_backend(mod_caps)}, 516: {mod_pubsub, mod_config(mod_pubsub, #{plugins => [<<"dag">>, <<"pep">>], 517: nodetree => nodetree_dag, 518: backend => mongoose_helper:mnesia_or_rdbms_backend(), 519: pep_mapping => #{}, 520: host => subhost_pattern("pubsub.@HOST@"), 521: last_item_cache => mongoose_helper:mnesia_or_rdbms_backend() 522: })}]. 523: 524: send_initial_presence_with_caps(NodeNS, Client) -> 525: case is_caps_client(Client) of 526: false -> escalus_story:send_initial_presence(Client); 527: true -> send_presence_with_caps(Client, caps(NodeNS)) 528: end. 529: 530: handle_requested_caps(NodeNS, User) -> 531: case is_caps_client(User) of 532: false -> ok; 533: true -> DiscoRequest = escalus:wait_for_stanza(User), 534: send_caps_disco_result(User, DiscoRequest, NodeNS) 535: end. 536: 537: is_caps_client(Client) -> 538: case escalus_client:username(Client) of 539: <<"alice", _/binary>> -> false; 540: _ -> true 541: end. 542: 543: send_presence_with_caps(User, Caps) -> 544: Presence = escalus_stanza:presence(<<"available">>, [Caps]), 545: escalus:send(User, Presence). 546: 547: send_caps_disco_result(User, DiscoRequest, NodeNS) -> 548: QueryEl = escalus_stanza:query_el(?NS_DISCO_INFO, feature_elems(NodeNS)), 549: DiscoResult = escalus_stanza:iq_result(DiscoRequest, [QueryEl]), 550: escalus:send(User, DiscoResult). 551: 552: receive_presence_with_caps(User1, User2, Caps) -> 553: PresenceNotification = escalus:wait_for_stanza(User1), 554: escalus:assert(is_presence, PresenceNotification), 555: escalus:assert(is_stanza_from, [User2], PresenceNotification), 556: Caps = exml_query:subelement(PresenceNotification, <<"c">>). 557: 558: make_pep_node_info(Client, NodeName) -> 559: {escalus_utils:jid_to_lower(escalus_utils:get_short_jid(Client)), NodeName}. 560: 561: verify_publish_options(FullNodeConfig, Options) -> 562: NodeConfig = [{Opt, Value} || {Opt, _, Value} <- FullNodeConfig], 563: Options = lists:filter(fun(Option) -> 564: lists:member(Option, NodeConfig) 565: end, Options). 566: 567: set_caps(Config) -> 568: [{escalus_overrides, [{start_ready_clients, {?MODULE, start_caps_clients}}]}, 569: {node_ns, random_node_ns()} | Config]. 570: 571: %% Implemented only for one resource per client, because it is enough 572: start_caps_clients(Config, [{UserSpec, Resource}]) -> 573: NodeNS = ?config(node_ns, Config), 574: {ok, Client} = escalus_client:start(Config, UserSpec, Resource), 575: send_initial_presence_with_caps(NodeNS, Client), 576: escalus:assert(is_presence, escalus:wait_for_stanza(Client)), 577: handle_requested_caps(NodeNS, Client), 578: [Client]. 579: 580: %%----------------------------------------------------------------- 581: %% XML helpers 582: %%----------------------------------------------------------------- 583: 584: feature_elems(PEPNodeNS) -> 585: [#xmlel{name = <<"identity">>, 586: attrs = [{<<"category">>, <<"client">>}, 587: {<<"name">>, <<"Psi">>}, 588: {<<"type">>, <<"pc">>}]} | 589: [feature_elem(F) || F <- features(PEPNodeNS)]]. 590: 591: feature_elem(F) -> 592: #xmlel{name = <<"feature">>, 593: attrs = [{<<"var">>, F}]}. 594: 595: caps(PEPNodeNS) -> 596: #xmlel{name = <<"c">>, 597: attrs = [{<<"xmlns">>, ?NS_CAPS}, 598: {<<"hash">>, <<"sha-1">>}, 599: {<<"node">>, caps_node_name()}, 600: {<<"ver">>, caps_hash(PEPNodeNS)}]}. 601: 602: features(PEPNodeNS) -> 603: [?NS_DISCO_INFO, 604: ?NS_DISCO_ITEMS, 605: ?NS_GEOLOC, 606: ns_notify(?NS_GEOLOC), 607: PEPNodeNS, 608: ns_notify(PEPNodeNS)]. 609: 610: ns_notify(NS) -> 611: <<NS/binary, "+notify">>. 612: 613: random_node_ns() -> 614: base64:encode(crypto:strong_rand_bytes(16)). 615: 616: caps_hash(PEPNodeNS) -> 617: rpc(mim(), mod_caps, make_disco_hash, [feature_elems(PEPNodeNS), sha1]). 618: 619: caps_node_name() -> 620: <<"http://www.chatopus.com">>. 621: 622: send_presence(From, Type, To) -> 623: ToJid = escalus_client:short_jid(To), 624: Stanza = escalus_stanza:presence_direct(ToJid, Type), 625: escalus_client:send(From, Stanza). 626: 627: make_friends(Bob, Alice) -> 628: % makes uni-directional presence subscriptions while SM is disabled 629: % returns stanzas received finally by the inviter 630: send_presence(Bob, <<"subscribe">>, Alice), 631: escalus:assert(is_iq, escalus_client:wait_for_stanza(Bob)), 632: escalus:assert(is_presence, escalus_client:wait_for_stanza(Alice)), 633: send_presence(Alice, <<"subscribed">>, Bob), 634: escalus:assert(is_iq, escalus_client:wait_for_stanza(Alice)), 635: escalus:assert_many([is_message, is_iq] 636: ++ lists:duplicate(2, is_presence), 637: BobStanzas = escalus_client:wait_for_stanzas(Bob, 4)), 638: lists:filter(fun escalus_pred:is_message/1, BobStanzas). 639: 640: make_friends_sm(Bob, Alice) -> 641: % makes uni-directional presence subscriptions while SM is enabled 642: % returns stanzas received finally by the inviter 643: send_presence(Bob, <<"subscribe">>, Alice), 644: escalus:assert_many([is_iq, is_sm_ack_request], 645: escalus_client:wait_for_stanzas(Bob, 2)), 646: escalus:assert_many([is_presence, is_sm_ack_request], 647: escalus_client:wait_for_stanzas(Alice, 2)), 648: send_presence(Alice, <<"subscribed">>, Bob), 649: escalus:assert_many([is_iq, is_sm_ack_request], 650: escalus_client:wait_for_stanzas(Alice, 2)), 651: escalus:assert_many([is_message, is_iq] 652: ++ lists:duplicate(2, is_presence) 653: ++ lists:duplicate(4, is_sm_ack_request), 654: BobStanzas = escalus_client:wait_for_stanzas(Bob, 8)), 655: lists:filter(fun escalus_pred:is_message/1, BobStanzas). 656: 657: publish_with_sm(User, ItemId, Node, Options) -> 658: Id = id(User, Node, <<"publish">>), 659: Request = case proplists:get_value(with_payload, Options, true) of 660: true -> escalus_pubsub_stanza:publish(User, ItemId, item_content(), Id, Node); 661: false -> escalus_pubsub_stanza:publish(User, Id, Node) 662: end, 663: escalus_client:send(User, Request), 664: escalus:wait_for_stanzas(User, 2). 665: 666: id(User, {NodeAddr, NodeName}, Suffix) -> 667: UserName = escalus_utils:get_username(User), 668: list_to_binary(io_lib:format("~s-~s-~s-~s", [UserName, NodeAddr, NodeName, Suffix])). 669: 670: item_content() -> 671: #xmlel{name = <<"entry">>, 672: attrs = [{<<"xmlns">>, <<"http://www.w3.org/2005/Atom">>}]}. 673: 674: enable_sm(User) -> 675: escalus_client:send(User, escalus_stanza:enable_sm()), 676: #xmlel{name = <<"enabled">>} = escalus:wait_for_stanza(User).