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