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