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