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