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