1: -module(push_SUITE). 2: -compile([export_all, nowarn_export_all]). 3: -include_lib("escalus/include/escalus.hrl"). 4: -include_lib("common_test/include/ct.hrl"). 5: -include_lib("eunit/include/eunit.hrl"). 6: -include_lib("escalus/include/escalus_xmlns.hrl"). 7: -include_lib("exml/include/exml.hrl"). 8: -include("push_helper.hrl"). 9: 10: -import(muc_light_helper, 11: [ 12: room_bin_jid/1, 13: create_room/6 14: ]). 15: -import(escalus_ejabberd, [rpc/3]). 16: -import(distributed_helper, [subhost_pattern/1]). 17: -import(push_helper, [ 18: enable_stanza/2, enable_stanza/3, enable_stanza/4, 19: disable_stanza/1, disable_stanza/2, become_unavailable/1 20: ]). 21: 22: -define(RPC_SPEC, distributed_helper:mim()). 23: -define(SESSION_KEY, publish_service). 24: 25: -define(VIRTUAL_PUBSUB_DOMAIN, <<"virtual.domain">>). 26: 27: %%-------------------------------------------------------------------- 28: %% Suite configuration 29: %%-------------------------------------------------------------------- 30: 31: all() -> 32: [ 33: {group, disco}, 34: {group, toggling}, 35: {group, pubsub_ful}, 36: {group, pubsub_less} 37: ]. 38: 39: groups() -> 40: G = [ 41: {disco, [], [ 42: push_notifications_listed_disco_when_available, 43: push_notifications_not_listed_disco_when_not_available 44: ]}, 45: {toggling, [parallel], [ 46: enable_should_fail_with_missing_attributes, 47: enable_should_fail_with_invalid_attributes, 48: enable_should_succeed_without_form, 49: enable_with_form_should_fail_with_incorrect_from, 50: enable_should_accept_correct_from, 51: disable_should_fail_with_missing_attributes, 52: disable_should_fail_with_invalid_attributes, 53: disable_all, 54: disable_node, 55: disable_node_enabled_in_session_removes_it_from_session_info, 56: disable_all_nodes_removes_it_from_all_user_session_infos, 57: disable_node_enabled_in_other_session_leaves_current_info_unchanged 58: ]}, 59: {pubsub_ful, [], notification_groups()}, 60: {pubsub_less, [], notification_groups()}, 61: {pm_msg_notifications, [parallel], [ 62: pm_no_msg_notifications_if_not_enabled, 63: pm_no_msg_notifications_if_user_online, 64: pm_msg_notify_if_user_offline, 65: pm_msg_notify_if_user_offline_with_publish_options, 66: pm_msg_notify_stops_after_disabling, 67: pm_msg_notify_stops_after_removal 68: ]}, 69: {muclight_msg_notifications, [parallel], [ 70: muclight_no_msg_notifications_if_not_enabled, 71: muclight_no_msg_notifications_if_user_online, 72: muclight_msg_notify_if_user_offline, 73: muclight_msg_notify_if_user_offline_with_publish_options, 74: muclight_msg_notify_stops_after_disabling 75: ]} 76: ], 77: ct_helper:repeat_all_until_all_ok(G). 78: 79: notification_groups() -> 80: [ 81: {group, pm_msg_notifications}, 82: {group, muclight_msg_notifications} 83: ]. 84: 85: suite() -> 86: escalus:suite(). 87: 88: %%-------------------------------------------------------------------- 89: %% Init & teardown 90: %%-------------------------------------------------------------------- 91: 92: %% --------------------- Callbacks ------------------------ 93: 94: init_per_suite(Config) -> 95: %% For mocking with unnamed functions 96: mongoose_helper:inject_module(?MODULE), 97: escalus:init_per_suite(Config). 98: end_per_suite(Config) -> 99: escalus_fresh:clean(), 100: escalus:end_per_suite(Config). 101: 102: init_per_group(disco, Config) -> 103: escalus:create_users(Config, escalus:get_users([alice])); 104: init_per_group(pubsub_ful, Config) -> 105: [{pubsub_host, real} | Config]; 106: init_per_group(pubsub_less, Config) -> 107: [{pubsub_host, virtual} | Config]; 108: init_per_group(muclight_msg_notifications, Config0) -> 109: HostType = domain_helper:host_type(), 110: Config = ensure_pusher_module_and_save_old_mods(Config0), 111: dynamic_modules:ensure_modules(HostType, [{mod_muc_light, 112: [{host, subhost_pattern(?MUCHOST)}, 113: {backend, mongoose_helper:mnesia_or_rdbms_backend()}, 114: {rooms_in_rosters, true}]}]), 115: muc_light_helper:clear_db(HostType), 116: Config; 117: init_per_group(_, Config) -> 118: ensure_pusher_module_and_save_old_mods(Config). 119: 120: end_per_group(disco, Config) -> 121: escalus:delete_users(Config), 122: Config; 123: end_per_group(ComplexGroup, Config) when ComplexGroup == pubsub_ful; 124: ComplexGroup == pubsub_less -> 125: Config; 126: end_per_group(_, Config) -> 127: restore_modules(Config), 128: Config. 129: 130: init_per_testcase(CaseName = push_notifications_listed_disco_when_available, Config1) -> 131: Config2 = ensure_pusher_module_and_save_old_mods(Config1), 132: escalus:init_per_testcase(CaseName, Config2); 133: init_per_testcase(CaseName = push_notifications_not_listed_disco_when_not_available, Config) -> 134: escalus:init_per_testcase(CaseName, Config); 135: init_per_testcase(CaseName, Config0) -> 136: Config1 = escalus_fresh:create_users(Config0, [{bob, 1}, {alice, 1}, {kate, 1}]), 137: Config2 = add_pubsub_jid([{case_name, CaseName} | Config1]), 138: 139: Config = case ?config(pubsub_host, Config0) of 140: virtual -> 141: start_hook_listener(Config2); 142: _ -> 143: start_route_listener(Config2) 144: end, 145: 146: escalus:init_per_testcase(CaseName, Config). 147: 148: end_per_testcase(CaseName = push_notifications_listed_disco_when_available, Config) -> 149: restore_modules(Config), 150: escalus:end_per_testcase(CaseName, Config); 151: end_per_testcase(CaseName = push_notifications_not_listed_disco_when_not_available, Config) -> 152: escalus:end_per_testcase(CaseName, Config); 153: end_per_testcase(CaseName, Config) -> 154: case ?config(pubsub_host, Config) of 155: virtual -> 156: stop_hook_listener(Config); 157: _ -> 158: stop_route_listener(Config) 159: end, 160: escalus:end_per_testcase(CaseName, Config). 161: 162: %% --------------------- Helpers ------------------------ 163: ensure_pusher_module_and_save_old_mods(Config) -> 164: PushOpts = [{virtual_pubsub_hosts, [subhost_pattern(?VIRTUAL_PUBSUB_DOMAIN)]}, 165: {backend, mongoose_helper:mnesia_or_rdbms_backend()}], 166: HostType = domain_helper:host_type(), 167: Config1 = dynamic_modules:save_modules(HostType, Config), 168: PusherMod = {mod_event_pusher, [{backends, [{push, PushOpts}]}]}, 169: dynamic_modules:ensure_modules(HostType, [PusherMod]), 170: [{push_opts, PushOpts} | Config1]. 171: 172: restore_modules(Config) -> 173: dynamic_modules:restore_modules(Config). 174: 175: %%-------------------------------------------------------------------- 176: %% GROUP disco 177: %%-------------------------------------------------------------------- 178: 179: push_notifications_listed_disco_when_available(Config) -> 180: escalus:story( 181: Config, [{alice, 1}], 182: fun(Alice) -> 183: Server = escalus_client:server(Alice), 184: escalus:send(Alice, escalus_stanza:disco_info(Server)), 185: Stanza = escalus:wait_for_stanza(Alice), 186: escalus:assert(is_iq_result, Stanza), 187: escalus:assert(has_feature, [push_helper:ns_push()], Stanza), 188: ok 189: end). 190: 191: push_notifications_not_listed_disco_when_not_available(Config) -> 192: escalus:story( 193: Config, [{alice, 1}], 194: fun(Alice) -> 195: Server = escalus_client:server(Alice), 196: escalus:send(Alice, escalus_stanza:disco_info(Server)), 197: Stanza = escalus:wait_for_stanza(Alice), 198: escalus:assert(is_iq_result, Stanza), 199: Pred = fun(Feature, Stanza0) -> not escalus_pred:has_feature(Feature, Stanza0) end, 200: escalus:assert(Pred, [push_helper:ns_push()], Stanza), 201: ok 202: end). 203: 204: %%-------------------------------------------------------------------- 205: %% GROUP toggling 206: %%-------------------------------------------------------------------- 207: 208: enable_should_fail_with_missing_attributes(Config) -> 209: escalus:story( 210: Config, [{bob, 1}], 211: fun(Bob) -> 212: BobJID = escalus_utils:get_jid(Bob), 213: 214: escalus:send(Bob, escalus_stanza:iq(<<"set">>, [#xmlel{name = <<"enable">>}])), 215: escalus:assert(is_error, [<<"modify">>, <<"bad-request">>], 216: escalus:wait_for_stanza(Bob)), 217: 218: CorrectAttrs = [{<<"xmlns">>, <<"urn:xmpp:push:0">>}, 219: {<<"jid">>, BobJID}, 220: {<<"node">>, <<"NodeKey">>}], 221: 222: %% Sending only one attribute should fail 223: lists:foreach( 224: fun(Attr) -> 225: escalus:send(Bob, escalus_stanza:iq(<<"set">>, 226: [#xmlel{name = <<"enable">>, 227: attrs = [Attr]}])), 228: escalus:assert(is_error, [<<"modify">>, <<"bad-request">>], 229: escalus:wait_for_stanza(Bob)) 230: end, CorrectAttrs), 231: 232: %% Sending all but one attribute should fail 233: lists:foreach( 234: fun(Attr) -> 235: escalus:send(Bob, escalus_stanza:iq(<<"set">>, 236: [#xmlel{name = <<"enable">>, 237: attrs = CorrectAttrs -- [Attr]}])), 238: escalus:assert(is_error, [<<"modify">>, <<"bad-request">>], 239: escalus:wait_for_stanza(Bob)) 240: end, CorrectAttrs), 241: 242: ok 243: end). 244: 245: enable_should_fail_with_invalid_attributes(Config) -> 246: escalus:story( 247: Config, [{bob, 1}], 248: fun(Bob) -> 249: PubsubJID = pubsub_jid(Config), 250: 251: %% Empty JID 252: escalus:send(Bob, enable_stanza(<<>>, <<"nodeId">>)), 253: escalus:assert(is_error, [<<"modify">>, <<"bad-request">>], 254: escalus:wait_for_stanza(Bob)), 255: 256: %% Empty node 257: escalus:send(Bob, enable_stanza(PubsubJID, <<>>)), 258: escalus:assert(is_error, [<<"modify">>, <<"bad-request">>], 259: escalus:wait_for_stanza(Bob)), 260: ok 261: end). 262: 263: 264: enable_should_succeed_without_form(Config) -> 265: escalus:story( 266: Config, [{bob, 1}], 267: fun(Bob) -> 268: PubsubJID = pubsub_jid(Config), 269: 270: escalus:send(Bob, enable_stanza(PubsubJID, <<"NodeId">>)), 271: escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob)), 272: 273: ok 274: end). 275: 276: enable_with_form_should_fail_with_incorrect_from(Config) -> 277: escalus:story( 278: Config, [{bob, 1}], 279: fun(Bob) -> 280: PubsubJID = pubsub_jid(Config), 281: 282: escalus:send(Bob, enable_stanza(PubsubJID, <<"NodeId">>, [], <<"wrong">>)), 283: escalus:assert(is_error, [<<"modify">>, <<"bad-request">>], 284: escalus:wait_for_stanza(Bob)), 285: ok 286: end). 287: 288: enable_should_accept_correct_from(Config) -> 289: escalus:story( 290: Config, [{bob, 1}], 291: fun(Bob) -> 292: PubsubJID = pubsub_jid(Config), 293: 294: escalus:send(Bob, enable_stanza(PubsubJID, <<"NodeId">>, [])), 295: escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob)), 296: 297: escalus:send(Bob, enable_stanza(PubsubJID, <<"NodeId">>, [ 298: {<<"secret1">>, <<"token1">>}, 299: {<<"secret2">>, <<"token2">>} 300: ])), 301: escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob)), 302: 303: ok 304: end). 305: 306: disable_should_fail_with_missing_attributes(Config) -> 307: escalus:story( 308: Config, [{bob, 1}], 309: fun(Bob) -> 310: BobJID = escalus_utils:get_jid(Bob), 311: 312: escalus:send(Bob, escalus_stanza:iq(<<"set">>, [#xmlel{name = <<"disable">>}])), 313: escalus:assert(is_error, [<<"modify">>, <<"bad-request">>], 314: escalus:wait_for_stanza(Bob)), 315: 316: CorrectAttrs = [{<<"xmlns">>, <<"urn:xmpp:push:0">>}, {<<"jid">>, BobJID}], 317: 318: %% Sending only one attribute should fail 319: lists:foreach( 320: fun(Attr) -> 321: escalus:send(Bob, escalus_stanza:iq(<<"set">>, 322: [#xmlel{name = <<"disable">>, 323: attrs = [Attr]}])), 324: escalus:assert(is_error, [<<"modify">>, <<"bad-request">>], 325: escalus:wait_for_stanza(Bob)) 326: end, CorrectAttrs), 327: ok 328: end). 329: 330: disable_should_fail_with_invalid_attributes(Config) -> 331: escalus:story( 332: Config, [{bob, 1}], 333: fun(Bob) -> 334: %% Empty JID 335: escalus:send(Bob, disable_stanza(<<>>, <<"nodeId">>)), 336: escalus:assert(is_error, [<<"modify">>, <<"bad-request">>], 337: escalus:wait_for_stanza(Bob)), 338: escalus:send(Bob, disable_stanza(<<>>)), 339: escalus:assert(is_error, [<<"modify">>, <<"bad-request">>], 340: escalus:wait_for_stanza(Bob)), 341: ok 342: end). 343: 344: disable_all(Config) -> 345: escalus:story( 346: Config, [{bob, 1}], 347: fun(Bob) -> 348: PubsubJID = pubsub_jid(Config), 349: 350: escalus:send(Bob, disable_stanza(PubsubJID)), 351: escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob)), 352: 353: ok 354: end). 355: 356: disable_node(Config) -> 357: escalus:story( 358: Config, [{bob, 1}], 359: fun(Bob) -> 360: PubsubJID = pubsub_jid(Config), 361: 362: escalus:send(Bob, disable_stanza(PubsubJID, <<"NodeId">>)), 363: escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob)), 364: 365: ok 366: end). 367: 368: disable_node_enabled_in_session_removes_it_from_session_info(Config) -> 369: escalus:fresh_story( 370: Config, [{bob, 1}], 371: fun(Bob) -> 372: PubsubJID = pubsub_jid(Config), 373: NodeId = pubsub_tools:pubsub_node_name(), 374: 375: escalus:send(Bob, enable_stanza(PubsubJID, NodeId, [])), 376: escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob)), 377: 378: Info = mongoose_helper:get_session_info(?RPC_SPEC, Bob), 379: {_JID, NodeId, _} = maps:get(?SESSION_KEY, Info), 380: 381: escalus:send(Bob, disable_stanza(PubsubJID, NodeId)), 382: escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob)), 383: 384: Info2 = mongoose_helper:get_session_info(?RPC_SPEC, Bob), 385: false = maps:get(?SESSION_KEY, Info2, false) 386: end). 387: 388: disable_all_nodes_removes_it_from_all_user_session_infos(Config) -> 389: escalus:fresh_story( 390: Config, [{bob, 2}], 391: fun(Bob1, Bob2) -> 392: PubsubJID = pubsub_jid(Config), 393: NodeId = pubsub_tools:pubsub_node_name(), 394: NodeId2 = pubsub_tools:pubsub_node_name(), 395: 396: escalus:send(Bob1, enable_stanza(PubsubJID, NodeId, [])), 397: escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob1)), 398: 399: escalus:send(Bob2, enable_stanza(PubsubJID, NodeId2, [])), 400: escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob2)), 401: 402: Info = mongoose_helper:get_session_info(?RPC_SPEC, Bob1), 403: {JID, NodeId, _} = maps:get(?SESSION_KEY, Info), 404: 405: Info2 = mongoose_helper:get_session_info(?RPC_SPEC, Bob2), 406: {JID, NodeId2, _} = maps:get(?SESSION_KEY, Info2), 407: 408: %% Now Bob1 disables all nodes 409: escalus:send(Bob1, disable_stanza(PubsubJID)), 410: escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob1)), 411: 412: %% And we check if Bob1 and Bob2 have push notifications cleared from session info 413: Info3 = mongoose_helper:get_session_info(?RPC_SPEC, Bob1), 414: false = maps:get(?SESSION_KEY, Info3, false), 415: 416: Info4 = mongoose_helper:get_session_info(?RPC_SPEC, Bob2), 417: false = maps:get(?SESSION_KEY, Info4, false) 418: end). 419: 420: disable_node_enabled_in_other_session_leaves_current_info_unchanged(Config) -> 421: escalus:fresh_story( 422: Config, [{bob, 2}], 423: fun(Bob1, Bob2) -> 424: PubsubJID = pubsub_jid(Config), 425: NodeId = pubsub_tools:pubsub_node_name(), 426: NodeId2 = pubsub_tools:pubsub_node_name(), 427: 428: escalus:send(Bob1, enable_stanza(PubsubJID, NodeId, [])), 429: escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob1)), 430: 431: escalus:send(Bob2, enable_stanza(PubsubJID, NodeId2, [])), 432: escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob2)), 433: 434: Info = mongoose_helper:get_session_info(?RPC_SPEC, Bob1), 435: {JID, NodeId, _} = maps:get(?SESSION_KEY, Info), 436: 437: Info2 = mongoose_helper:get_session_info(?RPC_SPEC, Bob2), 438: {JID, NodeId2, _} = maps:get(?SESSION_KEY, Info2), 439: 440: %% Now Bob1 disables the node registered by Bob2 441: escalus:send(Bob1, disable_stanza(PubsubJID, NodeId)), 442: escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob1)), 443: 444: %% And we check if Bob1 still has its own Node in the session info 445: Info3 = mongoose_helper:get_session_info(?RPC_SPEC, Bob1), 446: false = maps:get(?SESSION_KEY, Info3, false) 447: end). 448: 449: %%-------------------------------------------------------------------- 450: %% GROUP pm_msg_notifications 451: %%-------------------------------------------------------------------- 452: 453: pm_no_msg_notifications_if_not_enabled(Config) -> 454: escalus:story( 455: Config, [{bob, 1}, {alice, 1}], 456: fun(Bob, Alice) -> 457: become_unavailable(Bob), 458: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)), 459: 460: ?assert(not truly(received_push())), 461: ok 462: end). 463: 464: pm_no_msg_notifications_if_user_online(Config) -> 465: escalus:story( 466: Config, [{bob, 1}, {alice, 1}], 467: fun(Bob, Alice) -> 468: PubsubJID = pubsub_jid(Config), 469: 470: escalus:send(Bob, enable_stanza(PubsubJID, <<"NodeId">>)), 471: escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob)), 472: 473: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)), 474: 475: ?assert(not truly(received_push())), 476: ok 477: end). 478: 479: pm_msg_notify_if_user_offline(Config) -> 480: escalus:story( 481: Config, [{bob, 1}, {alice, 1}], 482: fun(Bob, Alice) -> 483: PubsubJID = pubsub_jid(Config), 484: 485: AliceJID = bare_jid(Alice), 486: escalus:send(Bob, enable_stanza(PubsubJID, <<"NodeId">>)), 487: escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob)), 488: become_unavailable(Bob), 489: 490: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)), 491: 492: #{ payload := Payload } = received_push(), 493: ?assertMatch(<<"OH, HAI!">>, proplists:get_value(<<"last-message-body">>, Payload)), 494: ?assertMatch(AliceJID, 495: proplists:get_value(<<"last-message-sender">>, Payload)), 496: 497: ok 498: end). 499: 500: pm_msg_notify_if_user_offline_with_publish_options(Config) -> 501: escalus:story( 502: Config, [{bob, 1}, {alice, 1}], 503: fun(Bob, Alice) -> 504: PubsubJID = pubsub_jid(Config), 505: 506: escalus:send(Bob, enable_stanza(PubsubJID, <<"NodeId">>, 507: [{<<"field1">>, <<"value1">>}, 508: {<<"field2">>, <<"value2">>}])), 509: escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob)), 510: become_unavailable(Bob), 511: 512: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)), 513: 514: #{ publish_options := PublishOptions } = received_push(), 515: 516: ?assertMatch(<<"value1">>, proplists:get_value(<<"field1">>, PublishOptions)), 517: ?assertMatch(<<"value2">>, proplists:get_value(<<"field2">>, PublishOptions)), 518: ok 519: end). 520: 521: pm_msg_notify_stops_after_disabling(Config) -> 522: escalus:story( 523: Config, [{bob, 1}, {alice, 1}], 524: fun(Bob, Alice) -> 525: PubsubJID = pubsub_jid(Config), 526: 527: %% Enable 528: escalus:send(Bob, enable_stanza(PubsubJID, <<"NodeId">>, [])), 529: escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob)), 530: 531: %% Disable 532: escalus:send(Bob, disable_stanza(PubsubJID, <<"NodeId">>)), 533: escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob)), 534: become_unavailable(Bob), 535: 536: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)), 537: 538: ?assert(not received_push()), 539: 540: ok 541: end). 542: 543: pm_msg_notify_stops_after_removal(Config) -> 544: PubsubJID = pubsub_jid(Config), 545: escalus:story( 546: Config, [{bob, 1}], 547: fun(Bob) -> 548: %% Enable 549: escalus:send(Bob, enable_stanza(PubsubJID, <<"NodeId">>, [])), 550: escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob)), 551: 552: %% Remove account - this should disable the notifications 553: Pid = mongoose_helper:get_session_pid(Bob, distributed_helper:mim()), 554: escalus_connection:send(Bob, escalus_stanza:remove_account()), 555: escalus:assert(is_iq_result, escalus:wait_for_stanza(Bob)), 556: mongoose_helper:wait_for_pid_to_die(Pid) 557: end), 558: BobUser = lists:keyfind(bob, 1, escalus_config:get_config(escalus_users, Config)), 559: escalus_users:create_user(Config, BobUser), 560: escalus:story( 561: Config, [{bob, 1}, {alice, 1}], 562: fun(Bob, Alice) -> 563: become_unavailable(Bob), 564: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)), 565: ?assert(not truly(received_push())) 566: end). 567: 568: %%-------------------------------------------------------------------- 569: %% GROUP muclight_msg_notifications 570: %%-------------------------------------------------------------------- 571: 572: muclight_no_msg_notifications_if_not_enabled(Config) -> 573: escalus:story( 574: Config, [{alice, 1}, {bob, 1}, {kate, 1}], 575: fun(Alice, Bob, Kate) -> 576: Room = room_name(Config), 577: create_room(Room, [bob, alice, kate], Config), 578: become_unavailable(Alice), 579: become_unavailable(Kate), 580: 581: Msg = <<"Heyah!">>, 582: Stanza = escalus_stanza:groupchat_to(room_bin_jid(Room), Msg), 583: 584: escalus:send(Bob, Stanza), 585: 586: ?assert(not truly(received_push())), 587: 588: ok 589: end). 590: 591: muclight_no_msg_notifications_if_user_online(Config) -> 592: escalus:story( 593: Config, [{alice, 1}, {bob, 1}, {kate, 1}], 594: fun(Alice, Bob, Kate) -> 595: Room = room_name(Config), 596: PubsubJID = pubsub_jid(Config), 597: 598: create_room(Room, [bob, alice, kate], Config), 599: escalus:send(Alice, enable_stanza(PubsubJID, <<"NodeId">>)), 600: escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)), 601: become_unavailable(Kate), 602: 603: Msg = <<"Heyah!">>, 604: Stanza = escalus_stanza:groupchat_to(room_bin_jid(Room), Msg), 605: escalus:send(Bob, Stanza), 606: 607: ?assert(not truly(received_push())), 608: ok 609: end). 610: 611: muclight_msg_notify_if_user_offline(Config) -> 612: escalus:story( 613: Config, [{alice, 1}, {bob, 1}, {kate, 1}], 614: fun(Alice, Bob, _Kate) -> 615: PubsubJID = pubsub_jid(Config), 616: Room = room_name(Config), 617: BobJID = bare_jid(Bob), 618: 619: create_room(Room, [bob, alice, kate], Config), 620: escalus:send(Alice, enable_stanza(PubsubJID, <<"NodeId">>)), 621: escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)), 622: become_unavailable(Alice), 623: 624: Msg = <<"Heyah!">>, 625: Stanza = escalus_stanza:groupchat_to(room_bin_jid(Room), Msg), 626: escalus:send(Bob, Stanza), 627: 628: #{ payload := Payload } = received_push(), 629: 630: ?assertMatch(Msg, proplists:get_value(<<"last-message-body">>, Payload)), 631: SenderId = <<(room_bin_jid(Room))/binary, "/" ,BobJID/binary>>, 632: ?assertMatch(SenderId, 633: proplists:get_value(<<"last-message-sender">>, Payload)), 634: ok 635: end). 636: 637: muclight_msg_notify_if_user_offline_with_publish_options(Config) -> 638: escalus:story( 639: Config, [{alice, 1}, {bob, 1}, {kate, 1}], 640: fun(Alice, Bob, _Kate) -> 641: PubsubJID = pubsub_jid(Config), 642: Room = room_name(Config), 643: 644: create_room(Room, [bob, alice, kate], Config), 645: escalus:send(Alice, enable_stanza(PubsubJID, <<"NodeId">>, 646: [{<<"field1">>, <<"value1">>}, 647: {<<"field2">>, <<"value2">>}])), 648: escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)), 649: become_unavailable(Alice), 650: 651: Msg = <<"Heyah!">>, 652: Stanza = escalus_stanza:groupchat_to(room_bin_jid(Room), Msg), 653: escalus:send(Bob, Stanza), 654: 655: #{ publish_options := PublishOptions } = received_push(), 656: 657: ?assertMatch(<<"value1">>, proplists:get_value(<<"field1">>, PublishOptions)), 658: ?assertMatch(<<"value2">>, proplists:get_value(<<"field2">>, PublishOptions)), 659: ok 660: end). 661: 662: muclight_msg_notify_stops_after_disabling(Config) -> 663: escalus:story( 664: Config, [{alice, 1}, {bob, 1}, {kate, 1}], 665: fun(Alice, Bob, _Kate) -> 666: Room = room_name(Config), 667: PubsubJID = pubsub_jid(Config), 668: create_room(Room, [bob, alice, kate], Config), 669: 670: %% Enable 671: escalus:send(Alice, enable_stanza(PubsubJID, <<"NodeId">>)), 672: escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)), 673: 674: %% Disable 675: escalus:send(Alice, disable_stanza(PubsubJID, <<"NodeId">>)), 676: escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)), 677: become_unavailable(Alice), 678: 679: Msg = <<"Heyah!">>, 680: Stanza = escalus_stanza:groupchat_to(room_bin_jid(Room), Msg), 681: escalus:send(Bob, Stanza), 682: 683: ?assert(not truly(received_push())), 684: ok 685: end). 686: 687: %%-------------------------------------------------------------------- 688: %% Remote code 689: %% Functions that will be executed in MongooseIM context + helpers that set them up 690: %%-------------------------------------------------------------------- 691: 692: start_route_listener(Config) -> 693: %% We put namespaces in the state to avoid injecting push_helper module to MIM as well 694: State = #{ pid => self(), 695: pub_options_ns => push_helper:ns_pubsub_pub_options(), 696: push_form_ns => push_helper:push_form_type() }, 697: Handler = rpc(mongoose_packet_handler, new, [?MODULE, #{state => State}]), 698: Domain = pubsub_domain(Config), 699: rpc(mongoose_router, register_route, [Domain, Handler]), 700: Config. 701: 702: stop_route_listener(Config) -> 703: Domain = pubsub_domain(Config), 704: rpc(mongoose_router, unregister_route, [Domain]). 705: 706: process_packet(_Acc, _From, To, El, #{state := State}) -> 707: #{ pid := TestCasePid, pub_options_ns := PubOptionsNS, push_form_ns := PushFormNS } = State, 708: PublishXML = exml_query:path(El, [{element, <<"pubsub">>}, 709: {element, <<"publish-options">>}, 710: {element, <<"x">>}]), 711: PublishOptions = parse_form(PublishXML), 712: 713: PayloadXML = exml_query:path(El, [{element, <<"pubsub">>}, 714: {element, <<"publish">>}, 715: {element, <<"item">>}, 716: {element, <<"notification">>}, 717: {element, <<"x">>}]), 718: Payload = parse_form(PayloadXML), 719: 720: case valid_ns_if_defined(PubOptionsNS, PublishOptions) andalso 721: valid_ns_if_defined(PushFormNS, Payload) of 722: true -> 723: TestCasePid ! push_notification(jid:to_binary(To), Payload, PublishOptions); 724: false -> 725: %% We use publish_options0 and payload0 to avoid accidental match in received_push 726: %% even after some tests updates and refactors 727: TestCasePid ! #{ error => invalid_namespace, 728: publish_options0 => PublishOptions, 729: payload0 => Payload } 730: end. 731: 732: parse_form(undefined) -> 733: undefined; 734: parse_form(#xmlel{name = <<"x">>} = Form) -> 735: parse_form(exml_query:subelements(Form, <<"field">>)); 736: parse_form(Fields) when is_list(Fields) -> 737: lists:map( 738: fun(Field) -> 739: {exml_query:attr(Field, <<"var">>), 740: exml_query:path(Field, [{element, <<"value">>}, cdata])} 741: end, Fields). 742: 743: valid_ns_if_defined(_, undefined) -> 744: true; 745: valid_ns_if_defined(NS, FormProplist) -> 746: NS =:= proplists:get_value(<<"FORM_TYPE">>, FormProplist). 747: 748: start_hook_listener(Config) -> 749: TestCasePid = self(), 750: PubSubJID = pubsub_jid(Config), 751: rpc(?MODULE, rpc_start_hook_handler, [TestCasePid, PubSubJID]), 752: [{pid, TestCasePid}, {jid, PubSubJID} | Config]. 753: 754: stop_hook_listener(Config) -> 755: TestCasePid = proplists:get_value(pid, Config), 756: PubSubJID = proplists:get_value(jid, Config), 757: rpc(?MODULE, rpc_stop_hook_handler, [TestCasePid, PubSubJID]). 758: 759: rpc_start_hook_handler(TestCasePid, PubSubJID) -> 760: gen_hook:add_handler(push_notifications, <<"localhost">>, 761: fun ?MODULE:hook_handler_fn/3, 762: #{pid => TestCasePid, jid => PubSubJID}, 50). 763: 764: hook_handler_fn(Acc, 765: #{notification_forms := [PayloadMap], options := OptionMap} = _Params, 766: #{pid := TestCasePid, jid := PubSubJID} = _Extra) -> 767: try jid:to_binary(mongoose_acc:get(push_notifications, pubsub_jid, Acc)) of 768: PubSubJIDBin when PubSubJIDBin =:= PubSubJID -> 769: TestCasePid ! push_notification(PubSubJIDBin, 770: maps:to_list(PayloadMap), 771: maps:to_list(OptionMap)); 772: _ -> ok 773: catch 774: C:R:S -> 775: TestCasePid ! #{event => handler_error, 776: class => C, 777: reason => R, 778: stacktrace => S} 779: end, 780: {ok, Acc}. 781: 782: rpc_stop_hook_handler(TestCasePid, PubSubJID) -> 783: gen_hook:delete_handler(push_notifications, <<"localhost">>, 784: fun ?MODULE:hook_handler_fn/3, 785: #{pid => TestCasePid, jid => PubSubJID}, 50). 786: 787: %%-------------------------------------------------------------------- 788: %% Test helpers 789: %%-------------------------------------------------------------------- 790: 791: create_room(Room, [Owner | Members], Config) -> 792: Domain = domain_helper:domain(), 793: create_room(Room, <<"muclight.", Domain/binary>>, Owner, Members, 794: Config, <<"v1">>). 795: 796: received_push() -> 797: receive 798: #{ push_notification := true } = Push -> Push 799: after 800: timer:seconds(5) -> 801: ct:pal("~p", [#{ result => nomatch, msg_inbox => process_info(self(), messages) }]), 802: false 803: end. 804: 805: truly(false) -> 806: false; 807: truly(#{ push_notification := true }) -> 808: true. 809: 810: push_notification(PubsubJID, Payload, PublishOpts) -> 811: #{push_notification => true, pubsub_jid_bin => PubsubJID, 812: publish_options => PublishOpts, payload => Payload}. 813: 814: bare_jid(JIDOrClient) -> 815: ShortJID = escalus_client:short_jid(JIDOrClient), 816: list_to_binary(string:to_lower(binary_to_list(ShortJID))). 817: 818: add_pubsub_jid(Config) -> 819: CaseName = proplists:get_value(case_name, Config), 820: CaseNameBin = atom_to_binary(CaseName, utf8), 821: NameSuffix = uniq_name_suffix(), 822: UniqID = <<CaseNameBin/binary, "_", NameSuffix/binary>>, 823: {PubSubNodeName, PubSubDomain} = 824: case ?config(pubsub_host, Config) of 825: virtual -> 826: %% unique node name, but preconfigured domain name 827: {UniqID, ?VIRTUAL_PUBSUB_DOMAIN}; 828: _ -> 829: %% any node name, but unique domain. unique domain 830: %% is required to intercept safely message routing 831: {<<"pub-sub">>, UniqID} 832: end, 833: PubSubJID = <<PubSubNodeName/binary, "@", PubSubDomain/binary>>, 834: [{pubsub_jid, PubSubJID}, {pubsub_domain, PubSubDomain} | Config]. 835: 836: uniq_name_suffix() -> 837: {_, S, US} = erlang:timestamp(), 838: L = lists:flatten([integer_to_list(S rem 100), ".", integer_to_list(US)]), 839: list_to_binary(L). 840: 841: pubsub_domain(Config) -> 842: proplists:get_value(pubsub_domain, Config). 843: 844: pubsub_jid(Config) -> 845: proplists:get_value(pubsub_jid, Config). 846: 847: room_name(Config) -> 848: CaseName = proplists:get_value(case_name, Config), 849: <<"room_", (atom_to_binary(CaseName, utf8))/binary>>.