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