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