1: -module(amp_big_SUITE). 2: %% @doc Tests for XEP-0079 Advanced Message Processing support 3: %% @reference <a href="http://xmpp.org/extensions/xep-0079.html">XEP-0079</a> 4: %% @author <simon.zelazny@erlang-solutions.com> 5: %% @copyright 2014 Erlang Solutions, Ltd. 6: %% This work was sponsored by Grindr.com 7: 8: -compile([export_all, nowarn_export_all]). 9: -include_lib("common_test/include/ct.hrl"). 10: -include_lib("escalus/include/escalus.hrl"). 11: -include_lib("escalus/include/escalus_xmlns.hrl"). 12: -include_lib("exml/include/exml.hrl"). 13: 14: -import(distributed_helper, [mim/0, 15: require_rpc_nodes/1, 16: rpc/4]). 17: -import(muc_light_helper, [lbin/1]). 18: -import(domain_helper, [host_type/0, domain/0]). 19: 20: suite() -> 21: require_rpc_nodes([mim]) ++ escalus:suite(). 22: 23: all() -> 24: [{group, G} || G <- main_group_names(), is_enabled(G)]. 25: 26: groups() -> 27: group_spec(main_group_names()). 28: 29: is_enabled(mam) -> mongoose_helper:is_rdbms_enabled(host_type()); 30: is_enabled(_) -> true. 31: 32: %% Group definitions 33: 34: main_group_names() -> 35: [basic, mam, offline]. 36: 37: subgroups(mam) -> [mam_success, mam_failure]; 38: subgroups(offline) -> [offline_success, offline_failure]; 39: subgroups(_) -> []. 40: 41: group_spec(Groups) when is_list(Groups) -> 42: lists:flatmap(fun group_spec/1, Groups); 43: group_spec(Group) -> 44: case subgroups(Group) of 45: [] -> [{Group, [parallel], test_cases(Group)}]; 46: SubGroups -> [{Group, [{group, SubG} || SubG <- SubGroups]} | group_spec(SubGroups)] 47: end. 48: 49: test_cases(Group) -> 50: regular_tests(Group) ++ multiple_config_cth:flatten_and_strip_config(tests_with_config(Group)). 51: 52: regular_tests(basic) -> basic_test_cases(); 53: regular_tests(_) -> []. 54: 55: %% This function is called by multiple_config_cth for each group 56: %% to get a list of configs for each test case 57: -spec tests_with_config(_GroupName :: atom()) -> [{TestCase :: atom(), 58: [Config :: [{Key :: atom(), Value :: term()}]]}]. 59: tests_with_config(_GroupName) -> 60: lists:append([deliver_tests_with_config(notify), 61: deliver_tests_with_config(error), 62: deliver_tests_with_config(drop)]). 63: 64: %% Each of the 'deliver' tests is repeated several times, each time with a different config 65: deliver_tests_with_config(Action) -> 66: multiple_config_cth:add_config(deliver_rule_configs(Action), deliver_test_cases(Action)). 67: 68: %% Each config tests different rules in the AMP message 69: deliver_rule_configs(Action) -> 70: [ 71: [{rules, [{deliver, direct, Action}]}], 72: [{rules, [{deliver, stored, Action}]}], 73: [{rules, [{deliver, none, Action}]}], 74: [{rules, [{deliver, direct, Action}, 75: {deliver, stored, Action}, 76: {deliver, none, Action}]}] 77: ]. 78: 79: %% Test case list, each test has to be listed exactly once 80: 81: basic_test_cases() -> 82: [initial_service_discovery_test, 83: actions_and_conditions_discovery_test, 84: 85: unsupported_actions_test, 86: unsupported_conditions_test, 87: unacceptable_rules_test, 88: 89: notify_match_resource_any_test, 90: notify_match_resource_exact_test, 91: notify_match_resource_other_test, 92: notify_match_resource_other_bare_test, 93: 94: last_rule_applies_test]. 95: 96: deliver_test_cases(notify) -> 97: [notify_deliver_to_online_user_test, 98: notify_deliver_to_online_user_bare_jid_test, 99: notify_deliver_to_online_user_recipient_privacy_test, 100: notify_deliver_to_offline_user_test, 101: notify_deliver_to_offline_user_recipient_privacy_test, 102: notify_deliver_to_online_user_broken_connection_test, 103: notify_deliver_to_stranger_test, 104: notify_deliver_to_unknown_domain_test]; 105: deliver_test_cases(error) -> 106: [error_deliver_to_online_user_test, 107: error_deliver_to_offline_user_test, 108: error_deliver_to_stranger_test]; 109: deliver_test_cases(drop) -> 110: [drop_deliver_to_online_user_test, 111: drop_deliver_to_offline_user_test, 112: drop_deliver_to_stranger_test]. 113: 114: %% Setup and teardown 115: 116: init_per_suite(Config) -> 117: ConfigWithHooks = [{ct_hooks, [{multiple_config_cth, fun tests_with_config/1}]} | Config], 118: {Mod, Code} = rpc(mim(), dynamic_compile, from_string, [amp_test_helper_code()]), 119: rpc(mim(), code, load_binary, [Mod, "amp_test_helper.erl", Code]), 120: setup_meck(suite), 121: escalus:init_per_suite(ConfigWithHooks). 122: 123: amp_test_helper_code() -> 124: "-module(amp_test_helper).\n" 125: "-compile([export_all, nowarn_export_all]).\n" 126: "setup_meck() ->\n" 127: " meck:expect(ranch_tcp, send, fun ranch_tcp_send/2).\n" 128: "ranch_tcp_send(Socket, Data) ->\n" 129: " case catch binary:match(Data, <<\"Recipient connection breaks\">>) of\n" 130: " {N, _} when is_integer(N) -> {error, simulated};\n" 131: " _ -> meck:passthrough([Socket, Data])\n" 132: " end.\n". 133: 134: end_per_suite(C) -> 135: teardown_meck(suite), 136: escalus_fresh:clean(), 137: escalus:end_per_suite(C). 138: 139: init_per_group(GroupName, Config) -> 140: Config1 = case lists:member(GroupName, main_group_names()) of 141: true -> 142: ConfigWithModules = dynamic_modules:save_modules(host_type(), Config), 143: dynamic_modules:ensure_modules(host_type(), required_modules(GroupName)), 144: ConfigWithModules; 145: false -> 146: Config 147: end, 148: setup_meck(GroupName), 149: save_offline_status(GroupName, Config1). 150: 151: setup_meck(suite) -> 152: ok = rpc(mim(), meck, new, [ranch_tcp, [passthrough, no_link]]), 153: ok = rpc(mim(), amp_test_helper, setup_meck, []); 154: setup_meck(mam_failure) -> 155: ok = rpc(mim(), meck, expect, [mod_mam_rdbms_arch, archive_message, 3, {ok, {error, simulated}}]); 156: setup_meck(offline_failure) -> 157: ok = rpc(mim(), meck, expect, [mod_offline_backend_module(), write_messages, 4, {error, simulated}]); 158: setup_meck(_) -> ok. 159: 160: save_offline_status(mam_success, Config) -> [{offline_storage, mam} | Config]; 161: save_offline_status(mam_failure, Config) -> [{offline_storage, mam_failure} | Config]; 162: save_offline_status(offline_success, Config) -> [{offline_storage, offline} | Config]; 163: save_offline_status(offline_failure, Config) -> [{offline_storage, offline_failure} | Config]; 164: save_offline_status(basic, Config) -> [{offline_storage, none} | Config]; 165: save_offline_status(_GN, Config) -> Config. 166: 167: end_per_group(GroupName, Config) -> 168: teardown_meck(GroupName), 169: case lists:member(GroupName, main_group_names()) of 170: true -> dynamic_modules:restore_modules(Config); 171: false -> ok 172: end. 173: 174: teardown_meck(mam_failure) -> 175: rpc(mim(), meck, unload, [mod_mam_rdbms_arch]); 176: teardown_meck(offline_failure) -> 177: rpc(mim(), meck, unload, [mod_offline_backend_module()]); 178: teardown_meck(suite) -> 179: rpc(mim(), meck, unload, []); 180: teardown_meck(_) -> ok. 181: 182: init_per_testcase(Name, C) -> escalus:init_per_testcase(Name, C). 183: end_per_testcase(Name, C) -> escalus:end_per_testcase(Name, C). 184: 185: %% Test cases 186: 187: initial_service_discovery_test(Config) -> 188: escalus:fresh_story( 189: Config, [{alice, 1}], 190: fun(Alice) -> 191: escalus_client:send(Alice, disco_info()), 192: Response = escalus_client:wait_for_stanza(Alice), 193: escalus:assert(has_feature, [ns_amp()], Response) 194: end). 195: 196: actions_and_conditions_discovery_test(Config) -> 197: escalus:fresh_story( 198: Config, [{alice, 1}], 199: fun(Alice) -> 200: Args = [ns_amp(), 201: <<"http://jabber.org/protocol/amp?action=notify">>, 202: <<"http://jabber.org/protocol/amp?action=error">>, 203: <<"http://jabber.org/protocol/amp?condition=deliver">>, 204: <<"http://jabber.org/protocol/amp?condition=match-resource">> 205: ], 206: escalus_client:send(Alice, disco_info_amp_node()), 207: Response = escalus_client:wait_for_stanza(Alice), 208: assert_has_features(Response, Args) 209: end). 210: 211: 212: unsupported_actions_test(Config) -> 213: escalus:fresh_story( 214: Config, [{alice, 1}, {bob, 1}], 215: fun(Alice, Bob) -> 216: %% given 217: Msg = amp_message_to(Bob, [{deliver, direct, alert}], % alert is unsupported 218: <<"A paradoxical payload!">>), 219: %% when 220: client_sends_message(Alice, Msg), 221: 222: % then 223: client_receives_amp_error(Alice, {deliver, direct, alert}, <<"unsupported-actions">>) 224: end). 225: 226: unsupported_conditions_test(Config) -> 227: escalus:fresh_story( 228: Config, [{alice, 1}, {bob, 1}], 229: fun(Alice, Bob) -> 230: %% given 231: %% expire-at is unsupported 232: Msg = amp_message_to(Bob, [{'expire-at', <<"2020-06-06T12:20:20Z">>, notify}], 233: <<"Never fade away!">>), 234: %% when 235: client_sends_message(Alice, Msg), 236: 237: % then 238: client_receives_amp_error(Alice, {'expire-at', <<"2020-06-06T12:20:20Z">>, notify}, 239: <<"unsupported-conditions">>) 240: end). 241: 242: unacceptable_rules_test(Config) -> 243: escalus:fresh_story( 244: Config, [{alice, 1}, {bob, 1}], 245: fun(Alice, Bob) -> 246: %% given 247: Msg = amp_message_to(Bob, [{broken, rule, spec} 248: , {also_broken, rule, spec} 249: ], 250: <<"Break all the rules!">>), 251: %% when 252: client_sends_message(Alice, Msg), 253: 254: % then 255: client_receives_amp_error(Alice, [{broken, rule, spec} 256: , {also_broken, rule, spec}], 257: <<"not-acceptable">>) 258: end). 259: 260: notify_deliver_to_online_user_test(Config) -> 261: escalus:fresh_story( 262: Config, [{alice, 1}, {bob, 1}], 263: fun(Alice, Bob) -> 264: %% given 265: Rule = {deliver, direct, notify}, 266: Rules = rules(Config, [Rule]), 267: Msg = amp_message_to(Bob, Rules, <<"I want to be sure you get this!">>), 268: 269: %% when 270: client_sends_message(Alice, Msg), 271: 272: % then 273: case lists:member(Rule, Rules) of 274: true -> client_receives_notification(Alice, Bob, Rule); 275: false -> ok 276: end, 277: client_receives_message(Bob, <<"I want to be sure you get this!">>), 278: client_receives_nothing(Alice) 279: end). 280: 281: 282: 283: notify_deliver_to_online_user_bare_jid_test(Config) -> 284: escalus:fresh_story( 285: Config, [{alice, 1}, {bob, 1}], 286: fun(Alice, Bob) -> 287: %% given 288: Message = <<"One of your resources needs to get this!">>, 289: Rule = {deliver, direct, notify}, 290: Rules = rules(Config, [Rule]), 291: BobsBareJid = escalus_client:short_jid(Bob), 292: Msg = amp_message_to(BobsBareJid, Rules, Message), 293: %% when 294: client_sends_message(Alice, Msg), 295: % then 296: case lists:member(Rule, Rules) of 297: true -> client_receives_notification(Alice, BobsBareJid, Rule); 298: false -> ok 299: end, 300: client_receives_message(Bob, Message), 301: client_receives_nothing(Alice) 302: end). 303: 304: notify_deliver_to_online_user_recipient_privacy_test(Config) -> 305: case is_module_loaded(mod_mam_pm) of 306: true -> {skip, "MAM does not support privacy lists"}; 307: false -> do_notify_deliver_to_online_user_recipient_privacy_test(Config) 308: end. 309: 310: do_notify_deliver_to_online_user_recipient_privacy_test(Config) -> 311: escalus:fresh_story( 312: Config, [{alice, 1}, {bob, 1}], 313: fun(Alice, Bob) -> 314: %% given 315: Rule = {deliver, none, notify}, 316: Rules = rules(Config, [Rule]), 317: Msg = amp_message_to(Bob, Rules, <<"Should be filtered by Bob's privacy list">>), 318: privacy_helper:set_and_activate(Bob, <<"deny_all_message">>), 319: 320: %% when 321: client_sends_message(Alice, Msg), 322: 323: % then 324: case lists:member(Rule, Rules) of 325: true -> client_receives_notification(Alice, Bob, Rule); 326: false -> ok 327: end, 328: client_receives_generic_error(Alice, <<"503">>, <<"cancel">>), 329: client_receives_nothing(Alice), 330: client_receives_nothing(Bob) 331: end). 332: 333: notify_deliver_to_online_user_broken_connection_test(Config) -> 334: escalus:fresh_story( 335: Config, [{alice, 1}, {bob, 1}], 336: fun(Alice, Bob) -> 337: %% given 338: Rule = {deliver, case ?config(offline_storage, Config) of 339: mam -> stored; 340: _ -> none 341: end, notify}, 342: Rules = rules(Config, [Rule]), 343: %% This special message is matched by the 344: %% amp_test_helper:ranch_tcp_send/2 mock, 345: %% (see amp_test_helper_code/0) 346: Msg = amp_message_to(Bob, Rules, <<"Recipient connection breaks">>), 347: 348: %% when 349: client_sends_message(Alice, Msg), 350: 351: % then 352: case lists:member(Rule, Rules) of 353: true -> client_receives_notification(Alice, Bob, Rule); 354: false -> ok 355: end, 356: client_receives_nothing(Alice), 357: 358: %% Kill Bob's connection to avoid errors with closing the stream 359: %% while the session is being resumed after the simulated error 360: escalus_connection:kill(Bob) 361: end), 362: ok. 363: 364: notify_deliver_to_offline_user_test(Config) -> 365: FreshConfig = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 366: escalus:story( 367: FreshConfig, [{alice, 1}], 368: fun(Alice) -> 369: %% given 370: Rule = {deliver, case is_offline_storage_working(Config) of 371: true -> stored; 372: false -> none 373: end, notify}, 374: Rules = rules(Config, [Rule]), 375: BobJid = escalus_users:get_jid(FreshConfig, bob), 376: Msg = amp_message_to(BobJid, Rules, <<"A message in a bottle...">>), 377: 378: %% when 379: client_sends_message(Alice, Msg), 380: 381: % then 382: case lists:member(Rule, Rules) of 383: true -> client_receives_notification(Alice, BobJid, Rule); 384: false -> ok 385: end, 386: case ?config(offline_storage, Config) of 387: offline_failure -> client_receives_generic_error(Alice, <<"500">>, <<"wait">>); 388: _ -> client_receives_nothing(Alice) 389: end 390: end), 391: wait_until_no_session(FreshConfig, alice), 392: case is_offline_storage_working(Config) of 393: true -> user_has_incoming_offline_message(FreshConfig, bob, <<"A message in a bottle...">>); 394: false -> user_has_no_incoming_offline_messages(FreshConfig, bob) 395: end. 396: 397: is_offline_storage_working(Config) -> 398: Status = ?config(offline_storage, Config), 399: Status == mam orelse Status == offline. 400: 401: notify_deliver_to_offline_user_recipient_privacy_test(Config) -> 402: case is_module_loaded(mod_mam_pm) of 403: true -> {skip, "MAM does not support privacy lists"}; 404: false -> do_notify_deliver_to_offline_user_recipient_privacy_test(Config) 405: end. 406: 407: do_notify_deliver_to_offline_user_recipient_privacy_test(Config) -> 408: FreshConfig = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 409: escalus:story( 410: FreshConfig, [{alice, 1}, {bob, 1}], 411: fun(Alice, Bob) -> 412: 413: privacy_helper:set_and_activate(Bob, <<"deny_all_message">>), 414: privacy_helper:set_default_list(Bob, <<"deny_all_message">>), 415: mongoose_helper:logout_user(Config, Bob), 416: %% given 417: Rule = {deliver, none, notify}, 418: Rules = rules(Config, [Rule]), 419: BobJid = lbin(escalus_client:short_jid(Bob)), 420: 421: Msg = amp_message_to(BobJid, Rules, <<"A message in a bottle...">>), 422: 423: %% when 424: client_sends_message(Alice, Msg), 425: 426: % then 427: case lists:member(Rule, Rules) of 428: true -> client_receives_notification(Alice, BobJid, Rule); 429: false -> ok 430: end, 431: client_receives_nothing(Alice) 432: end), 433: user_has_no_incoming_offline_messages(FreshConfig, bob). 434: 435: notify_deliver_to_stranger_test(Config) -> 436: escalus:fresh_story( 437: Config, [{alice, 1}], 438: fun(Alice) -> 439: %% given 440: Rule = {deliver, none, notify}, 441: Rules = rules(Config, [Rule]), 442: Domain = domain(), 443: StrangerJid = <<"stranger@", Domain/binary>>, 444: Msg = amp_message_to(StrangerJid, Rules, <<"A message in a bottle...">>), 445: 446: %% when 447: client_sends_message(Alice, Msg), 448: 449: % then 450: case lists:member(Rule, Rules) of 451: true -> client_receives_notification(Alice, StrangerJid, Rule); 452: false -> ok 453: end, 454: client_receives_generic_error(Alice, <<"503">>, <<"cancel">>), 455: client_receives_nothing(Alice) 456: end). 457: 458: notify_deliver_to_unknown_domain_test(Config) -> 459: escalus:fresh_story( 460: Config, [{alice, 1}], 461: fun(Alice) -> 462: %% given 463: StrangerJid = <<"stranger@unknown.domain">>, 464: Rule = {deliver, none, notify}, 465: Rules = rules(Config, [Rule]), 466: Msg = amp_message_to(StrangerJid, Rules, <<"Msg to unknown domain">>), 467: %% when 468: client_sends_message(Alice, Msg), 469: 470: % then 471: case lists:member(Rule, Rules) of 472: true -> client_receives_notification(Alice, StrangerJid, Rule); 473: false -> ok 474: end, 475: % error 404: 'remote server not found' is expected 476: client_receives_generic_error(Alice, <<"404">>, <<"cancel">>), 477: client_receives_nothing(Alice) 478: end). 479: 480: notify_match_resource_any_test(Config) -> 481: escalus:fresh_story( 482: Config, [{alice, 1}, {bob, 4}], 483: fun(Alice, Bob, _, _, _) -> 484: %% given 485: Msg = amp_message_to(Bob, [{'match-resource', any, notify}], 486: <<"Church-encoded hot-dogs">>), 487: %% when 488: client_sends_message(Alice, Msg), 489: 490: % then 491: client_receives_notification(Alice, Bob, {'match-resource', any, notify}), 492: client_receives_message(Bob, <<"Church-encoded hot-dogs">>) 493: end). 494: 495: notify_match_resource_exact_test(Config) -> 496: escalus:fresh_story( 497: Config, [{alice, 1}, {bob, 4}], 498: fun(Alice, _, _, Bob3, _) -> 499: %% given 500: Msg = amp_message_to(Bob3, [{'match-resource', exact, notify}], 501: <<"Resource three, your battery is on fire!">>), 502: %% when 503: client_sends_message(Alice, Msg), 504: 505: % then 506: client_receives_notification(Alice, Bob3, {'match-resource', exact, notify}), 507: client_receives_message(Bob3, <<"Resource three, your battery is on fire!">>) 508: end). 509: 510: notify_match_resource_other_test(Config) -> 511: escalus:fresh_story( 512: Config, [{alice, 1}, {bob, 1}], 513: fun(Alice, Bob) -> 514: %% given 515: NonmatchingJid = << (escalus_client:short_jid(Bob))/binary, 516: "/blahblahblah_resource" >>, 517: Msg = amp_message_to(NonmatchingJid, 518: [{'match-resource', other, notify}], 519: <<"A Bob by any other name!">>), 520: 521: %% when 522: client_sends_message(Alice, Msg), 523: 524: % then 525: client_receives_notification(Alice, NonmatchingJid, 526: {'match-resource', other, notify}), 527: client_receives_message(Bob, <<"A Bob by any other name!">>) 528: end). 529: 530: notify_match_resource_other_bare_test(Config) -> 531: escalus:fresh_story( 532: Config, [{alice, 1}, {bob, 1}], 533: fun(Alice, Bob) -> 534: %% given 535: BareJid = escalus_client:short_jid(Bob), 536: Msg = amp_message_to(BareJid, 537: [{'match-resource', other, notify}], 538: <<"A Bob by any other name!">>), 539: 540: %% when 541: client_sends_message(Alice, Msg), 542: 543: % then 544: client_receives_notification(Alice, BareJid, {'match-resource', other, notify}), 545: client_receives_message(Bob, <<"A Bob by any other name!">>) 546: end). 547: 548: error_deliver_to_online_user_test(Config) -> 549: escalus:fresh_story( 550: Config, [{alice, 1}, {bob, 1}], 551: fun(Alice, Bob) -> 552: %% given 553: Rule = {deliver, direct, error}, 554: Rules = rules(Config, [Rule]), 555: Msg = amp_message_to(Bob, Rules, <<"It might cause an error">>), 556: 557: %% when 558: client_sends_message(Alice, Msg), 559: 560: % then 561: case lists:member(Rule, Rules) of 562: true -> 563: client_receives_amp_error(Alice, Bob, Rule, <<"undefined-condition">>), 564: client_receives_nothing(Bob); 565: false -> 566: client_receives_message(Bob, <<"It might cause an error">>) 567: end, 568: client_receives_nothing(Alice) 569: end). 570: 571: error_deliver_to_offline_user_test(Config) -> 572: FreshConfig = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 573: Rule = {deliver, case ?config(offline_storage, Config) of 574: none -> none; 575: _ -> stored 576: end, error}, 577: Rules = rules(Config, [Rule]), 578: escalus:story( 579: FreshConfig, [{alice, 1}], 580: fun(Alice) -> 581: %% given 582: BobJid = escalus_users:get_jid(FreshConfig, bob), 583: Msg = amp_message_to(BobJid, Rules, <<"A message in a bottle...">>), 584: 585: %% when 586: client_sends_message(Alice, Msg), 587: 588: % then 589: case lists:member(Rule, Rules) of 590: true -> 591: client_receives_amp_error(Alice, BobJid, Rule, <<"undefined-condition">>); 592: false -> 593: check_offline_storage(Alice, Config) 594: end 595: end), 596: wait_until_no_session(FreshConfig, alice), 597: case is_offline_storage_working(Config) andalso not lists:member(Rule, Rules) of 598: true -> user_has_incoming_offline_message(FreshConfig, bob, <<"A message in a bottle...">>); 599: false -> user_has_no_incoming_offline_messages(FreshConfig, bob) 600: end. 601: 602: check_offline_storage(User, Config) -> 603: case ?config(offline_storage, Config) of 604: offline_failure -> 605: client_receives_generic_error(User, <<"500">>, <<"wait">>); 606: _ -> client_receives_nothing(User) 607: end. 608: 609: error_deliver_to_stranger_test(Config) -> 610: escalus:fresh_story( 611: Config, [{alice, 1}], 612: fun(Alice) -> 613: %% given 614: Rule = {deliver, none, error}, 615: Rules = rules(Config, [Rule]), 616: Domain = domain(), 617: StrangerJid = <<"stranger@", Domain/binary>>, 618: Msg = amp_message_to(StrangerJid, Rules, <<"This cannot possibly succeed">>), 619: 620: %% when 621: client_sends_message(Alice, Msg), 622: 623: % then 624: case lists:member(Rule, Rules) of 625: true -> client_receives_amp_error(Alice, StrangerJid, Rule, 626: <<"undefined-condition">>); 627: false -> client_receives_generic_error(Alice, <<"503">>, <<"cancel">>) 628: end, 629: client_receives_nothing(Alice) 630: 631: end). 632: 633: drop_deliver_to_online_user_test(Config) -> 634: escalus:fresh_story( 635: Config, [{alice, 1}, {bob, 1}], 636: fun(Alice, Bob) -> 637: %% given 638: Rule = {deliver, direct, drop}, 639: Rules = rules(Config, [Rule]), 640: Msg = amp_message_to(Bob, Rules, <<"It might get dropped">>), 641: 642: %% when 643: client_sends_message(Alice, Msg), 644: 645: % then 646: case lists:member(Rule, Rules) of 647: true -> client_receives_nothing(Bob); 648: false -> client_receives_message(Bob, <<"It might get dropped">>) 649: end, 650: client_receives_nothing(Alice) 651: end). 652: 653: drop_deliver_to_offline_user_test(Config) -> 654: FreshConfig = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 655: Rule = {deliver, case ?config(offline_storage, Config) of 656: none -> none; 657: _ -> stored 658: end, drop}, 659: Rules = rules(Config, [Rule]), 660: Message = <<"A message in a bottle...">>, 661: escalus:story( 662: FreshConfig, [{alice, 1}], 663: fun(Alice) -> 664: %% given 665: BobJid = escalus_users:get_jid(FreshConfig, bob), 666: Msg = amp_message_to(BobJid, Rules, Message), 667: 668: %% when 669: client_sends_message(Alice, Msg), 670: 671: % then 672: case lists:member(Rule, Rules) orelse 673: ?config(offline_storage, Config) /= offline_failure of 674: true -> client_receives_nothing(Alice); 675: false -> client_receives_generic_error(Alice, <<"500">>, <<"wait">>) 676: end 677: end), 678: wait_until_no_session(FreshConfig, alice), 679: case is_offline_storage_working(Config) andalso not lists:member(Rule, Rules) of 680: true -> user_has_incoming_offline_message(FreshConfig, bob, Message); 681: false -> user_has_no_incoming_offline_messages(FreshConfig, bob) 682: end. 683: 684: drop_deliver_to_stranger_test(Config) -> 685: escalus:fresh_story( 686: Config, [{alice, 1}], 687: fun(Alice) -> 688: %% given 689: Rule = {deliver, none, drop}, 690: Rules = rules(Config, [Rule]), 691: Domain = domain(), 692: StrangerJid = <<"stranger@", Domain/binary>>, 693: Msg = amp_message_to(StrangerJid, Rules, <<"This cannot possibly succeed">>), 694: 695: %% when 696: client_sends_message(Alice, Msg), 697: 698: % then 699: case lists:member(Rule, Rules) of 700: true -> ok; 701: false -> client_receives_generic_error(Alice, <<"503">>, <<"cancel">>) 702: end, 703: client_receives_nothing(Alice) 704: 705: end). 706: 707: last_rule_applies_test(Config) -> 708: escalus:fresh_story( 709: Config, [{alice, 1}, {bob, 1}], 710: fun(Alice, Bob) -> 711: %% given 712: BobsBareJid = escalus_client:short_jid(Bob), 713: Msg = amp_message_to(BobsBareJid, [{deliver, none, error}, 714: {deliver, stored, error}, 715: {deliver, direct, notify}], 716: <<"One of your resources needs to get this!">>), 717: %% when 718: client_sends_message(Alice, Msg), 719: 720: % then 721: client_receives_notification(Alice, BobsBareJid, {deliver, direct, notify}), 722: client_receives_message(Bob, <<"One of your resources needs to get this!">>) 723: end). 724: 725: %% Internal 726: 727: wait_until_no_session(FreshConfig, User) -> 728: U = escalus_users:get_username(FreshConfig, User), 729: S = escalus_users:get_server(FreshConfig, User), 730: JID = jid:make(U, S, <<>>), 731: mongoose_helper:wait_until( 732: fun() -> rpc(mim(), ejabberd_sm, get_user_resources, [JID]) end, []). 733: 734: user_has_no_incoming_offline_messages(FreshConfig, UserName) -> 735: escalus:fresh_story( 736: FreshConfig, [{UserName, 1}], 737: fun(User) -> 738: client_receives_nothing(User), 739: case is_module_loaded(mod_mam_pm) of 740: true -> client_has_no_mam_messages(User); 741: false -> ok 742: end 743: end). 744: 745: user_has_incoming_offline_message(FreshConfig, UserName, MsgText) -> 746: true = is_module_loaded(mod_mam_pm) orelse is_module_loaded(mod_offline), 747: {ok, Client} = escalus_client:start(FreshConfig, UserName, <<"new-session">>), 748: escalus:send(Client, escalus_stanza:presence(<<"available">>)), 749: case is_module_loaded(mod_offline) of 750: true -> client_receives_message(Client, MsgText); 751: false -> ok 752: end, 753: Presence = escalus:wait_for_stanza(Client), 754: escalus:assert(is_presence, Presence), 755: case is_module_loaded(mod_mam_pm) of 756: true -> client_has_mam_message(Client); 757: false -> ok 758: end, 759: escalus_client:stop(FreshConfig, Client). 760: 761: client_has_no_mam_messages(User) -> 762: P = mam_helper:mam04_props(), 763: escalus:send(User, mam_helper:stanza_archive_request(P, <<"q1">>)), 764: Res = mam_helper:wait_archive_respond(User), 765: mam_helper:assert_respond_size(0, Res). 766: 767: client_has_mam_message(User) -> 768: P = mam_helper:mam04_props(), 769: escalus:send(User, mam_helper:stanza_archive_request(P, <<"q1">>)), 770: Res = mam_helper:wait_archive_respond(User), 771: mam_helper:assert_respond_size(1, Res). 772: 773: rules(Config, Default) -> 774: case lists:keysearch(rules, 1, Config) of 775: {value, {rules, Val}} -> Val; 776: _ -> Default 777: end. 778: 779: ns_amp() -> 780: <<"http://jabber.org/protocol/amp">>. 781: 782: client_sends_message(Client, Msg) -> 783: escalus_client:send(Client, Msg). 784: 785: client_receives_amp_error(Client, Rules, AmpErrorKind) when is_list(Rules) -> 786: Received = escalus_client:wait_for_stanza(Client), 787: assert_amp_error(Client, Received, Rules, AmpErrorKind); 788: client_receives_amp_error(Client, Rule, AmpErrorKind) -> 789: client_receives_amp_error(Client, [Rule], AmpErrorKind). 790: 791: client_receives_amp_error(Client, IntendedRecipient, Rule, AmpErrorKind) -> 792: Received = escalus_client:wait_for_stanza(Client), 793: assert_amp_error_with_full_amp(Client, IntendedRecipient, 794: Received, Rule, AmpErrorKind). 795: 796: client_receives_generic_error(Client, Code, Type) -> 797: Received = escalus_client:wait_for_stanza(Client, 5000), 798: escalus:assert(fun contains_error/3, [Code, Type], Received). 799: 800: client_receives_nothing(Client) -> 801: timer:sleep(300), 802: escalus_assert:has_no_stanzas(Client). 803: 804: client_receives_message(Client, MsgText) -> 805: Received = escalus_client:wait_for_stanza(Client), 806: escalus:assert(is_chat_message, [MsgText], Received). 807: 808: client_receives_notification(Client, IntendedRecipient, Rule) -> 809: Msg = escalus_client:wait_for_stanza(Client), 810: assert_notification(Client, IntendedRecipient, Msg, Rule). 811: 812: disco_info() -> 813: escalus_stanza:disco_info(domain()). 814: 815: disco_info_amp_node() -> 816: escalus_stanza:disco_info(domain(), ns_amp()). 817: 818: assert_amp_error(Client, Response, Rules, AmpErrorKind) when is_list(Rules) -> 819: ClientJID = escalus_client:full_jid(Client), 820: Server = escalus_client:server(Client), 821: Server = exml_query:attr(Response, <<"from">>), 822: ClientJID = exml_query:attr(Response, <<"to">>), 823: escalus:assert(fun contains_amp/5, 824: [amp_status_attr(AmpErrorKind), no_to_attr, no_from_attr, Rules], 825: Response), 826: escalus:assert(fun contains_amp_error/3, 827: [AmpErrorKind, Rules], 828: Response); 829: 830: assert_amp_error(Client, Response, Rule, AmpErrorKind) -> 831: assert_amp_error(Client, Response, [Rule], AmpErrorKind). 832: 833: assert_amp_error_with_full_amp(Client, IntendedRecipient, Response, 834: {_C, _V, _A} = Rule, AmpErrorKind) -> 835: ClientJID = escalus_client:full_jid(Client), 836: RecipientJID = full_jid(IntendedRecipient), 837: Server = escalus_client:server(Client), 838: Server = exml_query:attr(Response, <<"from">>), 839: ClientJID = exml_query:attr(Response, <<"to">>), 840: escalus:assert(fun contains_amp/5, 841: [amp_status_attr(AmpErrorKind), RecipientJID, ClientJID, [Rule]], 842: Response), 843: escalus:assert(fun contains_amp_error/3, 844: [AmpErrorKind, [Rule]], 845: Response). 846: 847: 848: assert_notification(Client, IntendedRecipient, Response, {_C, _V, A} = Rule) -> 849: ClientJID = escalus_client:full_jid(Client), 850: RecipientJID = full_jid(IntendedRecipient), 851: Server = escalus_client:server(Client), 852: Action = a2b(A), 853: Server = exml_query:attr(Response, <<"from">>), 854: ClientJID = exml_query:attr(Response, <<"to">>), 855: escalus:assert(fun contains_amp/5, 856: [Action, RecipientJID, ClientJID, [Rule]], 857: Response). 858: 859: assert_has_features(Response, Features) -> 860: CheckF = fun(F) -> escalus:assert(has_feature, [F], Response) end, 861: lists:foreach(CheckF, Features). 862: 863: full_jid(#client{} = Client) -> 864: escalus_client:full_jid(Client); 865: full_jid(B) when is_binary(B) -> 866: B. 867: 868: %% @TODO: Move me out to escalus_stanza %%%%%%%%% 869: %%%%%%%%% Element constructors %%%%%%%%%%%%%%%%%% 870: amp_message_to(To, Rules, MsgText) -> 871: Msg0 = #xmlel{children=C} = escalus_stanza:chat_to(To, MsgText), 872: Msg = escalus_stanza:set_id(Msg0, escalus_stanza:id()), 873: Amp = amp_el(Rules), 874: Msg#xmlel{children = C ++ [Amp]}. 875: 876: amp_el([]) -> 877: throw("cannot build <amp> with no rules!"); 878: amp_el(Rules) -> 879: #xmlel{name = <<"amp">>, 880: attrs = [{<<"xmlns">>, ns_amp()}], 881: children = [ rule_el(R) || R <- Rules ]}. 882: 883: rule_el({Condition, Value, Action}) -> 884: check_rules(Condition, Value, Action), 885: #xmlel{name = <<"rule">> 886: , attrs = [{<<"condition">>, a2b(Condition)} 887: , {<<"value">>, a2b(Value)} 888: , {<<"action">>, a2b(Action)}]}. 889: 890: %% @TODO: Move me out to escalus_pred %%%%%%%%%%%% 891: %%%%%%%%% XML predicates %%%%% %%%%%%%%%%%%%%%%%%% 892: contains_amp(Status, To, From, ExpectedRules, Stanza) when is_list(ExpectedRules)-> 893: Amp = exml_query:subelement(Stanza, <<"amp">>), 894: undefined =/= Amp andalso 895: To == exml_query:attr(Amp, <<"to">>, no_to_attr) andalso 896: From == exml_query:attr(Amp, <<"from">>, no_from_attr) andalso 897: Status == exml_query:attr(Amp, <<"status">>, no_status_attr) andalso 898: all_present([ rule_el(R) || R <- ExpectedRules ], exml_query:subelements(Amp, <<"rule">>)). 899: 900: contains_amp_error(AmpErrorKind, Rules, Response) -> 901: ErrorEl = exml_query:subelement(Response, <<"error">>), 902: <<"modify">> == exml_query:attr(ErrorEl, <<"type">>) 903: andalso 904: amp_error_code(AmpErrorKind) == exml_query:attr(ErrorEl, <<"code">>) 905: andalso 906: undefined =/= (Marker = exml_query:subelement(ErrorEl, amp_error_marker(AmpErrorKind))) 907: andalso 908: ns_stanzas() == exml_query:attr(Marker, <<"xmlns">>) 909: andalso 910: undefined =/= (Container = exml_query:subelement(ErrorEl, 911: amp_error_container(AmpErrorKind))) 912: andalso 913: all_present([ rule_el(R) || R <- Rules ], exml_query:subelements(Container, <<"rule">>)). 914: 915: contains_error(Code, Type, Response) -> 916: ErrorEl = exml_query:subelement(Response, <<"error">>), 917: Type == exml_query:attr(ErrorEl, <<"type">>) 918: andalso (Code == any orelse Code == exml_query:attr(ErrorEl, <<"code">>)). 919: 920: all_present(Needles, Haystack) -> 921: list_and([ lists:member(Needle, Haystack) 922: || Needle <- Needles ]). 923: 924: 925: list_and(List) -> 926: lists:all(fun(X) -> X =:= true end, List). 927: 928: ns_stanzas() -> 929: <<"urn:ietf:params:xml:ns:xmpp-stanzas">>. 930: 931: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 932: 933: check_rules(deliver, direct, notify) -> ok; 934: check_rules(deliver, stored, notify) -> ok; 935: check_rules(deliver, none, notify) -> ok; 936: 937: check_rules(deliver, direct, error) -> ok; 938: check_rules(deliver, stored, error) -> ok; 939: check_rules(deliver, none, error) -> ok; 940: 941: check_rules(deliver, direct, drop) -> ok; 942: check_rules(deliver, stored, drop) -> ok; 943: check_rules(deliver, none, drop) -> ok; 944: 945: check_rules('match-resource', any, notify) -> ok; 946: check_rules('match-resource', exact, notify) -> ok; 947: check_rules('match-resource', other, notify) -> ok; 948: 949: check_rules(deliver, direct, alert) -> ok; %% for testing unsupported rules 950: check_rules('expire-at', _binary, notify) -> ok; %% for testing unsupported conditions 951: check_rules(broken, rule, spec) -> ok; %% for testing unacceptable rules 952: check_rules(also_broken, rule, spec) -> ok; %% for testing unacceptable rules 953: 954: check_rules(C, V, A) -> throw({illegal_amp_rule, {C, V, A}}). 955: 956: a2b(B) when is_binary(B) -> B; 957: a2b(A) -> atom_to_binary(A, utf8). 958: 959: %% Undefined-condition errors return a fully-fledged amp element with status=error 960: %% The other errors have 'thin' <amp>s with no status attribute 961: amp_status_attr(<<"undefined-condition">>) -> <<"error">>; 962: amp_status_attr(_) -> no_status_attr. 963: 964: amp_error_code(<<"undefined-condition">>) -> <<"500">>; 965: amp_error_code(<<"not-acceptable">>) -> <<"405">>; 966: amp_error_code(<<"unsupported-actions">>) -> <<"400">>; 967: amp_error_code(<<"unsupported-conditions">>) -> <<"400">>. 968: 969: amp_error_marker(<<"not-acceptable">>) -> <<"not-acceptable">>; 970: amp_error_marker(<<"unsupported-actions">>) -> <<"bad-request">>; 971: amp_error_marker(<<"unsupported-conditions">>) -> <<"bad-request">>; 972: amp_error_marker(<<"undefined-condition">>) -> <<"undefined-condition">>. 973: 974: amp_error_container(<<"not-acceptable">>) -> <<"invalid-rules">>; 975: amp_error_container(<<"unsupported-actions">>) -> <<"unsupported-actions">>; 976: amp_error_container(<<"unsupported-conditions">>) -> <<"unsupported-conditions">>; 977: amp_error_container(<<"undefined-condition">>) -> <<"failed-rules">>. 978: 979: is_module_loaded(Mod) -> 980: rpc(mim(), gen_mod, is_loaded, [host_type(), Mod]). 981: 982: required_modules(basic) -> 983: mam_modules(off) ++ offline_modules(off) ++ privacy_modules(on); 984: required_modules(mam) -> 985: mam_modules(on) ++ offline_modules(off) ++ privacy_modules(off); 986: required_modules(offline) -> 987: mam_modules(off) ++ offline_modules(on) ++ privacy_modules(on); 988: required_modules(_) -> 989: []. 990: 991: mam_modules(on) -> 992: [{mod_mam, mam_helper:config_opts(#{pm => #{}, async_writer => #{enabled => false}})}]; 993: mam_modules(off) -> 994: [{mod_mam, stopped}]. 995: 996: offline_modules(on) -> 997: Backend = mongoose_helper:mnesia_or_rdbms_backend(), 998: [{mod_offline, config_parser_helper:mod_config(mod_offline, 999: #{backend => Backend, 1000: access_max_user_messages => max_user_offline_messages})}]; 1001: offline_modules(off) -> 1002: [{mod_offline, stopped}, 1003: {mod_offline_stub, []}]. 1004: 1005: privacy_modules(on) -> 1006: Backend = mongoose_helper:mnesia_or_rdbms_backend(), 1007: [{mod_privacy, config_parser_helper:mod_config(mod_privacy, #{backend => Backend})}, 1008: {mod_blocking, config_parser_helper:mod_config(mod_blocking, #{backend => Backend})}]; 1009: privacy_modules(off) -> 1010: [{mod_privacy, stopped}, 1011: {mod_blocking, stopped}]. 1012: 1013: mod_offline_backend_module() -> 1014: Backend = mongoose_helper:mnesia_or_rdbms_backend(), 1015: case Backend of 1016: mnesia -> 1017: mod_offline_mnesia; 1018: rdbms -> 1019: mod_offline_rdbms 1020: end.