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