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(ejabberd_socket, send, fun ejabberd_socket_send/2).\n" 128: "ejabberd_socket_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, [ejabberd_socket, [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, {error, simulated}]); 156: setup_meck(offline_failure) -> 157: ok = rpc(mim(), meck, expect, [mod_offline_mnesia, 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_mnesia]); 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) 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 ejabberd_socket mock 344: %% (see amp_test_helper_code/0) 345: Msg = amp_message_to(Bob, Rules, <<"Recipient connection breaks">>), 346: 347: %% when 348: client_sends_message(Alice, Msg), 349: 350: % then 351: case lists:member(Rule, Rules) of 352: true -> client_receives_notification(Alice, Bob, Rule); 353: false -> ok 354: end, 355: client_receives_nothing(Alice), 356: 357: %% Kill Bob's connection to avoid errors with closing the stream 358: %% while the session is being resumed after the simulated error 359: escalus_connection:kill(Bob) 360: end), 361: ok. 362: 363: notify_deliver_to_offline_user_test(Config) -> 364: FreshConfig = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 365: escalus:story( 366: FreshConfig, [{alice, 1}], 367: fun(Alice) -> 368: %% given 369: Rule = {deliver, case is_offline_storage_working(Config) of 370: true -> stored; 371: false -> none 372: end, notify}, 373: Rules = rules(Config, [Rule]), 374: BobJid = escalus_users:get_jid(FreshConfig, bob), 375: Msg = amp_message_to(BobJid, Rules, <<"A message in a bottle...">>), 376: 377: %% when 378: client_sends_message(Alice, Msg), 379: 380: % then 381: case lists:member(Rule, Rules) of 382: true -> client_receives_notification(Alice, BobJid, Rule); 383: false -> ok 384: end, 385: case ?config(offline_storage, Config) of 386: offline_failure -> client_receives_generic_error(Alice, <<"500">>, <<"wait">>); 387: _ -> client_receives_nothing(Alice) 388: end 389: end), 390: wait_until_no_session(FreshConfig, alice), 391: case is_offline_storage_working(Config) of 392: true -> user_has_incoming_offline_message(FreshConfig, bob, <<"A message in a bottle...">>); 393: false -> user_has_no_incoming_offline_messages(FreshConfig, bob) 394: end. 395: 396: is_offline_storage_working(Config) -> 397: Status = ?config(offline_storage, Config), 398: Status == mam orelse Status == offline. 399: 400: notify_deliver_to_offline_user_recipient_privacy_test(Config) -> 401: case is_module_loaded(mod_mam) of 402: true -> {skip, "MAM does not support privacy lists"}; 403: false -> do_notify_deliver_to_offline_user_recipient_privacy_test(Config) 404: end. 405: 406: do_notify_deliver_to_offline_user_recipient_privacy_test(Config) -> 407: FreshConfig = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 408: escalus:story( 409: FreshConfig, [{alice, 1}, {bob, 1}], 410: fun(Alice, Bob) -> 411: 412: privacy_helper:set_and_activate(Bob, <<"deny_all_message">>), 413: privacy_helper:set_default_list(Bob, <<"deny_all_message">>), 414: mongoose_helper:logout_user(Config, Bob), 415: %% given 416: Rule = {deliver, none, notify}, 417: Rules = rules(Config, [Rule]), 418: BobJid = lbin(escalus_client:short_jid(Bob)), 419: 420: Msg = amp_message_to(BobJid, Rules, <<"A message in a bottle...">>), 421: 422: %% when 423: client_sends_message(Alice, Msg), 424: 425: % then 426: case lists:member(Rule, Rules) of 427: true -> client_receives_notification(Alice, BobJid, Rule); 428: false -> ok 429: end, 430: client_receives_nothing(Alice) 431: end), 432: user_has_no_incoming_offline_messages(FreshConfig, bob). 433: 434: notify_deliver_to_stranger_test(Config) -> 435: escalus:fresh_story( 436: Config, [{alice, 1}], 437: fun(Alice) -> 438: %% given 439: Rule = {deliver, none, notify}, 440: Rules = rules(Config, [Rule]), 441: Domain = domain(), 442: StrangerJid = <<"stranger@", Domain/binary>>, 443: Msg = amp_message_to(StrangerJid, Rules, <<"A message in a bottle...">>), 444: 445: %% when 446: client_sends_message(Alice, Msg), 447: 448: % then 449: case lists:member(Rule, Rules) of 450: true -> client_receives_notification(Alice, StrangerJid, Rule); 451: false -> ok 452: end, 453: client_receives_generic_error(Alice, <<"503">>, <<"cancel">>), 454: client_receives_nothing(Alice) 455: end). 456: 457: notify_deliver_to_unknown_domain_test(Config) -> 458: escalus:fresh_story( 459: Config, [{alice, 1}], 460: fun(Alice) -> 461: %% given 462: StrangerJid = <<"stranger@unknown.domain">>, 463: Rule = {deliver, none, notify}, 464: Rules = rules(Config, [Rule]), 465: Msg = amp_message_to(StrangerJid, Rules, <<"Msg to unknown domain">>), 466: %% when 467: client_sends_message(Alice, Msg), 468: 469: % then 470: case lists:member(Rule, Rules) of 471: true -> client_receives_notification(Alice, StrangerJid, Rule); 472: false -> ok 473: end, 474: % error 404: 'remote server not found' is expected 475: client_receives_generic_error(Alice, <<"404">>, <<"cancel">>), 476: client_receives_nothing(Alice) 477: end). 478: 479: notify_match_resource_any_test(Config) -> 480: escalus:fresh_story( 481: Config, [{alice, 1}, {bob, 4}], 482: fun(Alice, Bob, _, _, _) -> 483: %% given 484: Msg = amp_message_to(Bob, [{'match-resource', any, notify}], 485: <<"Church-encoded hot-dogs">>), 486: %% when 487: client_sends_message(Alice, Msg), 488: 489: % then 490: client_receives_notification(Alice, Bob, {'match-resource', any, notify}), 491: client_receives_message(Bob, <<"Church-encoded hot-dogs">>) 492: end). 493: 494: notify_match_resource_exact_test(Config) -> 495: escalus:fresh_story( 496: Config, [{alice, 1}, {bob, 4}], 497: fun(Alice, _, _, Bob3, _) -> 498: %% given 499: Msg = amp_message_to(Bob3, [{'match-resource', exact, notify}], 500: <<"Resource three, your battery is on fire!">>), 501: %% when 502: client_sends_message(Alice, Msg), 503: 504: % then 505: client_receives_notification(Alice, Bob3, {'match-resource', exact, notify}), 506: client_receives_message(Bob3, <<"Resource three, your battery is on fire!">>) 507: end). 508: 509: notify_match_resource_other_test(Config) -> 510: escalus:fresh_story( 511: Config, [{alice, 1}, {bob, 1}], 512: fun(Alice, Bob) -> 513: %% given 514: NonmatchingJid = << (escalus_client:short_jid(Bob))/binary, 515: "/blahblahblah_resource" >>, 516: Msg = amp_message_to(NonmatchingJid, 517: [{'match-resource', other, notify}], 518: <<"A Bob by any other name!">>), 519: 520: %% when 521: client_sends_message(Alice, Msg), 522: 523: % then 524: client_receives_notification(Alice, NonmatchingJid, 525: {'match-resource', other, notify}), 526: client_receives_message(Bob, <<"A Bob by any other name!">>) 527: end). 528: 529: notify_match_resource_other_bare_test(Config) -> 530: escalus:fresh_story( 531: Config, [{alice, 1}, {bob, 1}], 532: fun(Alice, Bob) -> 533: %% given 534: BareJid = escalus_client:short_jid(Bob), 535: Msg = amp_message_to(BareJid, 536: [{'match-resource', other, notify}], 537: <<"A Bob by any other name!">>), 538: 539: %% when 540: client_sends_message(Alice, Msg), 541: 542: % then 543: client_receives_notification(Alice, BareJid, {'match-resource', other, notify}), 544: client_receives_message(Bob, <<"A Bob by any other name!">>) 545: end). 546: 547: error_deliver_to_online_user_test(Config) -> 548: escalus:fresh_story( 549: Config, [{alice, 1}, {bob, 1}], 550: fun(Alice, Bob) -> 551: %% given 552: Rule = {deliver, direct, error}, 553: Rules = rules(Config, [Rule]), 554: Msg = amp_message_to(Bob, Rules, <<"It might cause an error">>), 555: 556: %% when 557: client_sends_message(Alice, Msg), 558: 559: % then 560: case lists:member(Rule, Rules) of 561: true -> 562: client_receives_amp_error(Alice, Bob, Rule, <<"undefined-condition">>), 563: client_receives_nothing(Bob); 564: false -> 565: client_receives_message(Bob, <<"It might cause an error">>) 566: end, 567: client_receives_nothing(Alice) 568: end). 569: 570: error_deliver_to_offline_user_test(Config) -> 571: FreshConfig = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 572: Rule = {deliver, case ?config(offline_storage, Config) of 573: none -> none; 574: _ -> stored 575: end, error}, 576: Rules = rules(Config, [Rule]), 577: escalus:story( 578: FreshConfig, [{alice, 1}], 579: fun(Alice) -> 580: %% given 581: BobJid = escalus_users:get_jid(FreshConfig, bob), 582: Msg = amp_message_to(BobJid, Rules, <<"A message in a bottle...">>), 583: 584: %% when 585: client_sends_message(Alice, Msg), 586: 587: % then 588: case lists:member(Rule, Rules) of 589: true -> 590: client_receives_amp_error(Alice, BobJid, Rule, <<"undefined-condition">>); 591: false -> 592: check_offline_storage(Alice, Config) 593: end 594: end), 595: wait_until_no_session(FreshConfig, alice), 596: case is_offline_storage_working(Config) andalso not lists:member(Rule, Rules) of 597: true -> user_has_incoming_offline_message(FreshConfig, bob, <<"A message in a bottle...">>); 598: false -> user_has_no_incoming_offline_messages(FreshConfig, bob) 599: end. 600: 601: check_offline_storage(User, Config) -> 602: case ?config(offline_storage, Config) of 603: offline_failure -> 604: client_receives_generic_error(User, <<"500">>, <<"wait">>); 605: _ -> client_receives_nothing(User) 606: end. 607: 608: error_deliver_to_stranger_test(Config) -> 609: escalus:fresh_story( 610: Config, [{alice, 1}], 611: fun(Alice) -> 612: %% given 613: Rule = {deliver, none, error}, 614: Rules = rules(Config, [Rule]), 615: Domain = domain(), 616: StrangerJid = <<"stranger@", Domain/binary>>, 617: Msg = amp_message_to(StrangerJid, Rules, <<"This cannot possibly succeed">>), 618: 619: %% when 620: client_sends_message(Alice, Msg), 621: 622: % then 623: case lists:member(Rule, Rules) of 624: true -> client_receives_amp_error(Alice, StrangerJid, Rule, 625: <<"undefined-condition">>); 626: false -> client_receives_generic_error(Alice, <<"503">>, <<"cancel">>) 627: end, 628: client_receives_nothing(Alice) 629: 630: end). 631: 632: drop_deliver_to_online_user_test(Config) -> 633: escalus:fresh_story( 634: Config, [{alice, 1}, {bob, 1}], 635: fun(Alice, Bob) -> 636: %% given 637: Rule = {deliver, direct, drop}, 638: Rules = rules(Config, [Rule]), 639: Msg = amp_message_to(Bob, Rules, <<"It might get dropped">>), 640: 641: %% when 642: client_sends_message(Alice, Msg), 643: 644: % then 645: case lists:member(Rule, Rules) of 646: true -> client_receives_nothing(Bob); 647: false -> client_receives_message(Bob, <<"It might get dropped">>) 648: end, 649: client_receives_nothing(Alice) 650: end). 651: 652: drop_deliver_to_offline_user_test(Config) -> 653: FreshConfig = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 654: Rule = {deliver, case ?config(offline_storage, Config) of 655: none -> none; 656: _ -> stored 657: end, drop}, 658: Rules = rules(Config, [Rule]), 659: Message = <<"A message in a bottle...">>, 660: escalus:story( 661: FreshConfig, [{alice, 1}], 662: fun(Alice) -> 663: %% given 664: BobJid = escalus_users:get_jid(FreshConfig, bob), 665: Msg = amp_message_to(BobJid, Rules, Message), 666: 667: %% when 668: client_sends_message(Alice, Msg), 669: 670: % then 671: case lists:member(Rule, Rules) orelse 672: ?config(offline_storage, Config) /= offline_failure of 673: true -> client_receives_nothing(Alice); 674: false -> client_receives_generic_error(Alice, <<"500">>, <<"wait">>) 675: end 676: end), 677: wait_until_no_session(FreshConfig, alice), 678: case is_offline_storage_working(Config) andalso not lists:member(Rule, Rules) of 679: true -> user_has_incoming_offline_message(FreshConfig, bob, Message); 680: false -> user_has_no_incoming_offline_messages(FreshConfig, bob) 681: end. 682: 683: drop_deliver_to_stranger_test(Config) -> 684: escalus:fresh_story( 685: Config, [{alice, 1}], 686: fun(Alice) -> 687: %% given 688: Rule = {deliver, none, drop}, 689: Rules = rules(Config, [Rule]), 690: Domain = domain(), 691: StrangerJid = <<"stranger@", Domain/binary>>, 692: Msg = amp_message_to(StrangerJid, Rules, <<"This cannot possibly succeed">>), 693: 694: %% when 695: client_sends_message(Alice, Msg), 696: 697: % then 698: case lists:member(Rule, Rules) of 699: true -> ok; 700: false -> client_receives_generic_error(Alice, <<"503">>, <<"cancel">>) 701: end, 702: client_receives_nothing(Alice) 703: 704: end). 705: 706: last_rule_applies_test(Config) -> 707: escalus:fresh_story( 708: Config, [{alice, 1}, {bob, 1}], 709: fun(Alice, Bob) -> 710: %% given 711: BobsBareJid = escalus_client:short_jid(Bob), 712: Msg = amp_message_to(BobsBareJid, [{deliver, none, error}, 713: {deliver, stored, error}, 714: {deliver, direct, notify}], 715: <<"One of your resources needs to get this!">>), 716: %% when 717: client_sends_message(Alice, Msg), 718: 719: % then 720: client_receives_notification(Alice, BobsBareJid, {deliver, direct, notify}), 721: client_receives_message(Bob, <<"One of your resources needs to get this!">>) 722: end). 723: 724: %% Internal 725: 726: wait_until_no_session(FreshConfig, User) -> 727: U = escalus_users:get_username(FreshConfig, User), 728: S = escalus_users:get_server(FreshConfig, User), 729: JID = jid:make(U, S, <<>>), 730: mongoose_helper:wait_until( 731: fun() -> rpc(mim(), ejabberd_sm, get_user_resources, [JID]) end, []). 732: 733: user_has_no_incoming_offline_messages(FreshConfig, UserName) -> 734: escalus:fresh_story( 735: FreshConfig, [{UserName, 1}], 736: fun(User) -> 737: client_receives_nothing(User), 738: case is_module_loaded(mod_mam) of 739: true -> client_has_no_mam_messages(User); 740: false -> ok 741: end 742: end). 743: 744: user_has_incoming_offline_message(FreshConfig, UserName, MsgText) -> 745: true = is_module_loaded(mod_mam) orelse is_module_loaded(mod_offline), 746: {ok, Client} = escalus_client:start(FreshConfig, UserName, <<"new-session">>), 747: escalus:send(Client, escalus_stanza:presence(<<"available">>)), 748: case is_module_loaded(mod_offline) of 749: true -> client_receives_message(Client, MsgText); 750: false -> ok 751: end, 752: Presence = escalus:wait_for_stanza(Client), 753: escalus:assert(is_presence, Presence), 754: case is_module_loaded(mod_mam) of 755: true -> client_has_mam_message(Client); 756: false -> ok 757: end, 758: escalus_client:stop(FreshConfig, Client). 759: 760: client_has_no_mam_messages(User) -> 761: P = mam_helper:mam04_props(), 762: escalus:send(User, mam_helper:stanza_archive_request(P, <<"q1">>)), 763: Res = mam_helper:wait_archive_respond(User), 764: mam_helper:assert_respond_size(0, Res). 765: 766: client_has_mam_message(User) -> 767: P = mam_helper:mam04_props(), 768: escalus:send(User, mam_helper:stanza_archive_request(P, <<"q1">>)), 769: Res = mam_helper:wait_archive_respond(User), 770: mam_helper:assert_respond_size(1, Res). 771: 772: rules(Config, Default) -> 773: case lists:keysearch(rules, 1, Config) of 774: {value, {rules, Val}} -> Val; 775: _ -> Default 776: end. 777: 778: ns_amp() -> 779: <<"http://jabber.org/protocol/amp">>. 780: 781: client_sends_message(Client, Msg) -> 782: escalus_client:send(Client, Msg). 783: 784: client_receives_amp_error(Client, Rules, AmpErrorKind) when is_list(Rules) -> 785: Received = escalus_client:wait_for_stanza(Client), 786: assert_amp_error(Client, Received, Rules, AmpErrorKind); 787: client_receives_amp_error(Client, Rule, AmpErrorKind) -> 788: client_receives_amp_error(Client, [Rule], AmpErrorKind). 789: 790: client_receives_amp_error(Client, IntendedRecipient, Rule, AmpErrorKind) -> 791: Received = escalus_client:wait_for_stanza(Client), 792: assert_amp_error_with_full_amp(Client, IntendedRecipient, 793: Received, Rule, AmpErrorKind). 794: 795: client_receives_generic_error(Client, Code, Type) -> 796: Received = escalus_client:wait_for_stanza(Client, 5000), 797: escalus:assert(fun contains_error/3, [Code, Type], Received). 798: 799: client_receives_nothing(Client) -> 800: timer:sleep(300), 801: escalus_assert:has_no_stanzas(Client). 802: 803: client_receives_message(Client, MsgText) -> 804: Received = escalus_client:wait_for_stanza(Client), 805: escalus:assert(is_chat_message, [MsgText], Received). 806: 807: client_receives_notification(Client, IntendedRecipient, Rule) -> 808: Msg = escalus_client:wait_for_stanza(Client), 809: assert_notification(Client, IntendedRecipient, Msg, Rule). 810: 811: disco_info() -> 812: escalus_stanza:disco_info(domain()). 813: 814: disco_info_amp_node() -> 815: escalus_stanza:disco_info(domain(), ns_amp()). 816: 817: assert_amp_error(Client, Response, Rules, AmpErrorKind) when is_list(Rules) -> 818: ClientJID = escalus_client:full_jid(Client), 819: Server = escalus_client:server(Client), 820: Server = exml_query:attr(Response, <<"from">>), 821: ClientJID = exml_query:attr(Response, <<"to">>), 822: escalus:assert(fun contains_amp/5, 823: [amp_status_attr(AmpErrorKind), no_to_attr, no_from_attr, Rules], 824: Response), 825: escalus:assert(fun contains_amp_error/3, 826: [AmpErrorKind, Rules], 827: Response); 828: 829: assert_amp_error(Client, Response, Rule, AmpErrorKind) -> 830: assert_amp_error(Client, Response, [Rule], AmpErrorKind). 831: 832: assert_amp_error_with_full_amp(Client, IntendedRecipient, Response, 833: {_C, _V, _A} = Rule, AmpErrorKind) -> 834: ClientJID = escalus_client:full_jid(Client), 835: RecipientJID = full_jid(IntendedRecipient), 836: Server = escalus_client:server(Client), 837: Server = exml_query:attr(Response, <<"from">>), 838: ClientJID = exml_query:attr(Response, <<"to">>), 839: escalus:assert(fun contains_amp/5, 840: [amp_status_attr(AmpErrorKind), RecipientJID, ClientJID, [Rule]], 841: Response), 842: escalus:assert(fun contains_amp_error/3, 843: [AmpErrorKind, [Rule]], 844: Response). 845: 846: 847: assert_notification(Client, IntendedRecipient, Response, {_C, _V, A} = Rule) -> 848: ClientJID = escalus_client:full_jid(Client), 849: RecipientJID = full_jid(IntendedRecipient), 850: Server = escalus_client:server(Client), 851: Action = a2b(A), 852: Server = exml_query:attr(Response, <<"from">>), 853: ClientJID = exml_query:attr(Response, <<"to">>), 854: escalus:assert(fun contains_amp/5, 855: [Action, RecipientJID, ClientJID, [Rule]], 856: Response). 857: 858: assert_has_features(Response, Features) -> 859: CheckF = fun(F) -> escalus:assert(has_feature, [F], Response) end, 860: lists:foreach(CheckF, Features). 861: 862: full_jid(#client{} = Client) -> 863: escalus_client:full_jid(Client); 864: full_jid(B) when is_binary(B) -> 865: B. 866: 867: %% @TODO: Move me out to escalus_stanza %%%%%%%%% 868: %%%%%%%%% Element constructors %%%%%%%%%%%%%%%%%% 869: amp_message_to(To, Rules, MsgText) -> 870: Msg0 = #xmlel{children=C} = escalus_stanza:chat_to(To, MsgText), 871: Msg = escalus_stanza:set_id(Msg0, escalus_stanza:id()), 872: Amp = amp_el(Rules), 873: Msg#xmlel{children = C ++ [Amp]}. 874: 875: amp_el([]) -> 876: throw("cannot build <amp> with no rules!"); 877: amp_el(Rules) -> 878: #xmlel{name = <<"amp">>, 879: attrs = [{<<"xmlns">>, ns_amp()}], 880: children = [ rule_el(R) || R <- Rules ]}. 881: 882: rule_el({Condition, Value, Action}) -> 883: check_rules(Condition, Value, Action), 884: #xmlel{name = <<"rule">> 885: , attrs = [{<<"condition">>, a2b(Condition)} 886: , {<<"value">>, a2b(Value)} 887: , {<<"action">>, a2b(Action)}]}. 888: 889: %% @TODO: Move me out to escalus_pred %%%%%%%%%%%% 890: %%%%%%%%% XML predicates %%%%% %%%%%%%%%%%%%%%%%%% 891: contains_amp(Status, To, From, ExpectedRules, Stanza) when is_list(ExpectedRules)-> 892: Amp = exml_query:subelement(Stanza, <<"amp">>), 893: undefined =/= Amp andalso 894: To == exml_query:attr(Amp, <<"to">>, no_to_attr) andalso 895: From == exml_query:attr(Amp, <<"from">>, no_from_attr) andalso 896: Status == exml_query:attr(Amp, <<"status">>, no_status_attr) andalso 897: all_present([ rule_el(R) || R <- ExpectedRules ], exml_query:subelements(Amp, <<"rule">>)). 898: 899: contains_amp_error(AmpErrorKind, Rules, Response) -> 900: ErrorEl = exml_query:subelement(Response, <<"error">>), 901: <<"modify">> == exml_query:attr(ErrorEl, <<"type">>) 902: andalso 903: amp_error_code(AmpErrorKind) == exml_query:attr(ErrorEl, <<"code">>) 904: andalso 905: undefined =/= (Marker = exml_query:subelement(ErrorEl, amp_error_marker(AmpErrorKind))) 906: andalso 907: ns_stanzas() == exml_query:attr(Marker, <<"xmlns">>) 908: andalso 909: undefined =/= (Container = exml_query:subelement(ErrorEl, 910: amp_error_container(AmpErrorKind))) 911: andalso 912: all_present([ rule_el(R) || R <- Rules ], exml_query:subelements(Container, <<"rule">>)). 913: 914: contains_error(Code, Type, Response) -> 915: ErrorEl = exml_query:subelement(Response, <<"error">>), 916: Type == exml_query:attr(ErrorEl, <<"type">>) 917: andalso (Code == any orelse Code == exml_query:attr(ErrorEl, <<"code">>)). 918: 919: all_present(Needles, Haystack) -> 920: list_and([ lists:member(Needle, Haystack) 921: || Needle <- Needles ]). 922: 923: 924: list_and(List) -> 925: lists:all(fun(X) -> X =:= true end, List). 926: 927: ns_stanzas() -> 928: <<"urn:ietf:params:xml:ns:xmpp-stanzas">>. 929: 930: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 931: 932: check_rules(deliver, direct, notify) -> ok; 933: check_rules(deliver, stored, notify) -> ok; 934: check_rules(deliver, none, notify) -> ok; 935: 936: check_rules(deliver, direct, error) -> ok; 937: check_rules(deliver, stored, error) -> ok; 938: check_rules(deliver, none, error) -> ok; 939: 940: check_rules(deliver, direct, drop) -> ok; 941: check_rules(deliver, stored, drop) -> ok; 942: check_rules(deliver, none, drop) -> ok; 943: 944: check_rules('match-resource', any, notify) -> ok; 945: check_rules('match-resource', exact, notify) -> ok; 946: check_rules('match-resource', other, notify) -> ok; 947: 948: check_rules(deliver, direct, alert) -> ok; %% for testing unsupported rules 949: check_rules('expire-at', _binary, notify) -> ok; %% for testing unsupported conditions 950: check_rules(broken, rule, spec) -> ok; %% for testing unacceptable rules 951: check_rules(also_broken, rule, spec) -> ok; %% for testing unacceptable rules 952: 953: check_rules(C, V, A) -> throw({illegal_amp_rule, {C, V, A}}). 954: 955: a2b(B) when is_binary(B) -> B; 956: a2b(A) -> atom_to_binary(A, utf8). 957: 958: %% Undefined-condition errors return a fully-fledged amp element with status=error 959: %% The other errors have 'thin' <amp>s with no status attribute 960: amp_status_attr(<<"undefined-condition">>) -> <<"error">>; 961: amp_status_attr(_) -> no_status_attr. 962: 963: amp_error_code(<<"undefined-condition">>) -> <<"500">>; 964: amp_error_code(<<"not-acceptable">>) -> <<"405">>; 965: amp_error_code(<<"unsupported-actions">>) -> <<"400">>; 966: amp_error_code(<<"unsupported-conditions">>) -> <<"400">>. 967: 968: amp_error_marker(<<"not-acceptable">>) -> <<"not-acceptable">>; 969: amp_error_marker(<<"unsupported-actions">>) -> <<"bad-request">>; 970: amp_error_marker(<<"unsupported-conditions">>) -> <<"bad-request">>; 971: amp_error_marker(<<"undefined-condition">>) -> <<"undefined-condition">>. 972: 973: amp_error_container(<<"not-acceptable">>) -> <<"invalid-rules">>; 974: amp_error_container(<<"unsupported-actions">>) -> <<"unsupported-actions">>; 975: amp_error_container(<<"unsupported-conditions">>) -> <<"unsupported-conditions">>; 976: amp_error_container(<<"undefined-condition">>) -> <<"failed-rules">>. 977: 978: is_module_loaded(Mod) -> 979: rpc(mim(), gen_mod, is_loaded, [host_type(), Mod]). 980: 981: required_modules(basic) -> 982: mam_modules(off) ++ offline_modules(off) ++ privacy_modules(on); 983: required_modules(mam) -> 984: mam_modules(on) ++ offline_modules(off) ++ privacy_modules(off); 985: required_modules(offline) -> 986: mam_modules(off) ++ offline_modules(on) ++ privacy_modules(on); 987: required_modules(_) -> 988: []. 989: 990: mam_modules(on) -> 991: [{mod_mam_rdbms_user, [pm]}, 992: {mod_mam_rdbms_prefs, [pm]}, 993: {mod_mam_rdbms_arch, []}, 994: {mod_mam, []}]; 995: mam_modules(off) -> 996: [{mod_mam, stopped}]. 997: 998: offline_modules(on) -> 999: [{mod_offline, [{access_max_user_messages, max_user_offline_messages}]}]; 1000: offline_modules(off) -> 1001: [{mod_offline, stopped}, 1002: {mod_offline_stub, []}]. 1003: 1004: privacy_modules(on) -> 1005: [{mod_privacy, []}, 1006: {mod_blocking, []}]; 1007: privacy_modules(off) -> 1008: [{mod_privacy, stopped}, 1009: {mod_blocking, stopped}].