1: %%============================================================================== 2: %% Copyright 2012 Erlang Solutions Ltd. 3: %% 4: %% Licensed under the Apache License, Version 2.0 (the "License"); 5: %% you may not use this file except in compliance with the License. 6: %% You may obtain a copy of the License at 7: %% 8: %% http://www.apache.org/licenses/LICENSE-2.0 9: %% 10: %% Unless required by applicable law or agreed to in writing, software 11: %% distributed under the License is distributed on an "AS IS" BASIS, 12: %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13: %% See the License for the specific language governing permissions and 14: %% limitations under the License. 15: %%============================================================================== 16: -module(mam_SUITE). 17: 18: %% CT callbacks 19: -export([all/0, 20: groups/0, 21: suite/0, 22: init_per_suite/1, 23: end_per_suite/1, 24: init_per_group/2, 25: end_per_group/2, 26: init_per_testcase/2, 27: end_per_testcase/2]). 28: 29: %% Tests 30: -export([no_elements/1, 31: only_stanzaid/1, 32: same_stanza_id/1, 33: muc_no_elements/1, 34: muc_only_stanzaid/1, 35: mam_service_discovery/1, 36: muc_service_discovery/1, 37: simple_archive_request/1, 38: simple_archive_request_for_the_receiver/1, 39: text_search_query_fails_if_disabled/1, 40: text_search_is_not_available/1, 41: simple_text_search_request/1, 42: long_text_search_request/1, 43: text_search_is_available/1, 44: muc_message_with_stanzaid/1, 45: retract_muc_message/1, 46: retract_muc_message_on_stanza_id/1, 47: retract_wrong_muc_message/1, 48: muc_text_search_request/1, 49: muc_archive_request/1, 50: muc_multiple_devices/1, 51: muc_protected_message/1, 52: muc_deny_protected_room_access/1, 53: muc_allow_access_to_owner/1, 54: muc_delete_x_user_in_anon_rooms/1, 55: muc_show_x_user_to_moderators_in_anon_rooms/1, 56: muc_show_x_user_for_your_own_messages_in_anon_rooms/1, 57: range_archive_request/1, 58: range_archive_request_not_empty/1, 59: limit_archive_request/1, 60: prefs_set_request/1, 61: retrieve_form_fields/1, 62: prefs_set_cdata_request/1, 63: query_get_request/1, 64: pagination_first5/1, 65: pagination_last5/1, 66: pagination_offset5/1, 67: pagination_first0/1, 68: pagination_last0/1, 69: pagination_offset5_max0/1, 70: pagination_before10/1, 71: pagination_after10/1, 72: pagination_simple_before10/1, 73: pagination_last_after_id5/1, 74: pagination_last_after_id5_before_id11/1, 75: pagination_empty_rset/1, 76: pagination_first5_opt_count/1, 77: pagination_first25_opt_count_all/1, 78: pagination_last5_opt_count/1, 79: pagination_last25_opt_count_all/1, 80: pagination_offset5_opt_count/1, 81: pagination_offset5_opt_count_all/1, 82: server_returns_item_not_found_for_before_filter_with_nonexistent_id/1, 83: server_returns_item_not_found_for_after_filter_with_nonexistent_id/1, 84: server_returns_item_not_found_for_after_filter_with_invalid_id/1, 85: %% complete_flag_cases tests 86: before_complete_false_last5/1, 87: before_complete_false_before10/1, 88: before_complete_true_before1/1, 89: before_complete_true_before5/1, 90: before_complete_true_before6/1, 91: after_complete_false_first_page/1, 92: after_complete_false_after2/1, 93: after_complete_false_after9/1, 94: after_complete_true_after10/1, 95: after_complete_true_after11/1, 96: archived/1, 97: message_with_stanzaid/1, 98: retract_message/1, 99: retract_message_on_stanza_id/1, 100: retract_wrong_message/1, 101: ignore_bad_retraction/1, 102: filter_forwarded/1, 103: offline_message/1, 104: nostore_hint/1, 105: querying_for_all_messages_with_jid/1, 106: muc_querying_for_all_messages/1, 107: muc_querying_for_all_messages_with_jid/1, 108: muc_light_simple/1, 109: muc_light_shouldnt_modify_pm_archive/1, 110: muc_light_stored_in_pm_if_allowed_to/1, 111: muc_light_chat_markers_are_archived_if_enabled/1, 112: muc_light_chat_markers_are_not_archived_if_disabled/1, 113: messages_filtered_when_prefs_default_policy_is_always/1, 114: messages_filtered_when_prefs_default_policy_is_never/1, 115: messages_filtered_when_prefs_default_policy_is_roster/1, 116: run_set_and_get_prefs_cases/1, 117: check_user_exist/1, 118: metric_incremented_on_archive_request/1, 119: metric_incremented_when_store_message/1, 120: metrics_incremented_for_async_pools/1, 121: archive_chat_markers/1, 122: dont_archive_chat_markers/1, 123: save_unicode_messages/1, 124: unicode_messages_can_be_extracted/1, 125: stanza_id_is_appended_to_carbons/1]). 126: 127: -import(distributed_helper, [mim/0, 128: require_rpc_nodes/1, 129: subhost_pattern/1, 130: rpc/4]). 131: 132: -import(muc_helper, 133: [muc_host/0, 134: room_address/1, room_address/2, 135: stanza_muc_enter_room/2, 136: stanza_to_room/2]). 137: 138: -import(mam_helper, 139: [rpc_apply/3, 140: get_prop/2, 141: is_riak_enabled/1, 142: is_cassandra_enabled/1, 143: is_elasticsearch_enabled/1, 144: is_mam_possible/1, 145: respond_iq/1, 146: print_configuration_not_supported/2, 147: start_alice_room/1, 148: destroy_room/1, 149: send_muc_rsm_messages/1, 150: send_rsm_messages/1, 151: clean_archives/1, 152: mam04_props/0, 153: mam06_props/0, 154: bootstrap_archive/1, 155: muc_bootstrap_archive/1, 156: start_alice_protected_room/1, 157: start_alice_anonymous_room/1, 158: maybe_wait_for_archive/1, 159: stanza_archive_request/2, 160: stanza_text_search_archive_request/3, 161: stanza_date_range_archive_request_not_empty/3, 162: wait_archive_respond/1, 163: wait_for_complete_archive_response/3, 164: assert_respond_size/2, 165: assert_respond_query_id/3, 166: parse_result_iq/1, 167: nick_to_jid/2, 168: stanza_filtered_by_jid_request/2, 169: nick/1, 170: respond_messages/1, 171: parse_forwarded_message/1, 172: login_send_presence/2, 173: assert_only_one_of_many_is_equal/2, 174: add_nostore_hint/1, 175: assert_not_stored/2, 176: has_x_user_element/1, 177: stanza_date_range_archive_request/1, 178: make_iso_time/1, 179: stanza_retrieve_form_fields/2, 180: stanza_limit_archive_request/1, 181: rsm_send/3, 182: stanza_page_archive_request/3, 183: wait_empty_rset/2, 184: wait_message_range/3, 185: message_id/2, 186: wait_message_range/5, 187: stanza_prefs_set_request/4, 188: stanza_prefs_get_request/1, 189: stanza_query_get_request/1, 190: parse_prefs_result_iq/1, 191: mam_ns_binary/0, 192: mam_ns_binary_v04/0, 193: mam_ns_binary_v06/0, 194: retract_ns/0, 195: retract_tombstone_ns/0, 196: make_alice_and_bob_friends/2, 197: run_prefs_case/6, 198: prefs_cases2/0, 199: default_policy/1, 200: get_all_messages/2, 201: parse_messages/1, 202: run_set_and_get_prefs_case/4, 203: muc_light_host/0, 204: host_type/0, 205: set_wait_for_parallel_writer/2, 206: config_opts/1 207: ]). 208: 209: -import(muc_light_helper, 210: [given_muc_light_room/3, 211: when_muc_light_message_is_sent/4, 212: then_muc_light_message_is_received_by/2, 213: when_muc_light_affiliations_are_set/3, 214: then_muc_light_affiliations_are_received_by/2, 215: when_archive_query_is_sent/3, 216: then_archive_response_is/3]). 217: 218: -import(domain_helper, [domain/0]). 219: 220: -import(config_parser_helper, [default_mod_config/1]). 221: 222: -include("mam_helper.hrl"). 223: -include_lib("common_test/include/ct.hrl"). 224: -include_lib("exml/include/exml_stream.hrl"). 225: 226: %%-------------------------------------------------------------------- 227: %% Suite configuration 228: %%-------------------------------------------------------------------- 229: 230: 231: 232: configurations() -> 233: case ct_helper:is_ct_running() of 234: true -> 235: configurations_for_running_ct(); 236: false -> 237: all_configurations() 238: end. 239: 240: %% Called by test-runner for autocompletion 241: all_configurations() -> 242: cassandra_configs(true) 243: ++ rdbms_configs(true) 244: ++ riak_configs(true) 245: ++ elasticsearch_configs(true). 246: 247: configurations_for_running_ct() -> 248: cassandra_configs(is_cassandra_enabled(host_type())) 249: ++ rdbms_configs(mongoose_helper:is_rdbms_enabled(host_type())) 250: ++ riak_configs(is_riak_enabled(host_type())) 251: ++ elasticsearch_configs(is_elasticsearch_enabled(host_type())). 252: 253: rdbms_configs(true) -> 254: [rdbms, 255: rdbms_simple, 256: rdbms_async_pool, 257: rdbms_mnesia, 258: rdbms_async_cache, 259: rdbms_cache, 260: rdbms_mnesia_cache 261: ]; 262: rdbms_configs(_) -> 263: []. 264: 265: riak_configs(true) -> 266: [riak_timed_yz_buckets]; 267: riak_configs(_) -> 268: []. 269: 270: cassandra_configs(true) -> 271: [cassandra]; 272: cassandra_configs(_) -> 273: []. 274: 275: elasticsearch_configs(true) -> 276: [elasticsearch]; 277: elasticsearch_configs(_) -> 278: []. 279: 280: basic_group_names() -> 281: [ 282: mam_all, 283: chat_markers, 284: muc_all, 285: muc_light, 286: prefs_cases, 287: impl_specific, 288: disabled_text_search, 289: disabled_retraction 290: ]. 291: 292: all() -> 293: Reasons = 294: case ct_helper:is_ct_running() of 295: true -> 296: case is_mam_possible(host_type()) of 297: false -> [require_rdbms]; 298: true -> [] 299: end; 300: false -> 301: [] 302: end, 303: case Reasons of 304: [] -> 305: tests(); 306: [_|_] -> 307: {skip, Reasons} 308: end. 309: 310: tests() -> 311: [{group, full_group(C, G)} 312: || C <- configurations(), G <- basic_group_names(), 313: not is_skipped(C, G)]. 314: 315: groups() -> 316: [{full_group(C, G), Props, Tests} 317: || C <- configurations(), {G, Props, Tests} <- basic_groups(), 318: not is_skipped(C, G)]. 319: 320: is_skipped(_, _) -> 321: false. 322: 323: basic_groups() -> 324: [ 325: {mam_all, [parallel], 326: [{mam_metrics, [], mam_metrics_cases()}, 327: {mam04, [parallel], mam_cases() ++ [retrieve_form_fields] ++ text_search_cases()}, 328: {mam06, [parallel], mam_cases() ++ stanzaid_cases() ++ retract_cases()}, 329: {nostore, [parallel], nostore_cases()}, 330: {archived, [parallel], archived_cases()}, 331: {configurable_archiveid, [], configurable_archiveid_cases()}, 332: {rsm_all, [], %% not parallel, because we want to limit concurrency 333: [ 334: %% Functions mod_mam_utils:make_fin_element_v03/5 and make_fin_element/5 335: %% are almost the same, so don't need to test all versions of 336: %% MAM protocol with complete_flag_cases. 337: %% 338: %% We need a separate group for complete_flag_cases, 339: %% because there should not be a lot of cases running 340: %% using parallel_story with the same user. 341: %% Otherwise there would be a lot of presences sent between devices. 342: {rsm04, [parallel], rsm_cases()}, 343: {rsm04_comp, [parallel], complete_flag_cases()}, 344: {with_rsm04, [parallel], with_rsm_cases()}]}]}, 345: {muc_all, [parallel], 346: [{muc04, [parallel], muc_cases() ++ muc_text_search_cases()}, 347: {muc06, [parallel], muc_cases() ++ muc_stanzaid_cases() ++ muc_retract_cases()}, 348: {muc_configurable_archiveid, [], muc_configurable_archiveid_cases()}, 349: {muc_rsm_all, [parallel], 350: [{muc_rsm04, [parallel], muc_rsm_cases()}]}]}, 351: {muc_light, [], muc_light_cases()}, 352: {prefs_cases, [parallel], prefs_cases()}, 353: {impl_specific, [], impl_specific()}, 354: {disabled_text_search, [], 355: [{mam04, [], disabled_text_search_cases()}]}, 356: {chat_markers, [parallel], 357: [{mam04, [parallel], chat_markers_cases()}]}, 358: {disabled_retraction, [], 359: [{mam06, [parallel], disabled_retract_cases() ++ 360: [mam_service_discovery]}]}, 361: {muc_disabled_retraction, [], 362: [{muc06, [parallel], disabled_muc_retract_cases() ++ 363: [muc_service_discovery]}]} 364: ]. 365: 366: chat_markers_cases() -> 367: [archive_chat_markers, 368: dont_archive_chat_markers]. 369: 370: mam_metrics_cases() -> 371: [metric_incremented_on_archive_request, 372: metric_incremented_when_store_message]. 373: 374: mam_cases() -> 375: [mam_service_discovery, 376: simple_archive_request, 377: simple_archive_request_for_the_receiver, 378: range_archive_request, 379: range_archive_request_not_empty, 380: limit_archive_request, 381: querying_for_all_messages_with_jid, 382: unicode_messages_can_be_extracted 383: ]. 384: 385: text_search_cases() -> 386: [ 387: simple_text_search_request, 388: long_text_search_request, 389: text_search_is_available, 390: save_unicode_messages 391: ]. 392: 393: disabled_text_search_cases() -> 394: [ 395: text_search_is_not_available, 396: text_search_query_fails_if_disabled 397: ]. 398: 399: muc_text_search_cases() -> 400: [ 401: muc_text_search_request 402: ]. 403: 404: archived_cases() -> 405: [archived, 406: filter_forwarded, 407: metrics_incremented_for_async_pools 408: ]. 409: 410: stanzaid_cases() -> 411: [message_with_stanzaid, 412: stanza_id_is_appended_to_carbons]. 413: 414: retract_cases() -> 415: [retract_message, 416: retract_wrong_message, 417: ignore_bad_retraction]. 418: 419: disabled_retract_cases() -> 420: [retract_message]. 421: 422: nostore_cases() -> 423: [offline_message, 424: nostore_hint]. 425: 426: muc_cases() -> 427: [muc_service_discovery, 428: muc_archive_request, 429: muc_multiple_devices, 430: muc_protected_message, 431: muc_deny_protected_room_access, 432: muc_allow_access_to_owner, 433: muc_delete_x_user_in_anon_rooms, 434: muc_show_x_user_to_moderators_in_anon_rooms, 435: muc_show_x_user_for_your_own_messages_in_anon_rooms, 436: muc_querying_for_all_messages, 437: muc_querying_for_all_messages_with_jid 438: ]. 439: 440: muc_stanzaid_cases() -> 441: [muc_message_with_stanzaid]. 442: 443: muc_retract_cases() -> 444: [retract_muc_message, 445: retract_muc_message_on_stanza_id, 446: retract_wrong_muc_message]. 447: 448: disabled_muc_retract_cases() -> 449: [retract_muc_message]. 450: 451: muc_configurable_archiveid_cases() -> 452: [ 453: muc_no_elements, 454: muc_only_stanzaid 455: ]. 456: 457: configurable_archiveid_cases() -> 458: [no_elements, 459: only_stanzaid, 460: same_stanza_id, 461: retract_message_on_stanza_id 462: ]. 463: 464: muc_light_cases() -> 465: [ 466: muc_light_simple, 467: muc_light_shouldnt_modify_pm_archive, 468: muc_light_stored_in_pm_if_allowed_to, 469: muc_light_chat_markers_are_archived_if_enabled, 470: muc_light_chat_markers_are_not_archived_if_disabled 471: ]. 472: 473: muc_rsm_cases() -> 474: rsm_cases(). 475: 476: with_rsm_cases() -> 477: rsm_cases(). 478: 479: rsm_cases() -> 480: [pagination_first5, 481: pagination_last5, 482: pagination_offset5, 483: pagination_first0, 484: pagination_last0, 485: pagination_offset5_max0, 486: pagination_before10, 487: pagination_after10, 488: pagination_empty_rset, 489: %% Border cases 490: pagination_last_after_id5, 491: pagination_last_after_id5_before_id11, 492: %% Simple cases 493: pagination_simple_before10, 494: %% opt_count cases 495: pagination_first5_opt_count, 496: pagination_last5_opt_count, 497: pagination_offset5_opt_count, 498: %% opt_count cases with all messages on the page 499: pagination_first25_opt_count_all, 500: pagination_last25_opt_count_all, 501: pagination_offset5_opt_count_all, 502: %% item_not_found response for nonexistent message ID in before/after filters 503: server_returns_item_not_found_for_before_filter_with_nonexistent_id, 504: server_returns_item_not_found_for_after_filter_with_nonexistent_id, 505: server_returns_item_not_found_for_after_filter_with_invalid_id]. 506: 507: complete_flag_cases() -> 508: [before_complete_false_last5, 509: before_complete_false_before10, 510: before_complete_true_before1, 511: before_complete_true_before5, 512: before_complete_true_before6, 513: after_complete_false_first_page, 514: after_complete_false_after2, 515: after_complete_false_after9, 516: after_complete_true_after10, 517: after_complete_true_after11]. 518: 519: prefs_cases() -> 520: [prefs_set_request, 521: prefs_set_cdata_request, 522: query_get_request, 523: messages_filtered_when_prefs_default_policy_is_always, 524: messages_filtered_when_prefs_default_policy_is_never, 525: messages_filtered_when_prefs_default_policy_is_roster, 526: run_set_and_get_prefs_cases]. 527: 528: impl_specific() -> 529: [check_user_exist]. 530: 531: suite() -> 532: require_rpc_nodes([mim]) ++ escalus:suite(). 533: 534: init_per_suite(Config) -> 535: muc_helper:load_muc(), 536: mam_helper:prepare_for_suite( 537: increase_limits( 538: delete_users([{escalus_user_db, {module, escalus_ejabberd}} 539: | escalus:init_per_suite(Config)]))). 540: 541: end_per_suite(Config) -> 542: muc_helper:unload_muc(), 543: %% Next function creates a lot of sessions... 544: escalus_fresh:clean(), 545: %% and this function kicks them without waiting... 546: mongoose_helper:kick_everyone(), 547: %% so we don't have sessions anymore and other tests will not fail 548: mongoose_helper:restore_config(Config), 549: escalus:end_per_suite(Config). 550: 551: user_names() -> 552: [alice, bob, kate, carol]. 553: 554: create_users(Config) -> 555: escalus:create_users(Config, escalus:get_users(user_names())). 556: 557: delete_users(Config) -> 558: escalus:delete_users(Config, escalus:get_users(user_names())). 559: 560: increase_limits(Config) -> 561: Config1 = mongoose_helper:backup_and_set_config(Config, increased_limits()), 562: rpc_apply(shaper_srv, reset_all_shapers, [host_type()]), 563: Config1. 564: 565: increased_limits() -> 566: #{[shaper, mam_shaper] => #{max_rate => 10000}, 567: [shaper, normal] => #{max_rate => 10000000}, 568: [shaper, fast] => #{max_rate => 10000000}, 569: [{access, host_type()}, max_user_sessions] => [#{acl => all, value => 10000}]}. 570: 571: init_per_group(mam04, Config) -> 572: [{props, mam04_props()}|Config]; 573: init_per_group(mam06, Config) -> 574: [{props, mam06_props()}|Config]; 575: 576: init_per_group(rsm_all, Config) -> 577: Config1 = escalus_fresh:create_users(Config, [{N, 1} || N <- user_names()]), 578: send_rsm_messages(Config1); 579: init_per_group(rsm04, Config) -> 580: [{props, mam04_props()}|Config]; 581: init_per_group(rsm04_comp, Config) -> 582: [{props, mam04_props()}|Config]; 583: init_per_group(with_rsm04, Config) -> 584: [{props, mam04_props()}, {with_rsm, true}|Config]; 585: 586: init_per_group(nostore, Config) -> 587: Config; 588: init_per_group(archived, Config) -> 589: Config; 590: init_per_group(mam_metrics, Config) -> 591: Config; 592: init_per_group(muc04, Config) -> 593: [{props, mam04_props()}, {with_rsm, true}|Config]; 594: init_per_group(muc06, Config) -> 595: [{props, mam06_props()}, {with_rsm, true}|Config]; 596: 597: init_per_group(muc_configurable_archiveid, Config) -> 598: dynamic_modules:save_modules(host_type(), Config); 599: init_per_group(configurable_archiveid, Config) -> 600: dynamic_modules:save_modules(host_type(), Config); 601: 602: init_per_group(muc_rsm_all, Config) -> 603: Config1 = escalus_fresh:create_users(Config, [{N, 1} || N <- user_names()]), 604: Config2 = start_alice_room(Config1), 605: Config3 = send_muc_rsm_messages(Config2), 606: [{muc_rsm, true} | Config3]; 607: init_per_group(muc_rsm04, Config) -> 608: [{props, mam04_props()}|Config]; 609: 610: init_per_group(Group, ConfigIn) -> 611: C = configuration(Group), 612: B = basic_group(Group), 613: {ModulesToStart, Config0} = required_modules_for_group(C, B, ConfigIn), 614: ct:pal("Init per group ~p; configuration ~p; basic group ~p", [Group, C, B]), 615: Config01 = dynamic_modules:save_modules(host_type(), Config0), 616: dynamic_modules:ensure_modules(host_type(), ModulesToStart), 617: Config1 = do_init_per_group(C, Config01), 618: [{basic_group, B}, {configuration, C} | init_state(C, B, Config1)]. 619: 620: do_init_per_group(C, ConfigIn) -> 621: Config0 = create_users(ConfigIn), 622: case C of 623: riak_timed_yz_buckets -> 624: [{archive_wait, 2500} | Config0]; 625: cassandra -> 626: [{archive_wait, 1500} | Config0]; 627: elasticsearch -> 628: [{archive_wait, 2500} | Config0]; 629: _ -> 630: Config0 631: end. 632: 633: end_per_group(G, Config) when G == rsm_all; G == nostore; 634: G == mam04; G == rsm04; G == with_rsm04; G == muc04; G == muc_rsm04; G == rsm04_comp; 635: G == muc06; G == mam06; 636: G == archived; G == mam_metrics -> 637: Config; 638: end_per_group(muc_configurable_archiveid, Config) -> 639: dynamic_modules:restore_modules(Config), 640: Config; 641: end_per_group(configurable_archiveid, Config) -> 642: dynamic_modules:restore_modules(Config), 643: Config; 644: end_per_group(muc_rsm_all, Config) -> 645: destroy_room(Config); 646: end_per_group(Group, Config) -> 647: C = configuration(Group), 648: B = basic_group(Group), 649: Config0 = end_state(C, B, Config), 650: Config1 = dynamic_modules:restore_modules(Config0), 651: escalus_fresh:clean(), 652: delete_users(Config1). 653: 654: required_modules_for_group(C, muc_light, Config) -> 655: Extra = mam_opts_for_conf(C), 656: MUCHost = subhost_pattern(muc_light_helper:muc_host_pattern()), 657: Opts = config_opts(Extra#{pm => #{}, muc => #{host => MUCHost}}), 658: Config1 = maybe_set_wait(C, [muc, pm], [{mam_meta_opts, Opts} | Config]), 659: {[{mod_muc_light, default_mod_config(mod_muc_light)}, 660: {mod_mam_meta, Opts}], Config1}; 661: required_modules_for_group(C, BG, Config) when BG =:= muc_all; 662: BG =:= muc_disabled_retraction -> 663: Extra = maps:merge(mam_opts_for_conf(C), mam_opts_for_base_group(BG)), 664: MUCHost = subhost_pattern(muc_domain(Config)), 665: Opts = config_opts(Extra#{muc => #{host => MUCHost}}), 666: Config1 = maybe_set_wait(C, [muc], [{mam_meta_opts, Opts} | Config]), 667: {[{mod_mam_meta, Opts}], Config1}; 668: required_modules_for_group(C, BG, Config) -> 669: Extra = maps:merge(mam_opts_for_conf(C), mam_opts_for_base_group(BG)), 670: Opts = config_opts(Extra#{pm => #{}}), 671: Config1 = maybe_set_wait(C, [pm], [{mam_meta_opts, Opts} | Config]), 672: {[{mod_mam_meta, Opts}], Config1}. 673: 674: maybe_set_wait(C, Types, Config) when C =:= rdbms_async_pool; 675: C =:= rdbms_async_cache -> 676: [{wait_for_parallel_writer, Types} | Config]; 677: maybe_set_wait(_C, _, Config) -> 678: Config. 679: 680: mam_opts_for_conf(riak_timed_yz_buckets) -> 681: #{backend => riak, 682: user_prefs_store => mnesia}; 683: mam_opts_for_conf(elasticsearch) -> 684: #{backend => elasticsearch, 685: user_prefs_store => mnesia}; 686: mam_opts_for_conf(cassandra) -> 687: #{backend => cassandra, 688: user_prefs_store => cassandra}; 689: mam_opts_for_conf(rdbms_simple) -> 690: SimpleOpts = #{db_jid_format => mam_jid_rfc, 691: db_message_format => mam_message_xml}, 692: maps:merge(SimpleOpts, mam_opts_for_conf(rdbms)); 693: mam_opts_for_conf(rdbms) -> 694: #{user_prefs_store => rdbms, 695: async_writer => #{enabled => false}, 696: cache_users => false}; 697: mam_opts_for_conf(rdbms_async_pool) -> 698: #{user_prefs_store => rdbms, 699: async_writer => #{flush_interval => 1}, 700: cache_users => false}; 701: mam_opts_for_conf(rdbms_mnesia) -> 702: #{user_prefs_store => mnesia, 703: async_writer => #{enabled => false}, 704: cache_users => false}; 705: mam_opts_for_conf(rdbms_cache) -> 706: #{user_prefs_store => rdbms, 707: async_writer => #{enabled => false}}; 708: mam_opts_for_conf(rdbms_async_cache) -> 709: #{user_prefs_store => rdbms, 710: async_writer => #{flush_interval => 1}}; 711: mam_opts_for_conf(rdbms_mnesia_cache) -> 712: #{user_prefs_store => mnesia, 713: async_writer => #{enabled => false}}. 714: 715: muc_domain(Config) -> 716: proplists:get_value(muc_domain, Config, muc_helper:muc_host_pattern()). 717: 718: mam_opts_for_base_group(disabled_text_search) -> 719: #{full_text_search => false}; 720: mam_opts_for_base_group(BG) when BG =:= disabled_retraction; 721: BG =:= muc_disabled_retraction -> 722: #{message_retraction => false}; 723: mam_opts_for_base_group(chat_markers) -> 724: #{archive_chat_markers => true}; 725: mam_opts_for_base_group(_BG) -> 726: #{}. 727: 728: init_state(_, muc_all, Config) -> 729: Config; 730: init_state(C, muc_light, Config) -> 731: clean_archives(Config), 732: init_state(C, muc04, Config); 733: init_state(_C, prefs_cases, Config) -> 734: Config; 735: init_state(_, _, Config) -> 736: clean_archives(Config). 737: 738: end_state(C, muc_light, Config) -> 739: muc_light_helper:clear_db(host_type()), 740: end_state(C, generic, Config); 741: end_state(_, _, Config) -> 742: Config. 743: 744: init_per_testcase(C=metrics_incremented_for_async_pools, Config) -> 745: case ?config(configuration, Config) of 746: rdbms_async_pool -> 747: escalus:init_per_testcase(C, clean_archives(Config)); 748: _ -> 749: {skip, "Not an async-pool test"} 750: end; 751: init_per_testcase(C=metric_incremented_when_store_message, ConfigIn) -> 752: Config = case ?config(configuration, ConfigIn) of 753: rdbms_async_pool -> 754: MongooseMetrics = [ 755: {[global, data, rdbms, default], 756: [{recv_oct, '>'}, {send_oct, '>'}]} 757: ], 758: [{mongoose_metrics, MongooseMetrics} | ConfigIn]; 759: _ -> 760: ConfigIn 761: end, 762: escalus:init_per_testcase(C, clean_archives(Config)); 763: init_per_testcase(C=filter_forwarded, Config) -> 764: escalus:init_per_testcase(C, Config); 765: init_per_testcase(C=querying_for_all_messages_with_jid, Config) -> 766: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}, {carol, 1}]), 767: escalus:init_per_testcase(C, bootstrap_archive(Config1)); 768: init_per_testcase(C=archived, Config) -> 769: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 770: escalus:init_per_testcase(C, Config1); 771: init_per_testcase(C, Config) when C =:= retract_message; 772: C =:= retract_wrong_message; 773: C =:= ignore_bad_retraction -> 774: skip_if_retraction_not_supported(Config, fun() -> escalus:init_per_testcase(C, Config) end); 775: init_per_testcase(C=retract_message_on_stanza_id, Config) -> 776: Init = fun() -> 777: dynamic_modules:ensure_modules(host_type(), required_modules(C, Config)), 778: escalus:init_per_testcase(C, Config) 779: end, 780: skip_if_retraction_not_supported(Config, Init); 781: init_per_testcase(C=offline_message, Config) -> 782: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}, {carol, 1}]), 783: escalus:init_per_testcase(C, Config1); 784: init_per_testcase(C=nostore_hint, Config) -> 785: escalus:init_per_testcase(C, Config); 786: init_per_testcase(C=muc_querying_for_all_messages, Config) -> 787: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 788: escalus:init_per_testcase(C, 789: muc_bootstrap_archive(start_alice_room(Config1))); 790: init_per_testcase(C=muc_querying_for_all_messages_with_jid, Config) -> 791: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 792: escalus:init_per_testcase(C, 793: muc_bootstrap_archive(start_alice_room(Config1))); 794: init_per_testcase(C=muc_archive_request, Config) -> 795: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 796: Config2 = %% Check that metric is incremented on MUC flushed 797: case ?config(configuration, Config1) of 798: rdbms_async_pool -> 799: MongooseMetrics = [{['_', 'modMucMamFlushed'], changed}], 800: [{mongoose_metrics, MongooseMetrics} | Config1]; 801: _ -> 802: Config1 803: end, 804: escalus:init_per_testcase(C, start_alice_room(Config2)); 805: init_per_testcase(C=muc_no_elements, Config) -> 806: dynamic_modules:ensure_modules(host_type(), required_modules(C, Config)), 807: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 808: escalus:init_per_testcase(C, start_alice_room(Config1)); 809: init_per_testcase(C=muc_only_stanzaid, Config) -> 810: dynamic_modules:ensure_modules(host_type(), required_modules(C, Config)), 811: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 812: escalus:init_per_testcase(C, start_alice_room(Config1)); 813: init_per_testcase(C=no_elements, Config) -> 814: dynamic_modules:ensure_modules(host_type(), required_modules(C, Config)), 815: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 816: escalus:init_per_testcase(C, start_alice_room(Config1)); 817: init_per_testcase(C=only_stanzaid, Config) -> 818: dynamic_modules:ensure_modules(host_type(), required_modules(C, Config)), 819: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 820: escalus:init_per_testcase(C, start_alice_room(Config1)); 821: init_per_testcase(C=same_stanza_id, Config) -> 822: dynamic_modules:ensure_modules(host_type(), required_modules(C, Config)), 823: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 824: escalus:init_per_testcase(C, start_alice_room(Config1)); 825: init_per_testcase(C=muc_message_with_stanzaid, Config) -> 826: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 827: escalus:init_per_testcase(C, start_alice_room(Config1)); 828: init_per_testcase(C, Config) when C =:= retract_muc_message; 829: C =:= retract_muc_message_on_stanza_id; 830: C =:= retract_wrong_muc_message -> 831: Init = fun() -> 832: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 833: escalus:init_per_testcase(C, start_alice_room(Config1)) 834: end, 835: skip_if_retraction_not_supported(Config, Init); 836: init_per_testcase(C=muc_multiple_devices, Config) -> 837: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 838: escalus:init_per_testcase(C, start_alice_room(Config1)); 839: init_per_testcase(C=muc_protected_message, Config) -> 840: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 841: escalus:init_per_testcase(C, start_alice_room(Config1)); 842: init_per_testcase(C=muc_deny_protected_room_access, Config) -> 843: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 844: escalus:init_per_testcase(C, start_alice_protected_room(Config1)); 845: init_per_testcase(C=muc_allow_access_to_owner, Config) -> 846: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 847: escalus:init_per_testcase(C, start_alice_protected_room(Config1)); 848: init_per_testcase(C=muc_delete_x_user_in_anon_rooms, Config) -> 849: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 850: escalus:init_per_testcase(C, start_alice_anonymous_room(Config1)); 851: init_per_testcase(C=muc_show_x_user_to_moderators_in_anon_rooms, Config) -> 852: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 853: escalus:init_per_testcase(C, start_alice_anonymous_room(Config1)); 854: init_per_testcase(C=muc_show_x_user_for_your_own_messages_in_anon_rooms, Config) -> 855: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 856: escalus:init_per_testcase(C, start_alice_anonymous_room(Config1)); 857: init_per_testcase(C=range_archive_request_not_empty, Config) -> 858: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}, {carol, 1}]), 859: escalus:init_per_testcase(C, bootstrap_archive(Config1)); 860: init_per_testcase(C=limit_archive_request, Config) -> 861: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}, {carol, 1}]), 862: escalus:init_per_testcase(C, bootstrap_archive(Config1)); 863: init_per_testcase(C=prefs_set_request, Config) -> 864: skip_if_riak(C, Config); 865: init_per_testcase(C=prefs_set_cdata_request, Config) -> 866: skip_if_riak(C, Config); 867: init_per_testcase(C=simple_text_search_request, Config) -> 868: skip_if_cassandra(Config, fun() -> escalus:init_per_testcase(C, Config) end); 869: init_per_testcase(C=long_text_search_request, Config) -> 870: skip_if_cassandra(Config, fun() -> escalus:init_per_testcase(C, Config) end); 871: init_per_testcase(C=save_unicode_messages, Config) -> 872: skip_if_cassandra(Config, fun() -> escalus:init_per_testcase(C, Config) end); 873: init_per_testcase(C=muc_text_search_request, Config) -> 874: Init = 875: fun() -> 876: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 877: escalus:init_per_testcase(C, start_alice_room(Config1)) 878: end, 879: 880: skip_if_cassandra(Config, Init); 881: init_per_testcase(C = muc_light_stored_in_pm_if_allowed_to, Config) -> 882: dynamic_modules:ensure_modules(host_type(), required_modules(C, Config)), 883: clean_archives(Config), 884: escalus:init_per_testcase(C, Config); 885: init_per_testcase(C, Config) when C =:= muc_light_simple; 886: C =:= muc_light_shouldnt_modify_pm_archive; 887: C =:= muc_light_chat_markers_are_archived_if_enabled; 888: C =:= muc_light_chat_markers_are_not_archived_if_disabled-> 889: dynamic_modules:ensure_modules(host_type(), required_modules(C, Config)), 890: escalus:init_per_testcase(C, Config); 891: init_per_testcase(C=archive_chat_markers, Config) -> 892: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 893: escalus:init_per_testcase(C, Config1); 894: init_per_testcase(C=dont_archive_chat_markers, Config) -> 895: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 896: escalus:init_per_testcase(C, Config1); 897: init_per_testcase(CaseName, Config) -> 898: escalus:init_per_testcase(CaseName, Config). 899: 900: skip_if_retraction_not_supported(Config, Init) -> 901: case lists:member(?config(configuration, Config), rdbms_configs(true)) of 902: false -> 903: {skip, "message retraction not supported"}; 904: true -> 905: Init() 906: end. 907: 908: skip_if_riak(C, Config) -> 909: case ?config(configuration, Config) of 910: riak_timed_yz_buckets -> 911: {skip, "prefs not implemented for riak"}; 912: _ -> 913: escalus:init_per_testcase(C, Config) 914: end. 915: 916: skip_if_cassandra(Config, Init) -> 917: case ?config(configuration, Config) of 918: cassandra -> 919: {skip, "full text search is not implemented for cassandra backend"}; 920: _ -> 921: Init() 922: end. 923: 924: end_per_testcase(C=muc_text_search_request, Config) -> 925: destroy_room(Config), 926: escalus:end_per_testcase(C, Config); 927: end_per_testcase(C=muc_archive_request, Config) -> 928: destroy_room(Config), 929: escalus:end_per_testcase(C, Config); 930: end_per_testcase(C=muc_multiple_devices, Config) -> 931: destroy_room(Config), 932: escalus:end_per_testcase(C, Config); 933: end_per_testcase(C=muc_protected_message, Config) -> 934: destroy_room(Config), 935: escalus:end_per_testcase(C, Config); 936: end_per_testcase(C=muc_deny_protected_room_access, Config) -> 937: destroy_room(Config), 938: escalus:end_per_testcase(C, Config); 939: end_per_testcase(C=muc_allow_access_to_owner, Config) -> 940: destroy_room(Config), 941: escalus:end_per_testcase(C, Config); 942: end_per_testcase(C=muc_delete_x_user_in_anon_rooms, Config) -> 943: destroy_room(Config), 944: escalus:end_per_testcase(C, Config); 945: end_per_testcase(C=muc_show_x_user_to_moderators_in_anon_rooms, Config) -> 946: destroy_room(Config), 947: escalus:end_per_testcase(C, Config); 948: end_per_testcase(C=muc_show_x_user_for_your_own_messages_in_anon_rooms, Config) -> 949: destroy_room(Config), 950: escalus:end_per_testcase(C, Config); 951: end_per_testcase(C=muc_querying_for_all_messages, Config) -> 952: destroy_room(Config), 953: escalus:end_per_testcase(C, Config); 954: end_per_testcase(C=muc_querying_for_all_messages_with_jid, Config) -> 955: destroy_room(Config), 956: escalus:end_per_testcase(C, Config); 957: end_per_testcase(C=muc_message_with_stanzaid, Config) -> 958: destroy_room(Config), 959: escalus:end_per_testcase(C, Config); 960: end_per_testcase(C, Config) when C =:= retract_muc_message; 961: C =:= retract_muc_message_on_stanza_id; 962: C =:= retract_wrong_muc_message -> 963: destroy_room(Config), 964: escalus:end_per_testcase(C, Config); 965: end_per_testcase(C=muc_no_elements, Config) -> 966: destroy_room(Config), 967: escalus:end_per_testcase(C, Config); 968: end_per_testcase(C=muc_only_stanzaid, Config) -> 969: destroy_room(Config), 970: escalus:end_per_testcase(C, Config); 971: end_per_testcase(C = muc_light_stored_in_pm_if_allowed_to, Config) -> 972: escalus:end_per_testcase(C, Config); 973: end_per_testcase(C = retract_message_on_stanza_id, Config) -> 974: escalus:end_per_testcase(C, Config); 975: end_per_testcase(C = muc_light_chat_markers_are_archived_if_enabled, Config) -> 976: escalus:end_per_testcase(C, Config); 977: end_per_testcase(C = muc_light_chat_markers_are_not_archived_if_disabled, Config) -> 978: escalus:end_per_testcase(C, Config); 979: end_per_testcase(C = no_elements, Config) -> 980: escalus:end_per_testcase(C, Config); 981: end_per_testcase(C = only_stanzaid, Config) -> 982: escalus:end_per_testcase(C, Config); 983: end_per_testcase(C = same_stanza_id, Config) -> 984: escalus:end_per_testcase(C, Config); 985: end_per_testcase(CaseName, Config) -> 986: escalus:end_per_testcase(CaseName, Config). 987: 988: %% Module configuration per testcase 989: 990: required_modules(muc_light_stored_in_pm_if_allowed_to, Config) -> 991: Opts = #{pm := PM} = ?config(mam_meta_opts, Config), 992: NewOpts = Opts#{pm := PM#{archive_groupchats => true}}, 993: [{mod_mam_meta, NewOpts}]; 994: required_modules(muc_light_chat_markers_are_archived_if_enabled, Config) -> 995: Opts = #{muc := MUC} = ?config(mam_meta_opts, Config), 996: NewOpts = Opts#{muc := MUC#{archive_chat_markers => true}}, 997: [{mod_mam_meta, NewOpts}]; 998: required_modules(muc_no_elements, Config) -> 999: Opts = #{muc := MUC} = ?config(mam_meta_opts, Config), 1000: NewOpts = Opts#{muc := MUC#{no_stanzaid_element => true}}, 1001: [{mod_mam_meta, NewOpts}]; 1002: required_modules(muc_only_stanzaid, Config) -> 1003: Opts = ?config(mam_meta_opts, Config), 1004: [{mod_mam_meta, Opts}]; 1005: % configurable_archiveid basic group 1006: required_modules(no_elements, Config) -> 1007: Opts = #{pm := PM} = ?config(mam_meta_opts, Config), 1008: NewOpts = Opts#{pm := PM#{no_stanzaid_element => true}}, 1009: [{mod_mam_meta, NewOpts}]; 1010: required_modules(CaseName, Config) when CaseName =:= same_stanza_id; 1011: CaseName =:= retract_message_on_stanza_id -> 1012: Opts = #{pm := PM} = ?config(mam_meta_opts, Config), 1013: NewOpts = Opts#{pm := PM#{same_mam_id_for_peers => true}}, 1014: [{mod_mam_meta, NewOpts}]; 1015: required_modules(_, Config) -> 1016: Opts = ?config(mam_meta_opts, Config), 1017: [{mod_mam_meta, Opts}]. 1018: 1019: %%-------------------------------------------------------------------- 1020: %% Group name helpers 1021: %%-------------------------------------------------------------------- 1022: 1023: full_group(Conf, Group) -> 1024: list_to_atom(atom_to_list(Conf) ++ "_" ++ atom_to_list(Group)). 1025: 1026: %% @doc Delete suffix. 1027: configuration(Group) -> 1028: match_atom_prefix(Group, make_greedy(configurations())). 1029: 1030: %% @doc Rearrange a list of strings (or atoms), that all prefixes 1031: %% will be tested. 1032: %% 1033: %% Example: 1034: %% `make_greedy(rdbms_mnesia_muc, [rdbms, rdbms_mnesia]) -> rdbms' 1035: %% `make_greedy(rdbms_mnesia_muc, match_longer_first([rdbms, rdbms_mnesia])) -> rdbms_mnesia' 1036: %% @end 1037: make_greedy(List) -> 1038: lists:reverse(lists:usort(List)). 1039: 1040: %% @doc Delete prefix. 1041: basic_group(Group) -> 1042: basic_group(Group, configuration(Group)). 1043: 1044: basic_group(Group, Conf) -> 1045: ConfS = atom_to_list(Conf), 1046: GroupS = atom_to_list(Group), 1047: list_to_atom(delete_delimiter(delete_prefix(ConfS, GroupS))). 1048: 1049: match_atom_prefix(Target, Prefixes) -> 1050: match_atom_prefix1(atom_to_list(Target), Prefixes). 1051: 1052: match_atom_prefix1(TargetS, [PrefixA | Prefixes]) -> 1053: PrefixS = atom_to_list(PrefixA), 1054: case lists:prefix(PrefixS, TargetS) of 1055: true -> PrefixA; 1056: false -> match_atom_prefix1(TargetS, Prefixes) 1057: end. 1058: 1059: delete_prefix([H|Prefix], [H|Target]) -> 1060: delete_prefix(Prefix, Target); 1061: delete_prefix([], Target) -> 1062: Target. 1063: 1064: delete_delimiter("_" ++ Tail) -> 1065: Tail. 1066: 1067: %%-------------------------------------------------------------------- 1068: %% Adhoc tests 1069: %%-------------------------------------------------------------------- 1070: 1071: % @doc Helper function, sends an example message to a user and checks 1072: % if archive id elements are defined or not 1073: send_and_check_archive_elements(Config, Archived, Stanzaid) -> 1074: F = fun(Alice, Bob) -> 1075: %% Archive must be empty. 1076: %% Alice sends "OH, HAI!" to Bob. 1077: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)), 1078: 1079: %% Bob receives a message. 1080: BobMsg = escalus:wait_for_stanza(Bob), 1081: case exml_query:subelement(BobMsg, <<"archived">>) of 1082: undefined -> 1083: ?assert_equal(Archived, false); 1084: _ -> 1085: ?assert_equal(Archived, true) 1086: end, 1087: case exml_query:subelement(BobMsg, <<"stanza-id">>) of 1088: undefined -> 1089: ?assert_equal(Stanzaid, false); 1090: _ -> 1091: ?assert_equal(Stanzaid, true) 1092: end, 1093: ok 1094: end, 1095: %% Made fresh in init_per_testcase 1096: escalus:story(Config, [{alice, 1}, {bob, 1}], F). 1097: 1098: % @doc Helper function, sends an example message to a room and checks 1099: % if archive id elements are defined or not 1100: muc_send_and_check_archive_elements(Config, Archived, Stanzaid) -> 1101: F = fun(Alice, Bob) -> 1102: Room = ?config(room, Config), 1103: RoomAddr = room_address(Room), 1104: Text = <<"Hi, Bob!">>, 1105: escalus:send(Alice, stanza_muc_enter_room(Room, nick(Alice))), 1106: escalus:send(Bob, stanza_muc_enter_room(Room, nick(Bob))), 1107: 1108: %% Bob received presences. 1109: escalus:wait_for_stanzas(Bob, 2), 1110: 1111: %% Bob received the room's subject. 1112: escalus:wait_for_stanzas(Bob, 1), 1113: 1114: %% Alice sends another message to Bob. 1115: %% The message is not archived by the room. 1116: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)), 1117: escalus:assert(is_message, escalus:wait_for_stanza(Bob)), 1118: 1119: %% Alice sends to the chat room. 1120: escalus:send(Alice, escalus_stanza:groupchat_to(RoomAddr, Text)), 1121: 1122: %% Bob received the message "Hi, Bob!". 1123: %% This message will be archived (by alicesroom@localhost). 1124: %% User's archive is disabled (i.e. bob@localhost). 1125: BobMsg = escalus:wait_for_stanza(Bob), 1126: escalus:assert(is_message, BobMsg), 1127: case exml_query:subelement(BobMsg, <<"archived">>) of 1128: undefined -> 1129: ?assert_equal(Archived, false); 1130: _ -> 1131: ?assert_equal(Archived, true) 1132: end, 1133: case exml_query:subelement(BobMsg, <<"stanza-id">>) of 1134: undefined -> 1135: ?assert_equal(Stanzaid, false); 1136: _ -> 1137: ?assert_equal(Stanzaid, true) 1138: end, 1139: ok 1140: end, 1141: escalus:story(Config, [{alice, 1}, {bob, 1}], F). 1142: 1143: %% Archive id elements should be present when config says so 1144: muc_no_elements(Config) -> 1145: muc_send_and_check_archive_elements(Config, false, false). 1146: 1147: muc_only_stanzaid(Config) -> 1148: muc_send_and_check_archive_elements(Config, false, true). 1149: 1150: no_elements(Config) -> 1151: send_and_check_archive_elements(Config, false, false). 1152: 1153: only_stanzaid(Config) -> 1154: send_and_check_archive_elements(Config, false, true). 1155: 1156: same_stanza_id(Config) -> 1157: P = ?config(props, Config), 1158: F = fun(Alice, Bob) -> 1159: Body = <<"OH, HAI!">>, 1160: Msg = escalus_stanza:chat_to(Bob, Body), 1161: escalus:send(Alice, Msg), 1162: mam_helper:wait_for_archive_size(Alice, 1), 1163: escalus:send(Alice, stanza_archive_request(P, <<"q1">>)), 1164: Result = wait_archive_respond(Alice), 1165: [AliceCopyOfMessage] = respond_messages(Result), 1166: AliceId = exml_query:path(AliceCopyOfMessage, [{element, <<"result">>}, {attr, <<"id">>}]), 1167: %% ... and Bob receives the message 1168: RecvMsg = escalus:wait_for_stanza(Bob), 1169: BobId = exml_query:path(RecvMsg, [{element, <<"stanza-id">>}, {attr, <<"id">>}]), 1170: ?assert_equal(AliceId, BobId) 1171: end, 1172: escalus_fresh:story(Config, [{alice, 1}, {bob, 1}], F). 1173: 1174: %% Querying the archive for messages 1175: simple_archive_request(Config) -> 1176: P = ?config(props, Config), 1177: F = fun(Alice, Bob) -> 1178: %% Alice sends "OH, HAI!" to Bob 1179: %% {xmlel,<<"message">>, 1180: %% [{<<"from">>,<<"alice@localhost/res1">>}, 1181: %% {<<"to">>,<<"bob@localhost/res1">>}, 1182: %% {<<"xml:lang">>,<<"en">>}, 1183: %% {<<"type">>,<<"chat">>}], 1184: %% [{xmlel,<<"body">>,[],[{xmlcdata,<<"OH, HAI!">>}]}]} 1185: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)), 1186: mam_helper:wait_for_archive_size(Alice, 1), 1187: escalus:send(Alice, stanza_archive_request(P, <<"q1">>)), 1188: Res = wait_archive_respond(Alice), 1189: assert_respond_size(1, Res), 1190: assert_respond_query_id(P, <<"q1">>, parse_result_iq(Res)), 1191: ok 1192: end, 1193: escalus_fresh:story(Config, [{alice, 1}, {bob, 1}], F). 1194: 1195: simple_archive_request_for_the_receiver(Config) -> 1196: P = ?config(props, Config), 1197: F = fun(Alice, Bob) -> 1198: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)), 1199: BobMsg = escalus:wait_for_stanza(Bob), 1200: escalus:assert(is_message, BobMsg), 1201: mam_helper:wait_for_archive_size(Bob, 1), 1202: escalus:send(Bob, stanza_archive_request(P, <<"q1">>)), 1203: Res = wait_archive_respond(Bob), 1204: assert_respond_size(1, Res), 1205: assert_respond_query_id(P, <<"q1">>, parse_result_iq(Res)), 1206: ok 1207: end, 1208: escalus_fresh:story(Config, [{alice, 1}, {bob, 1}], F). 1209: 1210: text_search_is_not_available(Config) -> 1211: P = ?config(props, Config), 1212: F = fun(Alice) -> 1213: Namespace = get_prop(mam_ns, P), 1214: escalus:send(Alice, stanza_retrieve_form_fields(<<"q">>, Namespace)), 1215: Res = escalus:wait_for_stanza(Alice), 1216: escalus:assert(is_iq_with_ns, [Namespace], Res), 1217: QueryEl = exml_query:subelement(Res, <<"query">>), 1218: XEl = exml_query:subelement(QueryEl, <<"x">>), 1219: Fields = exml_query:paths(XEl, [{element, <<"field">>}]), 1220: HasFullTextSearch = lists:any(fun(Item) -> 1221: exml_query:attr(Item, <<"var">>) == <<"full-text-search">> 1222: end, Fields), 1223: 1224: ?assert_equal(false, HasFullTextSearch) 1225: end, 1226: escalus_fresh:story(Config, [{alice, 1}], F). 1227: 1228: text_search_query_fails_if_disabled(Config) -> 1229: P = ?config(props, Config), 1230: F = fun(_Alice, Bob) -> 1231: escalus:send(Bob, stanza_text_search_archive_request(P, <<"q1">>, 1232: <<"this IQ is expected to fail">>)), 1233: Res = escalus:wait_for_stanza(Bob), 1234: escalus:assert(is_iq_error, Res) 1235: end, 1236: escalus_fresh:story(Config, [{alice, 1}, {bob, 1}], F). 1237: 1238: text_search_is_available(Config) -> 1239: P = ?config(props, Config), 1240: F = fun(Alice) -> 1241: Namespace = get_prop(mam_ns, P), 1242: escalus:send(Alice, stanza_retrieve_form_fields(<<"q">>, Namespace)), 1243: Res = escalus:wait_for_stanza(Alice), 1244: escalus:assert(is_iq_with_ns, [Namespace], Res), 1245: QueryEl = exml_query:subelement(Res, <<"query">>), 1246: XEl = exml_query:subelement(QueryEl, <<"x">>), 1247: escalus:assert(has_field_with_type, [<<"full-text-search">>, <<"text-single">>], XEl), 1248: ok 1249: end, 1250: escalus_fresh:story(Config, [{alice, 1}], F). 1251: 1252: simple_text_search_request(Config) -> 1253: P = ?config(props, Config), 1254: F = fun(Alice, Bob) -> 1255: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"Hi there! My cat's name is John">>)), 1256: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"Also my bike broke down so I'm unable ", 1257: "to return him home">>)), 1258: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"Cats are awesome by the way">>)), 1259: mam_helper:wait_for_archive_size(Alice, 3), 1260: maybe_wait_for_archive(Config), %% yz lag 1261: 1262: %% 'Cat' query 1263: escalus:send(Alice, stanza_text_search_archive_request(P, <<"q1">>, <<"cat">>)), 1264: Res1 = wait_archive_respond(Alice), 1265: assert_respond_size(2, Res1), 1266: assert_respond_query_id(P, <<"q1">>, parse_result_iq(Res1)), 1267: [Msg1, Msg2] = respond_messages(Res1), 1268: #forwarded_message{message_body = Body1} = parse_forwarded_message(Msg1), 1269: #forwarded_message{message_body = Body2} = parse_forwarded_message(Msg2), 1270: ?assert_equal(<<"Hi there! My cat's name is John">>, Body1), 1271: ?assert_equal(<<"Cats are awesome by the way">>, Body2), 1272: 1273: %% 'Bike' query 1274: escalus:send(Alice, stanza_text_search_archive_request(P, <<"q2">>, <<"bike">>)), 1275: Res2 = wait_archive_respond(Alice), 1276: assert_respond_size(1, Res2), 1277: assert_respond_query_id(P, <<"q2">>, parse_result_iq(Res2)), 1278: [Msg3] = respond_messages(Res2), 1279: #forwarded_message{message_body = Body3} = parse_forwarded_message(Msg3), 1280: ?assert_equal(<<"Also my bike broke down so I'm unable to return him home">>, Body3), 1281: 1282: ok 1283: end, 1284: escalus_fresh:story(Config, [{alice, 1}, {bob, 1}], F). 1285: 1286: long_text_search_request(Config) -> 1287: P = ?config(props, Config), 1288: F = fun(Alice, Bob) -> 1289: Msgs = text_search_messages(), 1290: 1291: [ escalus:send(Alice, escalus_stanza:chat_to(Bob, Msg)) || Msg <- Msgs ], 1292: 1293: %% Just check that Bob receives the messages. 1294: %% It should help, when the CI server is overloaded. 1295: %% The test should work without this block. 1296: %% But sometimes on the CI server we ending up with not all messages 1297: %% yet archived, which leads to the test failure. 1298: ExpectedLen = length(Msgs), 1299: BobMessages = escalus:wait_for_stanzas(Bob, ExpectedLen, 15000), 1300: ?assert_equal_extra(ExpectedLen, length(BobMessages), 1301: #{bob_messages => BobMessages}), 1302: 1303: mam_helper:wait_for_archive_size(Bob, ExpectedLen), 1304: mam_helper:wait_for_archive_size(Alice, ExpectedLen), 1305: maybe_wait_for_archive(Config), %% yz lag 1306: escalus:send(Alice, stanza_text_search_archive_request(P, <<"q1">>, 1307: <<"Ribs poRk cUlpa">>)), 1308: Res = wait_archive_respond(Alice), 1309: assert_respond_size(3, Res), 1310: assert_respond_query_id(P, <<"q1">>, parse_result_iq(Res)), 1311: 1312: [Msg1, Msg2, Msg3] = respond_messages(Res), 1313: #forwarded_message{message_body = Body1} = parse_forwarded_message(Msg1), 1314: #forwarded_message{message_body = Body2} = parse_forwarded_message(Msg2), 1315: #forwarded_message{message_body = Body3} = parse_forwarded_message(Msg3), 1316: 1317: ?assert_equal(lists:nth(2, Msgs), Body1), 1318: ?assert_equal(lists:nth(8, Msgs), Body2), 1319: ?assert_equal(lists:nth(11, Msgs), Body3), 1320: 1321: ok 1322: end, 1323: escalus_fresh:story(Config, [{alice, 1}, {bob, 1}], F). 1324: 1325: %% Write and read Unicode messages back 1326: unicode_messages_can_be_extracted(Config) -> 1327: P = ?config(props, Config), 1328: F = fun(Alice, Bob) -> 1329: Texts = [<<"Hi! this is an unicode character lol 😂"/utf8>>, 1330: <<"this is another one no 🙅"/utf8>>, 1331: <<"This is the same again lol 😂"/utf8>>], 1332: 1333: [escalus:send(Alice, escalus_stanza:chat_to(Bob, Text)) 1334: || Text <- Texts], 1335: mam_helper:wait_for_archive_size(Alice, length(Texts)), 1336: 1337: %% WHEN Getting all messages 1338: escalus:send(Alice, stanza_archive_request(P, <<"uni-q">>)), 1339: Res = wait_archive_respond(Alice), 1340: assert_respond_size(3, Res), 1341: 1342: assert_respond_query_id(P, <<"uni-q">>, parse_result_iq(Res)), 1343: [Msg1, Msg2, Msg3] = respond_messages(Res), 1344: #forwarded_message{message_body = Body1} = parse_forwarded_message(Msg1), 1345: #forwarded_message{message_body = Body2} = parse_forwarded_message(Msg2), 1346: #forwarded_message{message_body = Body3} = parse_forwarded_message(Msg3), 1347: ?assert_equal(<<"Hi! this is an unicode character lol 😂"/utf8>>, Body1), 1348: ?assert_equal(<<"this is another one no 🙅"/utf8>>, Body2), 1349: ?assert_equal(<<"This is the same again lol 😂"/utf8>>, Body3), 1350: ok 1351: end, 1352: escalus_fresh:story(Config, [{alice, 1}, {bob, 1}], F). 1353: 1354: %% Depends on search feature 1355: %% Consult with unicode_messages_can_be_extracted, 1356: %% which ensures that unicode messages can be processed 1357: save_unicode_messages(Config) -> 1358: P = ?config(props, Config), 1359: F = fun(Alice, Bob) -> 1360: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"Hi! this is an unicode character lol 😂"/utf8>>)), 1361: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"this is another one no 🙅"/utf8>>)), 1362: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"This is the same again lol 😂"/utf8>>)), 1363: mam_helper:wait_for_archive_size(Alice, 3), 1364: 1365: %% Riak YZ is async. 1366: %% It takes time to index docs (usually 1 second). 1367: %% https://docs.riak.com/riak/kv/latest/developing/usage/search/index.html#indexing-values 1368: %% Each stanza_text_search_archive_request should call it regardless of wait_for_archive_size result. 1369: maybe_wait_for_archive(Config), 1370: 1371: %% WHEN Searching for a message with "lol" string 1372: escalus:send(Alice, stanza_text_search_archive_request(P, <<"q1">>, <<"lol"/utf8>>)), 1373: Res1 = wait_archive_respond(Alice), 1374: assert_respond_size(2, Res1), 1375: assert_respond_query_id(P, <<"q1">>, parse_result_iq(Res1)), 1376: [Msg1, Msg2] = respond_messages(Res1), 1377: #forwarded_message{message_body = Body1} = parse_forwarded_message(Msg1), 1378: #forwarded_message{message_body = Body2} = parse_forwarded_message(Msg2), 1379: ?assert_equal(<<"Hi! this is an unicode character lol 😂"/utf8>>, Body1), 1380: ?assert_equal(<<"This is the same again lol 😂"/utf8>>, Body2), 1381: 1382: escalus:send(Alice, stanza_text_search_archive_request(P, <<"q2">>, <<"another"/utf8>>)), 1383: Res2 = wait_archive_respond(Alice), 1384: assert_respond_size(1, Res2), 1385: assert_respond_query_id(P, <<"q2">>, parse_result_iq(Res2)), 1386: [Msg3] = respond_messages(Res2), 1387: #forwarded_message{message_body = Body3} = parse_forwarded_message(Msg3), 1388: ?assert_equal(<<"this is another one no 🙅"/utf8>>, Body3), 1389: 1390: ok 1391: end, 1392: escalus_fresh:story(Config, [{alice, 1}, {bob, 1}], F). 1393: 1394: stanza_id_is_appended_to_carbons(Config) -> 1395: F = fun(Alice1, Alice2, Bob1, Bob2) -> 1396: Msg = <<"OH, HAI!">>, 1397: mongoose_helper:enable_carbons([Alice1, Alice2, Bob1, Bob2]), 1398: escalus:send(Alice1, escalus_stanza:chat_to(Bob1, Msg)), 1399: mam_helper:wait_for_archive_size(Alice1, 1), 1400: escalus_client:wait_for_stanza(Bob1), 1401: Alice2CC = escalus_client:wait_for_stanza(Alice2), 1402: Bob2CC = escalus_client:wait_for_stanza(Bob2), 1403: 1404: SID = fun(Packet, Direction) -> 1405: exml_query:path(Packet, [{element_with_ns, Direction, <<"urn:xmpp:carbons:2">>}, 1406: {element_with_ns, <<"forwarded">>, <<"urn:xmpp:forward:0">>}, 1407: {element_with_ns, <<"message">>, <<"jabber:client">>}, 1408: {element_with_ns, <<"stanza-id">>, <<"urn:xmpp:sid:0">>}, 1409: {attr, <<"id">>}]) 1410: end, 1411: ?assert_equal(true, undefined =/= SID(Bob2CC, <<"received">>)), 1412: ?assert_equal(true, undefined =/= SID(Alice2CC, <<"sent">>)), 1413: escalus:assert(is_forwarded_sent_message, 1414: [escalus_client:full_jid(Alice1), escalus_client:full_jid(Bob1), Msg], Alice2CC), 1415: escalus:assert(is_forwarded_received_message, 1416: [escalus_client:full_jid(Alice1), escalus_client:full_jid(Bob1), Msg], Bob2CC) 1417: end, 1418: escalus_fresh:story(Config, [{alice, 2}, {bob, 2}], F). 1419: 1420: muc_text_search_request(Config) -> 1421: P = ?config(props, Config), 1422: F = fun(Alice, Bob) -> 1423: Room = ?config(room, Config), 1424: escalus:send(Alice, stanza_muc_enter_room(Room, nick(Alice))), 1425: escalus:send(Bob, stanza_muc_enter_room(Room, nick(Bob))), 1426: 1427: %% Bob received presences. 1428: escalus:wait_for_stanzas(Bob, 2), 1429: 1430: %% Bob received the room's subject. 1431: escalus:wait_for_stanzas(Bob, 1), 1432: 1433: Msgs = text_search_messages(), 1434: 1435: lists:foreach( 1436: fun(Msg) -> 1437: Stanza = escalus_stanza:groupchat_to(room_address(Room), Msg), 1438: escalus:send(Alice, Stanza), 1439: escalus:assert(is_message, escalus:wait_for_stanza(Bob)) 1440: end, Msgs), 1441: 1442: maybe_wait_for_archive(Config), 1443: SearchStanza = stanza_text_search_archive_request(P, <<"q1">>, <<"Ribs poRk cUlpa">>), 1444: escalus:send(Bob, stanza_to_room(SearchStanza, Room)), 1445: Res = wait_archive_respond(Bob), 1446: assert_respond_size(3, Res), 1447: assert_respond_query_id(P, <<"q1">>, parse_result_iq(Res)), 1448: 1449: [Msg1, Msg2, Msg3] = respond_messages(Res), 1450: #forwarded_message{message_body = Body1} = parse_forwarded_message(Msg1), 1451: ?assert_equal(lists:nth(2, Msgs), Body1), 1452: #forwarded_message{message_body = Body2} = parse_forwarded_message(Msg2), 1453: ?assert_equal(lists:nth(8, Msgs), Body2), 1454: #forwarded_message{message_body = Body3} = parse_forwarded_message(Msg3), 1455: ?assert_equal(lists:nth(11, Msgs), Body3), 1456: 1457: ok 1458: end, 1459: escalus:story(Config, [{alice, 1}, {bob, 1}], F). 1460: 1461: 1462: querying_for_all_messages_with_jid(Config) -> 1463: P = ?config(props, Config), 1464: F = fun(Alice) -> 1465: Pregenerated = ?config(pre_generated_msgs, Config), 1466: BWithJID = nick_to_jid(bob, Config), 1467: 1468: WithBob = [1 || {_, _, {JID, _, _}, _, _} <- Pregenerated, 1469: escalus_utils:jid_to_lower(JID) == BWithJID], 1470: 1471: CountWithBob = lists:sum(WithBob), 1472: escalus:send(Alice, stanza_filtered_by_jid_request(P, BWithJID)), 1473: assert_respond_size(CountWithBob, wait_archive_respond(Alice)), 1474: ok 1475: end, 1476: escalus:story(Config, [{alice, 1}], F). 1477: 1478: muc_querying_for_all_messages(Config) -> 1479: P = ?config(props, Config), 1480: F = fun(Alice) -> 1481: maybe_wait_for_archive(Config), 1482: 1483: Room = ?config(room, Config), 1484: MucMsgs = ?config(pre_generated_muc_msgs, Config), 1485: 1486: MucArchiveLen = length(MucMsgs), 1487: 1488: IQ = stanza_archive_request(P, <<>>), 1489: escalus:send(Alice, stanza_to_room(IQ, Room)), 1490: maybe_wait_for_archive(Config), 1491: assert_respond_size(MucArchiveLen, wait_archive_respond(Alice)), 1492: 1493: ok 1494: end, 1495: escalus:story(Config, [{alice, 1}], F). 1496: 1497: muc_querying_for_all_messages_with_jid(Config) -> 1498: P = ?config(props, Config), 1499: F = fun(Alice) -> 1500: Room = ?config(room, Config), 1501: BobNick = ?config(bob_nickname, Config), 1502: BWithJID = room_address(Room, BobNick), 1503: 1504: MucMsgs = ?config(pre_generated_muc_msgs, Config), 1505: WithJID = [1 || {_, _, {JID, _, _}, _, _} <- MucMsgs, JID == BWithJID], 1506: Len = lists:sum(WithJID), 1507: 1508: IQ = stanza_filtered_by_jid_request(P, BWithJID), 1509: escalus:send(Alice, stanza_to_room(IQ, Room)), 1510: Result = wait_archive_respond(Alice), 1511: 1512: assert_respond_size(Len, Result), 1513: ok 1514: end, 1515: escalus:story(Config, [{alice, 1}], F). 1516: 1517: muc_light_simple(Config) -> 1518: escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 1519: Room = <<"testroom">>, 1520: given_muc_light_room(Room, Alice, []), 1521: 1522: M1 = when_muc_light_message_is_sent(Alice, Room, 1523: <<"Msg 1">>, <<"Id1">>), 1524: then_muc_light_message_is_received_by([Alice], M1), 1525: 1526: M2 = when_muc_light_message_is_sent(Alice, Room, 1527: <<"Message 2">>, <<"MyID2">>), 1528: then_muc_light_message_is_received_by([Alice], M2), 1529: 1530: Aff = when_muc_light_affiliations_are_set(Alice, Room, [{Bob, member}]), 1531: then_muc_light_affiliations_are_received_by([Alice, Bob], Aff), 1532: 1533: maybe_wait_for_archive(Config), 1534: when_archive_query_is_sent(Bob, muc_light_helper:room_bin_jid(Room), Config), 1535: ExpectedResponse = [{create, [{Alice, owner}]}, 1536: {muc_message, Room, Alice, <<"Msg 1">>}, 1537: {muc_message, Room, Alice, <<"Message 2">>}, 1538: {affiliations, [{Bob, member}]}], 1539: then_archive_response_is(Bob, ExpectedResponse, Config) 1540: end). 1541: 1542: muc_light_shouldnt_modify_pm_archive(Config) -> 1543: escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 1544: Room = <<"testroom2">>, 1545: given_muc_light_room(Room, Alice, [{Bob, member}]), 1546: 1547: when_pm_message_is_sent(Alice, Bob, <<"private hi!">>), 1548: then_pm_message_is_received(Bob, <<"private hi!">>), 1549: 1550: maybe_wait_for_archive(Config), 1551: when_archive_query_is_sent(Alice, undefined, Config), 1552: then_archive_response_is(Alice, [{message, Alice, <<"private hi!">>}], Config), 1553: when_archive_query_is_sent(Bob, undefined, Config), 1554: then_archive_response_is(Bob, [{message, Alice, <<"private hi!">>}], Config), 1555: 1556: M1 = when_muc_light_message_is_sent(Alice, Room, 1557: <<"Msg 1">>, <<"Id 1">>), 1558: then_muc_light_message_is_received_by([Alice, Bob], M1), 1559: 1560: maybe_wait_for_archive(Config), 1561: when_archive_query_is_sent(Alice, muc_light_helper:room_bin_jid(Room), Config), 1562: then_archive_response_is(Alice, [{create, [{Alice, owner}, {Bob, member}]}, 1563: {muc_message, Room, Alice, <<"Msg 1">>}], Config), 1564: 1565: when_archive_query_is_sent(Alice, undefined, Config), 1566: then_archive_response_is(Alice, [{message, Alice, <<"private hi!">>}], Config), 1567: when_archive_query_is_sent(Bob, undefined, Config), 1568: then_archive_response_is(Bob, [{message, Alice, <<"private hi!">>}], Config) 1569: end). 1570: 1571: muc_light_stored_in_pm_if_allowed_to(Config) -> 1572: escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 1573: Room = <<"testroom_pm">>, 1574: given_muc_light_room(Room, Alice, [{Bob, member}]), 1575: 1576: maybe_wait_for_archive(Config), 1577: AliceAffEvent = {affiliations, [{Alice, owner}]}, 1578: when_archive_query_is_sent(Alice, undefined, Config), 1579: then_archive_response_is(Alice, [AliceAffEvent], Config), 1580: BobAffEvent = {affiliations, [{Bob, member}]}, 1581: when_archive_query_is_sent(Bob, undefined, Config), 1582: then_archive_response_is(Bob, [BobAffEvent], Config), 1583: 1584: M1 = when_muc_light_message_is_sent(Alice, Room, <<"Msg 1">>, <<"Id 1">>), 1585: then_muc_light_message_is_received_by([Alice, Bob], M1), 1586: 1587: maybe_wait_for_archive(Config), 1588: MessageEvent = {muc_message, Room, Alice, <<"Msg 1">>}, 1589: when_archive_query_is_sent(Alice, undefined, Config), 1590: then_archive_response_is(Alice, [AliceAffEvent, MessageEvent], Config), 1591: when_archive_query_is_sent(Bob, undefined, Config), 1592: then_archive_response_is(Bob, [BobAffEvent, MessageEvent], Config) 1593: end). 1594: 1595: muc_light_chat_markers_are_archived_if_enabled(Config) -> 1596: escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 1597: Room = <<"testroom_markers">>, 1598: given_muc_light_room(Room, Alice, [{Bob, member}]), 1599: 1600: %% Alice sends 3 chat markers 1601: MessageID = <<"some-fake-id">>, 1602: RoomJID = muc_light_helper:room_bin_jid(Room), 1603: lists:foreach( 1604: fun(Type) -> 1605: Marker1 = escalus_stanza:chat_marker(RoomJID, Type, MessageID), 1606: Marker2 = escalus_stanza:setattr(Marker1, <<"type">>, <<"groupchat">>), 1607: escalus:send(Alice, Marker2), 1608: escalus:wait_for_stanza(Alice), 1609: escalus:wait_for_stanza(Bob) 1610: end, [<<"received">>, <<"displayed">>, <<"acknowledged">>]), 1611: 1612: maybe_wait_for_archive(Config), 1613: when_archive_query_is_sent(Bob, muc_light_helper:room_bin_jid(Room), Config), 1614: ExpectedResponse = [ 1615: {create, [{Alice, owner}, {Bob, member}]}, 1616: {chat_marker, <<"received">>}, 1617: {chat_marker, <<"displayed">>}, 1618: {chat_marker, <<"acknowledged">>} 1619: ], 1620: then_archive_response_is(Bob, ExpectedResponse, Config) 1621: end). 1622: 1623: muc_light_chat_markers_are_not_archived_if_disabled(Config) -> 1624: escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 1625: Room = <<"testroom_no_markers">>, 1626: given_muc_light_room(Room, Alice, [{Bob, member}]), 1627: 1628: %% Alice sends 3 chat markers 1629: MessageID = <<"some-fake-id">>, 1630: RoomJID = muc_light_helper:room_bin_jid(Room), 1631: lists:foreach( 1632: fun(Type) -> 1633: Marker1 = escalus_stanza:chat_marker(RoomJID, Type, MessageID), 1634: Marker2 = escalus_stanza:setattr(Marker1, <<"type">>, <<"groupchat">>), 1635: escalus:send(Alice, Marker2), 1636: escalus:wait_for_stanza(Alice), 1637: escalus:wait_for_stanza(Bob) 1638: end, [<<"received">>, <<"displayed">>, <<"acknowledged">>]), 1639: 1640: maybe_wait_for_archive(Config), 1641: when_archive_query_is_sent(Bob, muc_light_helper:room_bin_jid(Room), Config), 1642: ExpectedResponse = [{create, [{Alice, owner}, {Bob, member}]}], 1643: then_archive_response_is(Bob, ExpectedResponse, Config) 1644: end). 1645: 1646: retrieve_form_fields(ConfigIn) -> 1647: escalus_fresh:story(ConfigIn, [{alice, 1}], fun(Alice) -> 1648: P = ?config(props, ConfigIn), 1649: Namespace = get_prop(mam_ns, P), 1650: escalus:send(Alice, stanza_retrieve_form_fields(<<"q">>, Namespace)), 1651: Res = escalus:wait_for_stanza(Alice), 1652: escalus:assert(is_iq_with_ns, [Namespace], Res) 1653: end). 1654: 1655: archived(Config) -> 1656: P = ?config(props, Config), 1657: F = fun(Alice, Bob) -> 1658: %% Archive must be empty. 1659: %% Alice sends "OH, HAI!" to Bob. 1660: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)), 1661: 1662: %% Bob receives a message. 1663: Msg = escalus:wait_for_stanza(Bob), 1664: try 1665: StanzaId = exml_query:subelement(Msg, <<"stanza-id">>), 1666: %% JID of the archive (i.e. where the client would send queries to) 1667: By = exml_query:attr(StanzaId, <<"by">>), 1668: %% Attribute giving the message's UID within the archive. 1669: Id = exml_query:attr(StanzaId, <<"id">>), 1670: 1671: ?assert_equal(By, escalus_client:short_jid(Bob)), 1672: 1673: %% Bob calls archive. 1674: maybe_wait_for_archive(Config), 1675: escalus:send(Bob, stanza_archive_request(P, <<"q1">>)), 1676: [ArcMsg] = respond_messages(assert_respond_size(1, wait_archive_respond(Bob))), 1677: #forwarded_message{result_id=ArcId} = parse_forwarded_message(ArcMsg), 1678: ?assert_equal(Id, ArcId), 1679: ok 1680: catch Class:Reason:StackTrace -> 1681: ct:pal("Msg ~p", [Msg]), 1682: erlang:raise(Class, Reason, StackTrace) 1683: end 1684: end, 1685: %% Made fresh in init_per_testcase 1686: escalus:story(Config, [{alice, 1}, {bob, 1}], F). 1687: 1688: 1689: message_with_stanzaid(Config) -> 1690: F = fun(Alice, Bob) -> 1691: %% Archive must be empty. 1692: %% Alice sends "OH, HAI!" to Bob. 1693: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)), 1694: 1695: %% Bob receives a message. 1696: Msg = escalus:wait_for_stanza(Bob), 1697: 1698: ArcStanzaid = exml_query:subelement(Msg, <<"stanza-id">>), 1699: 1700: %% stanza-id has a namespace 'urn:xmpp:sid:0' 1701: <<"urn:xmpp:sid:0">> = exml_query:attr(ArcStanzaid, <<"xmlns">>), 1702: ok 1703: end, 1704: escalus:story(Config, [{alice, 1}, {bob, 1}], F). 1705: 1706: retract_message_on_stanza_id(Config) -> 1707: test_retract_message([{retract_on, stanza_id} | Config]). 1708: 1709: retract_wrong_message(Config) -> 1710: test_retract_message([{retract_on, {origin_id, <<"wrong-id">>}} | Config]). 1711: 1712: ignore_bad_retraction(Config) -> 1713: test_retract_message([{retract_on, none} | Config]). 1714: 1715: retract_message(Config) -> 1716: test_retract_message([{retract_on, {origin_id, origin_id()}} | Config]). 1717: 1718: test_retract_message(Config) -> 1719: P = ?config(props, Config), 1720: F = fun(Alice, Bob) -> 1721: %% GIVEN Alice sends a message with 'origin-id' to Bob 1722: Body = <<"OH, HAI!">>, 1723: OriginIdElement = origin_id_element(origin_id()), 1724: Msg = #xmlel{children = Children} = escalus_stanza:chat_to(Bob, Body), 1725: escalus:send(Alice, Msg#xmlel{children = Children ++ [OriginIdElement]}), 1726: 1727: mam_helper:wait_for_archive_size(Alice, 1), 1728: escalus:send(Alice, stanza_archive_request(P, <<"q1">>)), 1729: Result = wait_archive_respond(Alice), 1730: [AliceCopyOfMessage] = respond_messages(Result), 1731: 1732: %% ... and Bob receives the message 1733: RecvMsg = escalus:wait_for_stanza(Bob), 1734: ?assert_equal(OriginIdElement, exml_query:subelement(RecvMsg, <<"origin-id">>)), 1735: 1736: %% WHEN Alice retracts the message 1737: ApplyToElement = apply_to_element(Config, AliceCopyOfMessage), 1738: RetractMsg = retraction_message(<<"chat">>, escalus_utils:get_jid(Bob), ApplyToElement), 1739: escalus:send(Alice, RetractMsg), 1740: 1741: %% THEN Bob receives the message with 'retract' ... 1742: RecvRetract = escalus:wait_for_stanza(Bob), 1743: ?assert_equal(ApplyToElement, exml_query:subelement(RecvRetract, <<"apply-to">>)), 1744: 1745: maybe_wait_for_archive(Config), 1746: 1747: %% ... and Alice and Bob have both messages in their archives 1748: escalus:send(Alice, stanza_archive_request(P, <<"q1">>)), 1749: check_archive_after_retraction(Config, Alice, ApplyToElement, Body), 1750: escalus:send(Bob, stanza_archive_request(P, <<"q2">>)), 1751: check_archive_after_retraction(Config, Bob, ApplyToElement, Body), 1752: 1753: ok 1754: end, 1755: escalus_fresh:story(Config, [{alice, 1}, {bob, 1}], F). 1756: 1757: filter_forwarded(Config) -> 1758: P = ?config(props, Config), 1759: F = fun(Alice, Bob) -> 1760: %% Alice sends "OH, HAI!" to Bob. 1761: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)), 1762: 1763: %% Bob receives a message. 1764: escalus:wait_for_stanza(Bob), 1765: mam_helper:wait_for_archive_size(Bob, 1), 1766: escalus:send(Bob, stanza_archive_request(P, <<"q1">>)), 1767: assert_respond_size(1, wait_archive_respond(Bob)), 1768: 1769: %% Check, that previous forwarded message was not archived. 1770: escalus:send(Bob, stanza_archive_request(P, <<"q2">>)), 1771: assert_respond_size(1, wait_archive_respond(Bob)), 1772: ok 1773: end, 1774: escalus_fresh:story(Config, [{alice, 1}, {bob, 1}], F). 1775: 1776: %% Ensure, that a offline message does not stored twice when delivered. 1777: offline_message(Config) -> 1778: Msg = <<"Is there anybody here?">>, 1779: P = ?config(props, Config), 1780: F = fun(Alice) -> 1781: %% Alice sends a message to Bob while bob is offline. 1782: escalus:send(Alice, 1783: escalus_stanza:chat_to(escalus_users:get_jid(Config, bob), Msg)), 1784: maybe_wait_for_archive(Config), 1785: ok 1786: end, 1787: escalus:story(Config, [{alice, 1}], F), 1788: 1789: %% Bob logs in 1790: Bob = login_send_presence(Config, bob), 1791: 1792: %% If mod_offline is enabled, then an offline message 1793: %% will be delivered automatically. 1794: 1795: %% He receives his initial presence and the message. 1796: escalus:wait_for_stanzas(Bob, 2, 1000), 1797: 1798: %% Bob checks his archive. 1799: escalus:send(Bob, stanza_archive_request(P, <<"q1">>)), 1800: ArcMsgs = respond_messages(wait_archive_respond(Bob)), 1801: assert_only_one_of_many_is_equal(ArcMsgs, Msg), 1802: 1803: escalus_client:stop(Config, Bob). 1804: 1805: nostore_hint(Config) -> 1806: Msg = <<"So secret">>, 1807: P = ?config(props, Config), 1808: F = fun(Alice, Bob) -> 1809: %% Alice sends a message to Bob with a hint. 1810: escalus:send(Alice, 1811: add_nostore_hint(escalus_stanza:chat_to(Bob, Msg))), 1812: maybe_wait_for_archive(Config), 1813: escalus:wait_for_stanzas(Bob, 1, 1000), 1814: 1815: %% Bob checks his archive. 1816: escalus:send(Bob, stanza_archive_request(P, <<"q1">>)), 1817: ArcMsgs = respond_messages(wait_archive_respond(Bob)), 1818: assert_not_stored(ArcMsgs, Msg), 1819: ok 1820: end, 1821: escalus_fresh:story(Config, [{alice, 1}, {bob, 1}], F). 1822: 1823: muc_message_with_stanzaid(Config) -> 1824: F = fun(Alice, Bob) -> 1825: Room = ?config(room, Config), 1826: RoomAddr = room_address(Room), 1827: Text = <<"Hi, Bob!">>, 1828: escalus:send(Alice, stanza_muc_enter_room(Room, nick(Alice))), 1829: escalus:send(Bob, stanza_muc_enter_room(Room, nick(Bob))), 1830: 1831: %% Bob received presences. 1832: escalus:wait_for_stanzas(Bob, 2), 1833: 1834: %% Bob received the room's subject. 1835: escalus:wait_for_stanzas(Bob, 1), 1836: 1837: %% Alice sends another message to Bob. 1838: %% The message is not archived by the room. 1839: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)), 1840: escalus:assert(is_message, escalus:wait_for_stanza(Bob)), 1841: 1842: %% Alice sends to the chat room. 1843: escalus:send(Alice, escalus_stanza:groupchat_to(RoomAddr, Text)), 1844: 1845: %% Bob received the message "Hi, Bob!". 1846: %% This message will be archived (by alicesroom@localhost). 1847: %% User's archive is disabled (i.e. bob@localhost). 1848: BobMsg = escalus:wait_for_stanza(Bob), 1849: escalus:assert(is_message, BobMsg), 1850: ArcStanzaid = exml_query:subelement(BobMsg, <<"stanza-id">>), 1851: 1852: %% stanza-id has a namespace 'urn:xmpp:sid:0' 1853: Xmlns = exml_query:attr(ArcStanzaid, <<"xmlns">>), 1854: ?assert_equal(Xmlns, <<"urn:xmpp:sid:0">>), 1855: ok 1856: end, 1857: escalus:story(Config, [{alice, 1}, {bob, 1}], F). 1858: 1859: retract_muc_message_on_stanza_id(Config) -> 1860: test_retract_muc_message([{retract_on, stanza_id} | Config]). 1861: 1862: retract_wrong_muc_message(Config) -> 1863: test_retract_muc_message([{retract_on, {origin_id, <<"wrong-id">>}} | Config]). 1864: 1865: retract_muc_message(Config) -> 1866: test_retract_muc_message([{retract_on, {origin_id, origin_id()}} | Config]). 1867: 1868: test_retract_muc_message(Config) -> 1869: P = ?config(props, Config), 1870: F = fun(Alice, Bob) -> 1871: Room = ?config(room, Config), 1872: RoomAddr = room_address(Room), 1873: escalus:send(Alice, stanza_muc_enter_room(Room, nick(Alice))), 1874: escalus:send(Bob, stanza_muc_enter_room(Room, nick(Bob))), 1875: %% Bob received presences. 1876: escalus:wait_for_stanzas(Bob, 2), 1877: %% Bob received the room's subject. 1878: escalus:wait_for_stanzas(Bob, 1), 1879: %% Alice receives all the messages Bob did as well 1880: escalus:wait_for_stanzas(Alice, 3), 1881: 1882: %% GIVEN Alice sends a message with 'origin-id' to the chat room ... 1883: Body = <<"Hi, Bob!">>, 1884: OriginIdElement = origin_id_element(origin_id()), 1885: Msg = #xmlel{children = Children} = escalus_stanza:groupchat_to(RoomAddr, Body), 1886: escalus:send(Alice, Msg#xmlel{children = Children ++ [OriginIdElement]}), 1887: 1888: AliceCopyOfMessage = escalus:wait_for_stanza(Alice), 1889: 1890: %% ... and Bob receives the message 1891: RecvMsg = escalus:wait_for_stanza(Bob), 1892: ?assert_equal(OriginIdElement, exml_query:subelement(RecvMsg, <<"origin-id">>)), 1893: 1894: %% WHEN Alice retracts the message 1895: ApplyToElement = apply_to_element(Config, AliceCopyOfMessage), 1896: RetractMsg = retraction_message(<<"groupchat">>, RoomAddr, ApplyToElement), 1897: escalus:send(Alice, RetractMsg), 1898: 1899: %% THEN Bob receives the message with 'retract' ... 1900: RecvRetract = escalus:wait_for_stanza(Bob), 1901: ?assert_equal(ApplyToElement, exml_query:subelement(RecvRetract, <<"apply-to">>)), 1902: 1903: maybe_wait_for_archive(Config), 1904: 1905: %% ... and finds both messages in the room archive 1906: escalus:send(Bob, stanza_to_room(stanza_archive_request(P, <<"q1">>), Room)), 1907: check_archive_after_retraction(Config, Bob, ApplyToElement, Body), 1908: 1909: ok 1910: end, 1911: escalus:story(Config, [{alice, 1}, {bob, 1}], F). 1912: 1913: muc_archive_request(Config) -> 1914: P = ?config(props, Config), 1915: F = fun(Alice, Bob) -> 1916: Room = ?config(room, Config), 1917: RoomAddr = room_address(Room), 1918: Text = <<"Hi, Bob!">>, 1919: escalus:send(Alice, stanza_muc_enter_room(Room, nick(Alice))), 1920: escalus:send(Bob, stanza_muc_enter_room(Room, nick(Bob))), 1921: 1922: %% Bob received presences. 1923: escalus:wait_for_stanzas(Bob, 2), 1924: 1925: %% Bob received the room's subject. 1926: escalus:wait_for_stanzas(Bob, 1), 1927: 1928: %% Alice sends another message to Bob. 1929: %% The message is not archived by the room. 1930: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)), 1931: escalus:assert(is_message, escalus:wait_for_stanza(Bob)), 1932: 1933: %% Alice sends to the chat room. 1934: escalus:send(Alice, escalus_stanza:groupchat_to(RoomAddr, Text)), 1935: 1936: %% Bob received the message "Hi, Bob!". 1937: %% This message will be archived (by alicesroom@localhost). 1938: %% User's archive is disabled (i.e. bob@localhost). 1939: BobMsg = escalus:wait_for_stanza(Bob), 1940: escalus:assert(is_message, BobMsg), 1941: Arc = exml_query:subelement(BobMsg, <<"stanza-id">>), 1942: %% JID of the archive (i.e. where the client would send queries to) 1943: By = exml_query:attr(Arc, <<"by">>), 1944: %% Attribute giving the message's UID within the archive. 1945: Id = exml_query:attr(Arc, <<"id">>), 1946: 1947: maybe_wait_for_archive(Config), 1948: 1949: %% Bob requests the room's archive. 1950: escalus:send(Bob, stanza_to_room(stanza_archive_request(P, <<"q1">>), Room)), 1951: [ArcMsg] = respond_messages(assert_respond_size(1, wait_archive_respond(Bob))), 1952: #forwarded_message{result_id=ArcId, message_body=ArcMsgBody, 1953: message_to=MsgTo, message_from=MsgFrom} = 1954: parse_forwarded_message(ArcMsg), 1955: %% XEP: the 'to' of the forwarded stanza MUST be empty 1956: %% However, Smack crashes if it is present, so it is removed 1957: ?assert_equal_extra(undefined, MsgTo, message_to), 1958: 1959: %% XEP: the 'from' MUST be the occupant JID of the sender of the archived message 1960: ?assert_equal_extra(escalus_utils:jid_to_lower(room_address(Room, nick(Alice))), 1961: escalus_utils:jid_to_lower(MsgFrom), message_from), 1962: 1963: ?assert_equal(Text, ArcMsgBody), 1964: ?assert_equal(ArcId, Id), 1965: ?assert_equal(escalus_utils:jid_to_lower(RoomAddr), By), 1966: ?assert_equal_extra(true, has_x_user_element(ArcMsg), 1967: [{forwarded_message, ArcMsg}]), 1968: ok 1969: end, 1970: escalus:story(Config, [{alice, 1}, {bob, 1}], F). 1971: 1972: muc_multiple_devices(Config) -> 1973: P = ?config(props, Config), 1974: F = fun(Alice1, Alice2, Bob) -> 1975: Room = ?config(room, Config), 1976: RoomAddr = room_address(Room), 1977: Text = <<"Hi, Bob!">>, 1978: %% You should use an unique nick for each device. 1979: escalus:send(Alice1, stanza_muc_enter_room(Room, <<"alice_1">>)), 1980: escalus:send(Alice2, stanza_muc_enter_room(Room, <<"alice_2">>)), 1981: escalus:send(Bob, stanza_muc_enter_room(Room, <<"bob">>)), 1982: 1983: %% Alice received presences. 1984: escalus:wait_for_stanzas(Alice1, 3), 1985: escalus:wait_for_stanzas(Alice2, 3), 1986: 1987: %% Bob received presences. 1988: escalus:wait_for_stanzas(Bob, 3), 1989: 1990: %% Bob received the room's subject. 1991: escalus:wait_for_stanzas(Bob, 1), 1992: 1993: %% Alice received the room's subject. 1994: escalus:wait_for_stanzas(Alice1, 1), 1995: escalus:wait_for_stanzas(Alice2, 1), 1996: 1997: %% Alice sends to the chat room. 1998: escalus:send(Alice1, escalus_stanza:groupchat_to(RoomAddr, Text)), 1999: 2000: %% Alice receives her own message. 2001: Alice1Msg = escalus:wait_for_stanza(Alice1), 2002: escalus:assert(is_message, Alice1Msg), 2003: 2004: Alice2Msg = escalus:wait_for_stanza(Alice2), 2005: escalus:assert(is_message, Alice2Msg), 2006: 2007: Alice1Arc = exml_query:subelement(Alice1Msg, <<"stanza-id">>), 2008: Alice2Arc = exml_query:subelement(Alice2Msg, <<"stanza-id">>), 2009: ?assert_equal(Alice1Arc, Alice2Arc), 2010: 2011: %% Bob received the message "Hi, Bob!". 2012: %% This message will be archived (by alicesroom@localhost). 2013: %% User's archive is disabled (i.e. bob@localhost). 2014: BobMsg = escalus:wait_for_stanza(Bob), 2015: escalus:assert(is_message, BobMsg), 2016: Arc = exml_query:subelement(BobMsg, <<"stanza-id">>), 2017: %% JID of the archive (i.e. where the client would send queries to) 2018: By = exml_query:attr(Arc, <<"by">>), 2019: %% Attribute giving the message's UID within the archive. 2020: Id = exml_query:attr(Arc, <<"id">>), 2021: 2022: ?assert_equal(Alice1Arc, Arc), 2023: 2024: %% Bob requests the room's archive. 2025: 2026: maybe_wait_for_archive(Config), 2027: 2028: escalus:send(Bob, stanza_to_room(stanza_archive_request(P, <<"q1">>), Room)), 2029: [ArcMsg] = respond_messages(assert_respond_size(1, wait_archive_respond(Bob))), 2030: #forwarded_message{result_id=ArcId, message_body=ArcMsgBody} = 2031: parse_forwarded_message(ArcMsg), 2032: ?assert_equal(Text, ArcMsgBody), 2033: ?assert_equal(ArcId, Id), 2034: ?assert_equal(escalus_utils:jid_to_lower(RoomAddr), By), 2035: ok 2036: end, 2037: escalus:story(Config, [{alice, 2}, {bob, 1}], F). 2038: 2039: muc_protected_message(Config) -> 2040: P = ?config(props, Config), 2041: F = fun(Alice, Bob) -> 2042: Room = ?config(room, Config), 2043: Text = <<"Hi, Bob!">>, 2044: escalus:send(Alice, stanza_muc_enter_room(Room, nick(Alice))), 2045: escalus:send(Bob, stanza_muc_enter_room(Room, nick(Bob))), 2046: 2047: %% Bob received presences. 2048: escalus:wait_for_stanzas(Bob, 2), 2049: 2050: %% Bob received the room's subject. 2051: escalus:wait_for_stanzas(Bob, 1), 2052: 2053: %% Alice sends to Bob, using his occupant JID. 2054: %% This message will not be put into room's history. 2055: Msg = escalus_stanza:chat_to(room_address(Room, nick(Bob)), Text), 2056: escalus:send(Alice, Msg), 2057: 2058: %% Bob received the message "Hi, Bob!". 2059: BobMsg = escalus:wait_for_stanza(Bob), 2060: escalus:assert(is_message, BobMsg), 2061: 2062: BobArchiveAddr = escalus_client:short_jid(Bob), 2063: ArchivedBy = [exml_query:attr(Arc, <<"by">>) 2064: || Arc <- BobMsg#xmlel.children, 2065: Arc#xmlel.name =:= <<"archived">>, 2066: BobArchiveAddr =/= exml_query:attr(Arc, <<"by">>)], 2067: ?assert_equal([], ArchivedBy), 2068: 2069: %% Bob requests the room's archive. 2070: escalus:send(Bob, stanza_to_room(stanza_archive_request(P, <<"q1">>), Room)), 2071: assert_respond_size(0, wait_archive_respond(Bob)), 2072: ok 2073: end, 2074: escalus:story(Config, [{alice, 1}, {bob, 1}], F). 2075: 2076: muc_deny_protected_room_access(Config) -> 2077: P = ?config(props, Config), 2078: F = fun(Alice, Bob) -> 2079: Room = ?config(room, Config), 2080: RoomAddr = room_address(Room), 2081: Text = <<"Hi, Bob!">>, 2082: escalus:send(Alice, stanza_muc_enter_room(Room, nick(Alice))), 2083: escalus:send(Bob, stanza_muc_enter_room(Room, nick(Bob))), 2084: 2085: %% mod_muc returns error presence. 2086: Err1 = escalus:wait_for_stanza(Bob), 2087: escalus_assert:is_error(Err1, <<"auth">>, <<"not-authorized">>), 2088: 2089: %% Alice sends to the chat room. 2090: escalus:send(Alice, escalus_stanza:groupchat_to(RoomAddr, Text)), 2091: 2092: %% Bob requests the room's archive. 2093: escalus:send(Bob, stanza_to_room(stanza_archive_request(P, <<"q1">>), Room)), 2094: Err2 = escalus:wait_for_stanza(Bob), 2095: %% mod_mam_muc returns error iq. 2096: escalus:assert(is_error, [<<"cancel">>, <<"not-allowed">>], Err2), 2097: ok 2098: end, 2099: escalus:story(Config, [{alice, 1}, {bob, 1}], F). 2100: 2101: %% @doc Allow access to non-in-room users who able to connect 2102: muc_allow_access_to_owner(Config) -> 2103: P = ?config(props, Config), 2104: F = fun(Alice, _Bob) -> 2105: Room = ?config(room, Config), 2106: _RoomAddr = room_address(Room), 2107: 2108: %% Alice (not in room) requests the room's archive. 2109: escalus:send(Alice, stanza_to_room(stanza_archive_request(P, <<"q1">>), Room)), 2110: %% mod_mam_muc returns result. 2111: assert_respond_size(0, wait_archive_respond(Alice)), 2112: ok 2113: end, 2114: escalus:story(Config, [{alice, 1}, {bob, 1}], F). 2115: 2116: muc_delete_x_user_in_anon_rooms(Config) -> 2117: escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 2118: {Room, RoomAddr} = enter_room(Config, [Alice, Bob]), 2119: Text = <<"Hi all!">>, 2120: 2121: %% Bob received presences. 2122: escalus:wait_for_stanzas(Bob, 2), 2123: 2124: %% Bob received the room's subject. 2125: escalus:wait_for_stanzas(Bob, 1), 2126: 2127: %% Alice sends to the chat room. 2128: escalus:send(Alice, escalus_stanza:groupchat_to(RoomAddr, Text)), 2129: 2130: %% Bob receives the message. 2131: escalus:assert(is_message, escalus:wait_for_stanza(Bob)), 2132: 2133: maybe_wait_for_archive(Config), 2134: Props = ?config(props, Config), 2135: 2136: %% Bob requests the room's archive. 2137: escalus:send(Bob, stanza_to_room(stanza_archive_request(Props, <<"q1">>), Room)), 2138: 2139: %% mod_mam_muc returns result. 2140: [ArcMsg] = respond_messages(assert_respond_size(1, wait_archive_respond(Bob))), 2141: 2142: ?assert_equal_extra(false, has_x_user_element(ArcMsg), 2143: [{forwarded_message, ArcMsg}]), 2144: ok 2145: end). 2146: 2147: muc_show_x_user_to_moderators_in_anon_rooms(Config) -> 2148: escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 2149: {Room, RoomAddr} = enter_room(Config, [Alice, Bob]), 2150: Text = <<"What a lovely day!">>, 2151: 2152: %% Alice received presences. 2153: escalus:wait_for_stanzas(Alice, 2), 2154: 2155: %% Alice received the room's subject. 2156: escalus:wait_for_stanzas(Alice, 1), 2157: 2158: %% Bob sends to the chat room. 2159: escalus:send(Bob, escalus_stanza:groupchat_to(RoomAddr, Text)), 2160: 2161: %% Alice receives the message. 2162: escalus:assert(is_message, escalus:wait_for_stanza(Alice)), 2163: 2164: maybe_wait_for_archive(Config), 2165: Props = ?config(props, Config), 2166: 2167: %% Alice requests the room's archive. 2168: escalus:send(Alice, stanza_to_room(stanza_archive_request(Props, <<"q1">>), Room)), 2169: 2170: %% mod_mam_muc returns result. 2171: [ArcMsg] = respond_messages(assert_respond_size(1, wait_archive_respond(Alice))), 2172: 2173: ?assert_equal_extra(true, has_x_user_element(ArcMsg), 2174: [{forwarded_message, ArcMsg}]), 2175: ok 2176: end). 2177: 2178: muc_show_x_user_for_your_own_messages_in_anon_rooms(Config) -> 2179: escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 2180: {Room, RoomAddr} = enter_room(Config, [Alice, Bob]), 2181: Text = <<"How are you?">>, 2182: 2183: %% Bob received presences. 2184: escalus:wait_for_stanzas(Bob, 2), 2185: 2186: %% Bob received the room's subject. 2187: escalus:wait_for_stanzas(Bob, 1), 2188: 2189: %% Bob sends to the chat room. 2190: escalus:send(Bob, escalus_stanza:groupchat_to(RoomAddr, Text)), 2191: 2192: %% Bob receives the message. 2193: escalus:assert(is_message, escalus:wait_for_stanza(Bob)), 2194: 2195: maybe_wait_for_archive(Config), 2196: Props = ?config(props, Config), 2197: 2198: %% Bob requests the room's archive. 2199: escalus:send(Bob, stanza_to_room(stanza_archive_request(Props, <<"q1">>), Room)), 2200: 2201: %% mod_mam_muc returns result. 2202: [ArcMsg] = respond_messages(assert_respond_size(1, wait_archive_respond(Bob))), 2203: 2204: ?assert_equal_extra(true, has_x_user_element(ArcMsg), 2205: [{forwarded_message, ArcMsg}]), 2206: ok 2207: end). 2208: 2209: %% @doc Querying the archive for all messages in a certain timespan. 2210: range_archive_request(Config) -> 2211: P = ?config(props, Config), 2212: F = fun(Alice) -> 2213: %% Send 2214: %% <iq type='get'> 2215: %% <query xmlns='urn:xmpp:mam:tmp'> 2216: %% <start>2010-06-07T00:00:00Z</start> 2217: %% <end>2010-07-07T13:23:54Z</end> 2218: %% </query> 2219: %% </iq> 2220: escalus:send(Alice, stanza_date_range_archive_request(P)), 2221: IQ = escalus:wait_for_stanza(Alice, 5000), 2222: escalus:assert(is_iq_result, IQ), 2223: ok 2224: end, 2225: escalus_fresh:story(Config, [{alice, 1}], F). 2226: 2227: range_archive_request_not_empty(Config) -> 2228: P = ?config(props, Config), 2229: F = fun(Alice) -> 2230: Msgs = ?config(pre_generated_msgs, Config), 2231: [_, _, StartMsg, StopMsg | _] = Msgs, 2232: {{StartMsgId, _}, _, _, _, _StartMsgPacket} = StartMsg, 2233: {{StopMsgId, _}, _, _, _, _StopMsgPacket} = StopMsg, 2234: {StartMicro, _} = rpc_apply(mod_mam_utils, decode_compact_uuid, [StartMsgId]), 2235: {StopMicro, _} = rpc_apply(mod_mam_utils, decode_compact_uuid, [StopMsgId]), 2236: StartTime = make_iso_time(StartMicro), 2237: StopTime = make_iso_time(StopMicro), 2238: %% Send 2239: %% <iq type='get'> 2240: %% <query xmlns='urn:xmpp:mam:tmp'> 2241: %% <start>StartTime</start> 2242: %% <end>StopTime</end> 2243: %% </query> 2244: %% </iq> 2245: escalus:send(Alice, stanza_date_range_archive_request_not_empty(P, StartTime, StopTime)), 2246: %% Receive two messages and IQ 2247: Result = wait_archive_respond(Alice), 2248: IQ = respond_iq(Result), 2249: [M1, M2|_] = respond_messages(Result), 2250: escalus:assert(is_iq_result, IQ), 2251: #forwarded_message{delay_stamp=Stamp1} = parse_forwarded_message(M1), 2252: #forwarded_message{delay_stamp=Stamp2} = parse_forwarded_message(M2), 2253: ?assert_equal(list_to_binary(StartTime), Stamp1), 2254: ?assert_equal(list_to_binary(StopTime), Stamp2), 2255: ok 2256: end, 2257: %% Made fresh in init_per_testcase 2258: escalus:story(Config, [{alice, 1}], F). 2259: 2260: %% @doc A query using Result Set Management. 2261: %% See also `#rsm_in.max'. 2262: limit_archive_request(Config) -> 2263: P = ?config(props, Config), 2264: F = fun(Alice) -> 2265: %% Send 2266: %% <iq type='get' id='q29302'> 2267: %% <query xmlns='urn:xmpp:mam:tmp'> 2268: %% <start>2010-08-07T00:00:00Z</start> 2269: %% <set xmlns='http://jabber.org/protocol/rsm'> 2270: %% <limit>10</limit> 2271: %% </set> 2272: %% </query> 2273: %% </iq> 2274: escalus:send(Alice, stanza_limit_archive_request(P)), 2275: Result = wait_archive_respond(Alice), 2276: Msgs = respond_messages(Result), 2277: IQ = respond_iq(Result), 2278: escalus:assert(is_iq_result, IQ), 2279: 10 = length(Msgs), 2280: ok 2281: end, 2282: %% Made fresh in init_per_testcase 2283: escalus:story(Config, [{alice, 1}], F). 2284: 2285: archive_chat_markers(Config) -> 2286: P = ?config(props, Config), 2287: F = fun(Alice, Bob) -> 2288: %% Alice sends markable message to Bob 2289: Message = escalus_stanza:markable( 2290: escalus_stanza:chat_to(Bob, <<"Hello, Bob!">>) 2291: ), 2292: MessageID = escalus_stanza:id(), 2293: escalus:send(Alice, escalus_stanza:set_id(Message, MessageID)), 2294: escalus:wait_for_stanza(Bob), 2295: 2296: %% Bob sends 3 chat markers 2297: Marker1 = escalus_stanza:chat_marker(Alice, <<"received">>, 2298: MessageID), 2299: Marker2 = escalus_stanza:chat_marker(Alice, <<"displayed">>, 2300: MessageID), 2301: Marker3 = escalus_stanza:chat_marker(Alice, <<"acknowledged">>, 2302: MessageID), 2303: escalus:send(Bob, Marker1), 2304: escalus:send(Bob, Marker2), 2305: escalus:send(Bob, Marker3), 2306: escalus:wait_for_stanzas(Alice, 3), 2307: 2308: %% Alice queries MAM 2309: maybe_wait_for_archive(Config), 2310: escalus:send(Alice, stanza_archive_request(P, <<"q1">>)), 2311: Result = wait_archive_respond(Alice), 2312: 2313: %% archived message + 3 markers 2314: assert_respond_size(1 + 3, Result), 2315: assert_respond_query_id(P, <<"q1">>, parse_result_iq(Result)) 2316: end, 2317: escalus:story(Config, [{alice, 1}, {bob, 1}], F). 2318: 2319: dont_archive_chat_markers(Config) -> 2320: P = ?config(props, Config), 2321: F = fun(Alice, Bob) -> 2322: %% Alice sends markable message to Bob 2323: Message = escalus_stanza:markable( 2324: escalus_stanza:chat_to(Bob, <<"Hello, Bob!">>) 2325: ), 2326: MessageID = escalus_stanza:id(), 2327: escalus:send(Alice, escalus_stanza:set_id(Message, MessageID)), 2328: escalus:wait_for_stanza(Bob), 2329: 2330: %% Bob sends 3 chat markers which also contain non-archivable elements 2331: Marker = #xmlel{children = Children} = 2332: escalus_stanza:chat_marker(Alice, <<"received">>, MessageID), 2333: ResultEl = #xmlel{name = <<"result">>}, 2334: DelayEl = #xmlel{name = <<"delay">>}, 2335: NoStoreEl = mam_helper:hint_elem(no_store), 2336: 2337: escalus:send(Bob, Marker#xmlel{children = [ResultEl|Children]}), 2338: escalus:send(Bob, Marker#xmlel{children = [DelayEl|Children]}), 2339: escalus:send(Bob, Marker#xmlel{children = [NoStoreEl|Children]}), 2340: escalus:wait_for_stanzas(Alice, 3), 2341: 2342: %% Alice queries MAM 2343: maybe_wait_for_archive(Config), 2344: escalus:send(Alice, stanza_archive_request(P, <<"q1">>)), 2345: Result = wait_archive_respond(Alice), 2346: 2347: %% archived message (no archived markers) 2348: assert_respond_size(1, Result), 2349: assert_respond_query_id(P, <<"q1">>, parse_result_iq(Result)) 2350: end, 2351: escalus:story(Config, [{alice, 1}, {bob, 1}], F). 2352: 2353: pagination_empty_rset(Config) -> 2354: P = ?config(props, Config), 2355: F = fun(Alice) -> 2356: %% Get the first page of size 5. 2357: RSM = #rsm_in{max=0}, 2358: 2359: rsm_send(Config, Alice, 2360: stanza_page_archive_request(P, <<"empty_rset">>, RSM)), 2361: wait_empty_rset(Alice, 15) 2362: end, 2363: parallel_story(Config, [{alice, 1}], F). 2364: 2365: pagination_first5(Config) -> 2366: P = ?config(props, Config), 2367: F = fun(Alice) -> 2368: %% Get the first page of size 5. 2369: RSM = #rsm_in{max=5}, 2370: rsm_send(Config, Alice, 2371: stanza_page_archive_request(P, <<"first5">>, RSM)), 2372: wait_message_range(Alice, 1, 5), 2373: ok 2374: end, 2375: parallel_story(Config, [{alice, 1}], F). 2376: 2377: pagination_first0(Config) -> 2378: P = ?config(props, Config), 2379: F = fun(Alice) -> 2380: %% Get the first page of size 0. 2381: RSM = #rsm_in{max=0}, 2382: rsm_send(Config, Alice, 2383: stanza_page_archive_request(P, <<"first5">>, RSM)), 2384: wait_empty_rset(Alice, 15), 2385: ok 2386: end, 2387: parallel_story(Config, [{alice, 1}], F). 2388: 2389: pagination_first5_opt_count(Config) -> 2390: P = ?config(props, Config), 2391: F = fun(Alice) -> 2392: %% Get the first page of size 5. 2393: RSM = #rsm_in{max=5}, 2394: rsm_send(Config, Alice, 2395: stanza_page_archive_request(P, <<"first5_opt">>, RSM)), 2396: wait_message_range(Alice, 1, 5), 2397: ok 2398: end, 2399: parallel_story(Config, [{alice, 1}], F). 2400: 2401: pagination_first25_opt_count_all(Config) -> 2402: P = ?config(props, Config), 2403: F = fun(Alice) -> 2404: %% Get the first page of size 25. 2405: RSM = #rsm_in{max=25}, 2406: rsm_send(Config, Alice, 2407: stanza_page_archive_request(P, <<"first25_opt_all">>, RSM)), 2408: wait_message_range(Alice, 1, 15), 2409: ok 2410: end, 2411: parallel_story(Config, [{alice, 1}], F). 2412: 2413: pagination_last5(Config) -> 2414: P = ?config(props, Config), 2415: F = fun(Alice) -> 2416: %% Get the last page of size 5. 2417: RSM = #rsm_in{max=5, direction=before}, 2418: rsm_send(Config, Alice, 2419: stanza_page_archive_request(P, <<"last5">>, RSM)), 2420: wait_message_range(Alice, 11, 15), 2421: ok 2422: end, 2423: parallel_story(Config, [{alice, 1}], F). 2424: 2425: pagination_last0(Config) -> 2426: P = ?config(props, Config), 2427: F = fun(Alice) -> 2428: %% Get the last page of size 0. 2429: RSM = #rsm_in{max=0, direction=before}, 2430: rsm_send(Config, Alice, 2431: stanza_page_archive_request(P, <<"last0">>, RSM)), 2432: wait_empty_rset(Alice, 15), 2433: ok 2434: end, 2435: parallel_story(Config, [{alice, 1}], F). 2436: 2437: pagination_offset5(Config) -> 2438: P = ?config(props, Config), 2439: F = fun(Alice) -> 2440: %% Skip 5 messages, get 5 messages. 2441: RSM = #rsm_in{max=5, index=5}, 2442: rsm_send(Config, Alice, 2443: stanza_page_archive_request(P, <<"offset5">>, RSM)), 2444: wait_message_range(Alice, 6, 10), 2445: ok 2446: end, 2447: parallel_story(Config, [{alice, 1}], F). 2448: 2449: pagination_offset5_max0(Config) -> 2450: P = ?config(props, Config), 2451: F = fun(Alice) -> 2452: %% Skip 5 messages, get 0 messages. 2453: RSM = #rsm_in{max=0, index=5}, 2454: rsm_send(Config, Alice, 2455: stanza_page_archive_request(P, <<"offset0_max5">>, RSM)), 2456: wait_empty_rset(Alice, 15), 2457: ok 2458: end, 2459: parallel_story(Config, [{alice, 1}], F). 2460: 2461: pagination_last5_opt_count(Config) -> 2462: P = ?config(props, Config), 2463: F = fun(Alice) -> 2464: %% Get the last page of size 5. 2465: RSM = #rsm_in{max=5, direction=before, opt_count=true}, 2466: rsm_send(Config, Alice, 2467: stanza_page_archive_request(P, <<"last5_opt">>, RSM)), 2468: wait_message_range(Alice, 11, 15), 2469: ok 2470: end, 2471: parallel_story(Config, [{alice, 1}], F). 2472: 2473: pagination_last25_opt_count_all(Config) -> 2474: P = ?config(props, Config), 2475: F = fun(Alice) -> 2476: %% Get the last page of size 25. 2477: RSM = #rsm_in{max=25, direction=before, opt_count=true}, 2478: rsm_send(Config, Alice, 2479: stanza_page_archive_request(P, <<"last25_opt_all">>, RSM)), 2480: wait_message_range(Alice, 1, 15), 2481: ok 2482: end, 2483: parallel_story(Config, [{alice, 1}], F). 2484: 2485: pagination_offset5_opt_count(Config) -> 2486: P = ?config(props, Config), 2487: F = fun(Alice) -> 2488: %% Skip 5 messages, get 5 messages. 2489: RSM = #rsm_in{max=5, index=5, opt_count=true}, 2490: rsm_send(Config, Alice, 2491: stanza_page_archive_request(P, <<"last5_opt">>, RSM)), 2492: wait_message_range(Alice, 6, 10), 2493: ok 2494: end, 2495: parallel_story(Config, [{alice, 1}], F). 2496: 2497: pagination_offset5_opt_count_all(Config) -> 2498: P = ?config(props, Config), 2499: F = fun(Alice) -> 2500: %% Skip 5 messages, get 25 messages (only 10 are available). 2501: RSM = #rsm_in{max=25, index=5, opt_count=true}, 2502: rsm_send(Config, Alice, 2503: stanza_page_archive_request(P, <<"last5_opt_all">>, RSM)), 2504: wait_message_range(Alice, 6, 15), 2505: ok 2506: end, 2507: parallel_story(Config, [{alice, 1}], F). 2508: 2509: 2510: pagination_before10(Config) -> 2511: P = ?config(props, Config), 2512: F = fun(Alice) -> 2513: %% Get the last page of size 5. 2514: RSM = #rsm_in{max=5, direction=before, id=message_id(10, Config)}, 2515: rsm_send(Config, Alice, 2516: stanza_page_archive_request(P, <<"before10">>, RSM)), 2517: wait_message_range(Alice, 5, 9), 2518: ok 2519: end, 2520: parallel_story(Config, [{alice, 1}], F). 2521: 2522: pagination_simple_before10(Config) -> 2523: P = ?config(props, Config), 2524: F = fun(Alice) -> 2525: %% Get the last page of size 5. 2526: RSM = #rsm_in{max=5, direction=before, id=message_id(10, Config), simple=true}, 2527: rsm_send(Config, Alice, 2528: stanza_page_archive_request(P, <<"before10">>, RSM)), 2529: %% wait_message_range(Client, TotalCount, Offset, FromN, ToN), 2530: wait_message_range(Alice, undefined, undefined, 5, 9), 2531: ok 2532: end, 2533: parallel_story(Config, [{alice, 1}], F). 2534: 2535: pagination_after10(Config) -> 2536: P = ?config(props, Config), 2537: F = fun(Alice) -> 2538: %% Get the last page of size 5. 2539: RSM = #rsm_in{max=5, direction='after', id=message_id(10, Config)}, 2540: rsm_send(Config, Alice, 2541: stanza_page_archive_request(P, <<"after10">>, RSM)), 2542: wait_message_range(Alice, 11, 15), 2543: ok 2544: end, 2545: parallel_story(Config, [{alice, 1}], F). 2546: 2547: %% Select first page of recent messages after last known id. 2548: %% Paginating from newest messages to oldest ones. 2549: pagination_last_after_id5(Config) -> 2550: P = ?config(props, Config), 2551: F = fun(Alice) -> 2552: %% Get the last page of size 5 after 5-th message. 2553: RSM = #rsm_in{max=5, direction='before', 2554: after_id=message_id(5, Config)}, 2555: rsm_send(Config, Alice, 2556: stanza_page_archive_request(P, <<"last_after_id5">>, RSM)), 2557: %% wait_message_range(Client, TotalCount, Offset, FromN, ToN), 2558: wait_message_range(Alice, 10, 5, 11, 15), 2559: ok 2560: end, 2561: parallel_story(Config, [{alice, 1}], F). 2562: 2563: %% Select second page of recent messages after last known id. 2564: pagination_last_after_id5_before_id11(Config) -> 2565: P = ?config(props, Config), 2566: F = fun(Alice) -> 2567: RSM = #rsm_in{max=5, direction='before', 2568: after_id=message_id(5, Config), 2569: before_id=message_id(11, Config)}, 2570: rsm_send(Config, Alice, 2571: stanza_page_archive_request(P, <<"last_after_id5_before_id11">>, RSM)), 2572: %% wait_message_range(Client, TotalCount, Offset, FromN, ToN), 2573: wait_message_range(Alice, 5, 0, 6, 10), 2574: ok 2575: end, 2576: parallel_story(Config, [{alice, 1}], F). 2577: 2578: server_returns_item_not_found_for_before_filter_with_nonexistent_id(Config) -> 2579: NonexistentID = <<"AV25E9SCO50K">>, 2580: RSM = #rsm_in{max = 5, direction = 'before', id = NonexistentID}, 2581: StanzaID = <<"before-nonexistent-id">>, 2582: Condition = [<<"cancel">>, <<"item-not-found">>], 2583: server_returns_item_not_found_for_nonexistent_id(Config, RSM, StanzaID, Condition). 2584: 2585: server_returns_item_not_found_for_after_filter_with_nonexistent_id(Config) -> 2586: NonexistentID = <<"AV25E9SCO50K">>, 2587: RSM = #rsm_in{max = 5, direction = 'after', id = NonexistentID}, 2588: StanzaID = <<"after-nonexistent-id">>, 2589: Condition = [<<"cancel">>, <<"item-not-found">>], 2590: server_returns_item_not_found_for_nonexistent_id(Config, RSM, StanzaID, Condition). 2591: 2592: server_returns_item_not_found_for_after_filter_with_invalid_id(Config) -> 2593: NonexistentID = <<"bef3a242-99ce-402a-9ffc-2f3c20da92d4">>, 2594: RSM = #rsm_in{max = 5, direction = 'after', from_id = NonexistentID}, 2595: StanzaID = <<"AV25E9SCO50K">>, 2596: Condition = [<<"modify">>, <<"not-acceptable">>], 2597: server_returns_item_not_found_for_nonexistent_id(Config, RSM, StanzaID, Condition). 2598: 2599: server_returns_item_not_found_for_nonexistent_id(Config, RSM, StanzaID, Condition) -> 2600: P = ?config(props, Config), 2601: F = fun(Alice) -> 2602: IQ = stanza_page_archive_request(P, StanzaID, RSM), 2603: rsm_send(Config, Alice, IQ), 2604: Res = escalus:wait_for_stanza(Alice), 2605: escalus:assert(is_iq_error, [IQ], Res), 2606: escalus:assert(is_error, Condition, Res), 2607: ok 2608: end, 2609: parallel_story(Config, [{alice, 1}], F). 2610: 2611: 2612: %% Test cases for "complete" attribute 2613: %% Complete attribute is used for pagination, telling when to stop paginating. 2614: %% see complete_flag_cases with the whole list of the cases. 2615: %% ----------------------------------------------- 2616: 2617: %% Get last page with most recent messages 2618: %% rsm_id.id is undefined 2619: %% GIVEN 15 archived messages 2620: %% WHEN direction=before, page_size=5 2621: %% THEN complete=false 2622: before_complete_false_last5(Config) -> 2623: P = ?config(props, Config), 2624: F = fun(Alice) -> 2625: %% Get the last page of size 5. 2626: %% Get messages: 11,12,13,14,15 2627: RSM = #rsm_in{max=5, direction=before}, 2628: rsm_send(Config, Alice, 2629: stanza_page_archive_request(P, <<"last5">>, RSM)), 2630: wait_for_complete_archive_response(P, Alice, <<"false">>) 2631: end, 2632: parallel_story(Config, [{alice, 1}], F). 2633: 2634: %% Gets some page in the midle of the result set 2635: %% GIVEN 15 archived messages 2636: %% WHEN direction=before, rsm_id=10, page_size=5 2637: %% THEN complete=false 2638: before_complete_false_before10(Config) -> 2639: P = ?config(props, Config), 2640: F = fun(Alice) -> 2641: %% Get messages: 5,6,7,8,9 2642: RSM = #rsm_in{max=5, direction=before, id=message_id(10, Config)}, 2643: rsm_send(Config, Alice, 2644: stanza_page_archive_request(P, <<"before10">>, RSM)), 2645: wait_for_complete_archive_response(P, Alice, <<"false">>) 2646: end, 2647: parallel_story(Config, [{alice, 1}], F). 2648: 2649: %% Reaches the end of result set 2650: %% No messages are returned. 2651: %% GIVEN 15 archived messages 2652: %% WHEN direction=before, rsm_id=1, page_size=5 2653: %% THEN complete=true 2654: before_complete_true_before1(Config) -> 2655: P = ?config(props, Config), 2656: F = fun(Alice) -> 2657: %% Get no messages 2658: RSM = #rsm_in{max=5, direction=before, id=message_id(1, Config)}, 2659: rsm_send(Config, Alice, 2660: stanza_page_archive_request(P, <<"before1">>, RSM)), 2661: wait_for_complete_archive_response(P, Alice, <<"true">>) 2662: end, 2663: parallel_story(Config, [{alice, 1}], F). 2664: 2665: %% Reaches the end of result set 2666: %% Less than maximum number of messages are returned. 2667: %% GIVEN 15 archived messages 2668: %% WHEN direction=before, rsm_id=5, page_size=5 2669: %% THEN complete=true 2670: before_complete_true_before5(Config) -> 2671: P = ?config(props, Config), 2672: F = fun(Alice) -> 2673: %% Get messages: 1,2,3,4 2674: RSM = #rsm_in{max=5, direction=before, id=message_id(5, Config)}, 2675: rsm_send(Config, Alice, 2676: stanza_page_archive_request(P, <<"before5">>, RSM)), 2677: wait_for_complete_archive_response(P, Alice, <<"true">>) 2678: end, 2679: parallel_story(Config, [{alice, 1}], F). 2680: 2681: %% Reaches the end of result set 2682: %% A special case when exactly maximum number of messages are returned. 2683: %% GIVEN 15 archived messages 2684: %% WHEN direction=before, rsm_id=6, page_size=5 2685: %% THEN complete=true 2686: before_complete_true_before6(Config) -> 2687: P = ?config(props, Config), 2688: F = fun(Alice) -> 2689: %% Get messages: 1,2,3,4,5 2690: RSM = #rsm_in{max=5, direction=before, id=message_id(6, Config)}, 2691: rsm_send(Config, Alice, 2692: stanza_page_archive_request(P, <<"before6">>, RSM)), 2693: wait_for_complete_archive_response(P, Alice, <<"true">>) 2694: end, 2695: parallel_story(Config, [{alice, 1}], F). 2696: 2697: %% First page is not complete, because max is smaller than archive size. 2698: %% rsm_id.id is undefined 2699: %% GIVEN 15 archived messages 2700: %% WHEN direction=after, rsm_id=6, page_size=5 2701: %% THEN complete=false 2702: after_complete_false_first_page(Config) -> 2703: P = ?config(props, Config), 2704: F = fun(Alice) -> 2705: %% Get messages: 1,2,3,4,5 2706: RSM = #rsm_in{max=5, direction='after'}, 2707: rsm_send(Config, Alice, 2708: stanza_page_archive_request(P, <<"firstpage">>, RSM)), 2709: wait_for_complete_archive_response(P, Alice, <<"false">>) 2710: end, 2711: parallel_story(Config, [{alice, 1}], F). 2712: 2713: %% There are still 8-15 messages to paginate after this request. 2714: %% GIVEN 15 archived messages 2715: %% WHEN direction=after, rsm_id=2, page_size=5 2716: %% THEN complete=false 2717: after_complete_false_after2(Config) -> 2718: P = ?config(props, Config), 2719: F = fun(Alice) -> 2720: %% Get messages: 3,4,5,6,7 2721: RSM = #rsm_in{max=5, direction='after', id=message_id(2, Config)}, 2722: rsm_send(Config, Alice, 2723: stanza_page_archive_request(P, <<"after2">>, RSM)), 2724: wait_for_complete_archive_response(P, Alice, <<"false">>) 2725: end, 2726: parallel_story(Config, [{alice, 1}], F). 2727: 2728: %% There is still one message to paginate after this request. 2729: %% GIVEN 15 archived messages 2730: %% WHEN direction=after, rsm_id=9, page_size=5 2731: %% THEN complete=false 2732: after_complete_false_after9(Config) -> 2733: P = ?config(props, Config), 2734: F = fun(Alice) -> 2735: %% Get messages: 10,11,12,13,14 2736: RSM = #rsm_in{max=5, direction='after', id=message_id(9, Config)}, 2737: rsm_send(Config, Alice, 2738: stanza_page_archive_request(P, <<"after9">>, RSM)), 2739: wait_for_complete_archive_response(P, Alice, <<"false">>) 2740: end, 2741: parallel_story(Config, [{alice, 1}], F). 2742: 2743: %% There are no messages to paginate after this request. 2744: %% Special case, when exactly page_size messages are returned. 2745: %% GIVEN 15 archived messages 2746: %% WHEN direction=after, rsm_id=10, page_size=5 2747: %% THEN complete=true 2748: after_complete_true_after10(Config) -> 2749: P = ?config(props, Config), 2750: F = fun(Alice) -> 2751: %% Get the last page of size 5. 2752: %% Get messages: 11,12,13,14,15 2753: RSM = #rsm_in{max=5, direction='after', id=message_id(10, Config)}, 2754: rsm_send(Config, Alice, 2755: stanza_page_archive_request(P, <<"after10">>, RSM)), 2756: wait_for_complete_archive_response(P, Alice, <<"true">>) 2757: end, 2758: parallel_story(Config, [{alice, 1}], F). 2759: 2760: %% There are no messages to paginate after this request. 2761: %% Less than page_size are returned. 2762: %% GIVEN 15 archived messages 2763: %% WHEN direction=after, rsm_id=10, page_size=5 2764: %% THEN complete=true 2765: after_complete_true_after11(Config) -> 2766: P = ?config(props, Config), 2767: F = fun(Alice) -> 2768: %% Get the last page of size 5. 2769: %% Get messages: 12,13,14,15 2770: RSM = #rsm_in{max=5, direction='after', id=message_id(11, Config)}, 2771: rsm_send(Config, Alice, 2772: stanza_page_archive_request(P, <<"after11">>, RSM)), 2773: wait_for_complete_archive_response(P, Alice, <<"true">>) 2774: end, 2775: parallel_story(Config, [{alice, 1}], F). 2776: 2777: 2778: %% Test cases for preferences IQs 2779: %% ------------------------------------------------------------------ 2780: 2781: prefs_set_request(Config) -> 2782: _P = ?config(props, Config), 2783: F = fun(Alice) -> 2784: %% Send 2785: %% 2786: %% <iq type='set' id='juliet2'> 2787: %% <prefs xmlns='urn:xmpp:mam:tmp' default="roster"> 2788: %% <always> 2789: %% <jid>romeo@montague.net</jid> 2790: %% </always> 2791: %% <never> 2792: %% <jid>montague@montague.net</jid> 2793: %% </never> 2794: %% </prefs> 2795: %% </iq> 2796: escalus:send(Alice, stanza_prefs_set_request(<<"roster">>, 2797: [<<"romeo@montague.net">>], 2798: [<<"montague@montague.net">>], 2799: mam_ns_binary())), 2800: ReplySet = escalus:wait_for_stanza(Alice), 2801: 2802: escalus:send(Alice, stanza_prefs_get_request(mam_ns_binary())), 2803: ReplyGet = escalus:wait_for_stanza(Alice), 2804: 2805: ResultIQ1 = parse_prefs_result_iq(ReplySet), 2806: ResultIQ2 = parse_prefs_result_iq(ReplyGet), 2807: ?assert_equal(ResultIQ1, ResultIQ2), 2808: ok 2809: end, 2810: escalus:story(Config, [{alice, 1}], F). 2811: 2812: query_get_request(Config) -> 2813: F = fun(Alice) -> 2814: QueryXmlns = mam_ns_binary_v04(), 2815: escalus:send(Alice, stanza_query_get_request(QueryXmlns)), 2816: ReplyFields = escalus:wait_for_stanza(Alice), 2817: ResponseXmlns = exml_query:path(ReplyFields, 2818: [{element, <<"query">>}, 2819: {element, <<"x">>}, 2820: {element, <<"field">>}, 2821: {element, <<"value">>}, 2822: cdata]), 2823: ?assert_equal(QueryXmlns, ResponseXmlns) 2824: end, 2825: escalus_fresh:story(Config, [{alice, 1}], F). 2826: 2827: %% Test reproducing https://github.com/esl/MongooseIM/issues/263 2828: %% The idea is this: in a "perfect" world jid elements are put together 2829: %% without whitespaces. In the real world it is not true. 2830: %% Put "\n" between two jid elements. 2831: prefs_set_cdata_request(Config) -> 2832: _P = ?config(props, Config), 2833: F = fun(Alice) -> 2834: %% Send 2835: %% 2836: %% <iq type='set' id='juliet2'> 2837: %% <prefs xmlns='urn:xmpp:mam:tmp' default="roster"> 2838: %% <always> 2839: %% <jid>romeo@montague.net</jid> 2840: %% <jid>montague@montague.net</jid> 2841: %% </always> 2842: %% </prefs> 2843: %% </iq> 2844: escalus:send(Alice, stanza_prefs_set_request(<<"roster">>, 2845: [<<"romeo@montague.net">>, 2846: {xmlcdata, <<"\n">>}, %% Put as it is 2847: <<"montague@montague.net">>], [], 2848: mam_ns_binary_v04())), 2849: ReplySet = escalus:wait_for_stanza(Alice), 2850: 2851: escalus:send(Alice, stanza_prefs_get_request(mam_ns_binary_v04())), 2852: ReplyGet = escalus:wait_for_stanza(Alice), 2853: 2854: ResultIQ1 = parse_prefs_result_iq(ReplySet), 2855: ResultIQ2 = parse_prefs_result_iq(ReplyGet), 2856: ?assert_equal(ResultIQ1, ResultIQ2), 2857: ok 2858: end, 2859: escalus_fresh:story(Config, [{alice, 1}], F). 2860: 2861: mam_service_discovery(Config) -> 2862: _P = ?config(props, Config), 2863: F = fun(Alice) -> 2864: Server = escalus_client:server(Alice), 2865: discover_features(Config, Alice, Server) 2866: end, 2867: escalus_fresh:story(Config, [{alice, 1}], F). 2868: 2869: %% Check, that MUC is supported. 2870: muc_service_discovery(Config) -> 2871: _P = ?config(props, Config), 2872: F = fun(Alice) -> 2873: Domain = domain(), 2874: Server = escalus_client:server(Alice), 2875: escalus:send(Alice, escalus_stanza:service_discovery(Server)), 2876: Stanza = escalus:wait_for_stanza(Alice), 2877: escalus:assert(has_service, [muc_host()], Stanza), 2878: escalus:assert(is_stanza_from, [Domain], Stanza), 2879: 2880: discover_features(Config, Alice, muc_host()) 2881: end, 2882: escalus:fresh_story(Config, [{alice, 1}], F). 2883: 2884: discover_features(Config, Client, Service) -> 2885: escalus:send(Client, escalus_stanza:disco_info(Service)), 2886: Stanza = escalus:wait_for_stanza(Client), 2887: escalus:assert(is_iq_result, Stanza), 2888: escalus:assert(has_feature, [mam_ns_binary_v04()], Stanza), 2889: escalus:assert(has_feature, [mam_ns_binary_v06()], Stanza), 2890: escalus:assert(has_feature, [retract_ns()], Stanza), 2891: ?assert_equal(message_retraction_is_enabled(Config), 2892: escalus_pred:has_feature(retract_tombstone_ns(), Stanza)). 2893: 2894: metric_incremented_on_archive_request(ConfigIn) -> 2895: P = ?config(props, ConfigIn), 2896: F = fun(Alice) -> 2897: escalus:send(Alice, stanza_archive_request(P, <<"metric_q1">>)), 2898: Res = wait_archive_respond(Alice), 2899: assert_respond_size(0, Res), 2900: assert_respond_query_id(P, <<"metric_q1">>, parse_result_iq(Res)), 2901: ok 2902: end, 2903: HostType = domain_helper:host_type(mim), 2904: HostTypePrefix = domain_helper:make_metrics_prefix(HostType), 2905: MongooseMetrics = [{[HostTypePrefix, backends, mod_mam, lookup], changed}], 2906: Config = [{mongoose_metrics, MongooseMetrics} | ConfigIn], 2907: escalus_fresh:story(Config, [{alice, 1}], F). 2908: 2909: metrics_incremented_for_async_pools(Config) -> 2910: OldValue = get_mongoose_async_metrics(), 2911: archived(Config), 2912: Validator = fun(NewValue) -> OldValue =/= NewValue end, 2913: mongoose_helper:wait_until( 2914: fun get_mongoose_async_metrics/0, 2915: true, #{validator => Validator, name => ?FUNCTION_NAME}). 2916: 2917: get_mongoose_async_metrics() -> 2918: HostType = domain_helper:host_type(mim), 2919: HostTypePrefix = domain_helper:make_metrics_prefix(HostType), 2920: #{batch_flushes => get_mongoose_async_metrics(HostTypePrefix, batch_flushes), 2921: timed_flushes => get_mongoose_async_metrics(HostTypePrefix, timed_flushes)}. 2922: 2923: get_mongoose_async_metrics(HostTypePrefix, MetricName) -> 2924: Metric = [HostTypePrefix, mongoose_async_pools, pm_mam, MetricName], 2925: {ok, Value} = rpc(mim(), mongoose_metrics, get_metric_value, [Metric]), 2926: {value, Count} = lists:keyfind(value, 1, Value), 2927: Count. 2928: 2929: metric_incremented_when_store_message(Config) -> 2930: archived(Config). 2931: 2932: messages_filtered_when_prefs_default_policy_is_always(Config) -> 2933: run_prefs_cases(always, Config). 2934: 2935: messages_filtered_when_prefs_default_policy_is_never(Config) -> 2936: run_prefs_cases(never, Config). 2937: 2938: messages_filtered_when_prefs_default_policy_is_roster(Config) -> 2939: run_prefs_cases(roster, Config). 2940: 2941: 2942: -spec enter_room(Config :: proplists:proplist(), [User :: term()]) -> 2943: {Room :: binary(), RoomAddr :: binary()}. 2944: enter_room(Config, Users) -> 2945: Room = ?config(room, Config), 2946: RoomAddr = room_address(Room), 2947: [escalus:send(User, stanza_muc_enter_room(Room, nick(User))) || User <- Users], 2948: {Room, RoomAddr}. 2949: 2950: %% First write all messages, than read and check 2951: run_prefs_cases(DefaultPolicy, ConfigIn) -> 2952: P = ?config(props, ConfigIn), 2953: F = fun(Config, Alice, Bob, Kate) -> 2954: make_alice_and_bob_friends(Alice, Bob), 2955: %% Just send messages for each prefs configuration 2956: Namespace = mam_ns_binary_v04(), 2957: Funs = [run_prefs_case(Case, Namespace, Alice, Bob, Kate, Config) 2958: || Case <- prefs_cases2(), 2959: default_policy(Case) =:= DefaultPolicy], 2960: 2961: maybe_wait_for_archive(Config), 2962: 2963: %% Get ALL messages using several queries if required 2964: Stanzas = get_all_messages(P, Alice), 2965: ParsedMessages = parse_messages(Stanzas), 2966: Bodies = [B || #forwarded_message{message_body=B} <- ParsedMessages], 2967: 2968: %% Check messages, print out all failed cases 2969: Fails = lists:append([Fun(Bodies) || Fun <- Funs]), 2970: %% If fails consult with ct:pal/2 why 2971: ?assert_equal([], Fails) 2972: end, 2973: escalus_fresh:story_with_config(ConfigIn, [{alice, 1}, {bob, 1}, {kate, 1}], F). 2974: 2975: %% The same as prefs_set_request case but for different configurations 2976: run_set_and_get_prefs_cases(ConfigIn) -> 2977: _P = ?config(props, ConfigIn), 2978: F = fun(Config, Alice, _Bob, _Kate) -> 2979: Namespace = mam_ns_binary_v04(), 2980: [run_set_and_get_prefs_case(Case, Namespace, Alice, Config) || Case <- prefs_cases2()] 2981: end, 2982: escalus_fresh:story_with_config(ConfigIn, [{alice, 1}, {bob, 1}, {kate, 1}], F). 2983: 2984: %% MAM's implementation specific test 2985: check_user_exist(Config) -> 2986: %% when 2987: [{_, AdminSpec}] = escalus_users:get_users([admin]), 2988: [AdminU, AdminS, AdminP] = escalus_users:get_usp(Config, AdminSpec), 2989: JID = mongoose_helper:make_jid(AdminU, AdminS), 2990: ok = rpc(mim(), ejabberd_auth, try_register, [JID, AdminP]), 2991: %% admin user already registered 2992: {ok, HostType} = rpc(mim(), mongoose_domain_core, get_host_type, [AdminS]), 2993: true = rpc(mim(), ejabberd_auth, does_user_exist, 2994: [HostType, JID, stored]), 2995: false = rpc(mim(), ejabberd_auth, does_user_exist, 2996: [HostType, mongoose_helper:make_jid(<<"fake-user">>, AdminS), stored]), 2997: false = rpc(mim(), ejabberd_auth, does_user_exist, 2998: [HostType, mongoose_helper:make_jid(AdminU, <<"fake-domain">>), stored]), 2999: %% cleanup 3000: ok = rpc(mim(), ejabberd_auth, remove_user, [JID]). 3001: 3002: %% This function supports only one device, one user. 3003: %% We don't send initial presence to avoid presence broadcasts between resources 3004: %% of the same user from different stories. 3005: %% It is limited comparing to escalus story, but reduces CPU usage, because we don't 3006: %% need to send any presences. 3007: parallel_story(Config, [{_, 1}] = ResourceCounts, F) -> 3008: Config1 = override_for_parallel(Config), 3009: escalus:story(Config1, ResourceCounts, F). 3010: 3011: override_for_parallel(Config) -> 3012: Overrides = [ 3013: {start_ready_clients, start_ready_clients()} 3014: ], 3015: [{escalus_overrides, Overrides} | Config]. 3016: 3017: start_ready_clients() -> 3018: fun(Config, [{UserSpec, BaseResource}]) -> 3019: Suffix = list_to_binary(pid_to_list(self()) -- "<>"), 3020: Resource = <<BaseResource/binary, Suffix/binary>>, 3021: {ok, Client} = escalus_client:start(Config, UserSpec, Resource), 3022: [Client] 3023: end. 3024: 3025: text_search_messages() -> 3026: [ 3027: <<"Tongue chicken jowl hamburger duis exercitation.">>, 3028: <<"Ribs eu aliquip pork veniam dolor jowl id laborum in frankfurter culpa ribs.">>, 3029: <<"Fatback ut labore pariatur, eiusmod esse dolore turducken jowl exercitation ", 3030: "shankle shoulder.">>, 3031: <<"Kevin ribeye short ribs, nostrud short loin quis voluptate cow. Do brisket eu ", 3032: "sunt tail ullamco cow in bacon burgdoggen.">>, 3033: <<"Occaecat in voluptate incididunt aliqua dolor bacon salami anim picanha pork ", 3034: "reprehenderit pancetta tail.">>, 3035: <<"Nisi shank doner dolore officia ribeye. Proident shankle tenderloin consequat ", 3036: "bresaola quis tongue ut sirloin pork chop pariatur fatback ex cupidatat venison.">>, 3037: <<"Brisket in pastrami dolore cupidatat. Est corned beef ad ribeye ball tip aliqua ", 3038: "cupidatat andouille cillum et consequat leberkas.">>, 3039: <<"Qui mollit short ribs, capicola bresaola pork meatloaf kielbasa und culpa.">>, 3040: <<"Meatloaf esse jowl do ham hock consequat. Duis laboris ribeye ullamco, sed elit ", 3041: "porchetta sirloin.">>, 3042: <<"In boudin ad et salami exercitation sausage flank strip steak ball tip dolore ", 3043: "pig officia.">>, 3044: <<"Spare ribs landjaeger pork belly, chuck aliquip turducken beef culpa nostrud.">> 3045: ]. 3046: 3047: %% --------- MUC Light stories helpers ---------- 3048: 3049: when_pm_message_is_sent(Sender, Receiver, Body) -> 3050: escalus:send(Sender, escalus_stanza:chat_to(Receiver, Body)). 3051: 3052: then_pm_message_is_received(Receiver, Body) -> 3053: escalus:assert(is_chat_message, [Body], escalus:wait_for_stanza(Receiver)). 3054: 3055: %% Message retraction helpers 3056: 3057: check_archive_after_retraction(Config, Client, ApplyToElement, Body) -> 3058: case message_should_be_retracted(Config) of 3059: true -> expect_tombstone_and_retraction_message(Client, ApplyToElement); 3060: false -> expect_original_and_retraction_message(Client, ApplyToElement, Body); 3061: ignore -> expect_only_original_message(Client, Body) 3062: end. 3063: 3064: message_should_be_retracted(Config) -> 3065: message_retraction_is_enabled(Config) andalso retraction_requested(Config). 3066: 3067: retraction_requested(Config) -> 3068: OriginId = origin_id(), 3069: case lists:keyfind(retract_on, 1, Config) of 3070: {retract_on, none} -> ignore; 3071: {retract_on, stanza_id} -> true; 3072: {retract_on, {origin_id, OriginId}} -> true; 3073: _ -> false 3074: end. 3075: 3076: message_retraction_is_enabled(Config) -> 3077: BasicGroup = ?config(basic_group, Config), 3078: BasicGroup =/= disabled_retraction andalso BasicGroup =/= muc_disabled_retraction. 3079: 3080: expect_tombstone_and_retraction_message(Client, ApplyToElement) -> 3081: [ArcMsg1, ArcMsg2] = respond_messages(assert_respond_size(2, wait_archive_respond(Client))), 3082: #forwarded_message{message_body = undefined, 3083: message_children = [#xmlel{name = <<"retracted">>}]} = parse_forwarded_message(ArcMsg1), 3084: #forwarded_message{message_body = undefined, 3085: message_children = [ApplyToElement]} = parse_forwarded_message(ArcMsg2). 3086: 3087: expect_original_and_retraction_message(Client, ApplyToElement, Body) -> 3088: [ArcMsg1, ArcMsg2] = respond_messages(assert_respond_size(2, wait_archive_respond(Client))), 3089: #forwarded_message{message_body = Body} = parse_forwarded_message(ArcMsg1), 3090: #forwarded_message{message_body = undefined, 3091: message_children = [ApplyToElement]} = parse_forwarded_message(ArcMsg2). 3092: 3093: expect_only_original_message(Client, Body) -> 3094: [ArcMsg1] = respond_messages(assert_respond_size(1, wait_archive_respond(Client))), 3095: #forwarded_message{message_body = Body} = parse_forwarded_message(ArcMsg1). 3096: 3097: retraction_message(Type, To, ApplyToElement) -> 3098: #xmlel{name = <<"message">>, 3099: attrs = [{<<"type">>, Type}, 3100: {<<"to">>, To}], 3101: children = [ApplyToElement]}. 3102: 3103: origin_id_element(OriginId) -> 3104: #xmlel{name = <<"origin-id">>, 3105: attrs = [{<<"xmlns">>, <<"urn:xmpp:sid:0">>}, 3106: {<<"id">>, OriginId}]}. 3107: 3108: apply_to_element(Config, Copy) -> 3109: {RetractOn, Id} = case ?config(retract_on, Config) of 3110: {origin_id, OrigId} -> {origin_id, OrigId}; 3111: stanza_id -> {stanza_id, stanza_id_from_msg(Copy)}; 3112: none -> {origin_id, none} 3113: end, 3114: #xmlel{name = <<"apply-to">>, 3115: attrs = [{<<"xmlns">>, <<"urn:xmpp:fasten:0">>} | maybe_append_id(Id)], 3116: children = [retract_element(RetractOn)] 3117: }. 3118: 3119: maybe_append_id(none) -> 3120: []; 3121: maybe_append_id(Id) -> 3122: [{<<"id">>, Id}]. 3123: 3124: stanza_id_from_msg(Msg) -> 3125: case exml_query:path(Msg, [{element, <<"stanza-id">>}, {attr, <<"id">>}]) of 3126: undefined -> exml_query:path(Msg, [{element, <<"result">>}, {attr, <<"id">>}]); 3127: Id -> Id 3128: end. 3129: 3130: retract_element(origin_id) -> 3131: #xmlel{name = <<"retract">>, 3132: attrs = [{<<"xmlns">>, <<"urn:xmpp:message-retract:0">>}]}; 3133: retract_element(stanza_id) -> 3134: #xmlel{name = <<"retract">>, 3135: attrs = [{<<"xmlns">>, <<"urn:esl:message-retract-by-stanza-id:0">>}]}. 3136: 3137: origin_id() -> 3138: <<"orig-id-1">>.