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