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