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