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