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