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