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