1: %%==============================================================================
    2: %% Copyright 2013 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(mongooseimctl_SUITE).
   17: -compile([export_all, nowarn_export_all, nowarn_shadow_vars]).
   18: -include_lib("escalus/include/escalus.hrl").
   19: -include_lib("common_test/include/ct.hrl").
   20: -include_lib("exml/include/exml.hrl").
   21: -include_lib("eunit/include/eunit.hrl").
   22: 
   23: -import(mongooseimctl_helper, [mongooseimctl/3, rpc_call/3]).
   24: -import(mongoose_helper, [auth_modules/0]).
   25: -import(distributed_helper, [mim/0,
   26:                              require_rpc_nodes/1,
   27:                              rpc/4]).
   28: -import(domain_helper, [host_type/0, domain/0]).
   29: -import(config_parser_helper, [config/2, default_mod_config/1]).
   30: 
   31: -define(HTTP_UPLOAD_FILENAME, "tmp.txt").
   32: -define(HTTP_UPLOAD_FILESIZE, "5").
   33: -define(HTTP_UPLOAD_TIMEOUT, "666").
   34: -define(HTTP_UPLOAD_PARAMS(ContentType), ?HTTP_UPLOAD_PARAMS(?HTTP_UPLOAD_FILENAME,
   35:                                                              ?HTTP_UPLOAD_FILESIZE,
   36:                                                              ContentType,
   37:                                                              ?HTTP_UPLOAD_TIMEOUT)).
   38: -define(HTTP_UPLOAD_PARAMS_WITH_FILESIZE(X), ?HTTP_UPLOAD_PARAMS(?HTTP_UPLOAD_FILENAME, X,
   39:                                                                  "", ?HTTP_UPLOAD_TIMEOUT)).
   40: -define(HTTP_UPLOAD_PARAMS_WITH_TIMEOUT(X), ?HTTP_UPLOAD_PARAMS(?HTTP_UPLOAD_FILENAME,
   41:                                                                 ?HTTP_UPLOAD_FILESIZE, "", X)).
   42: -define(HTTP_UPLOAD_PARAMS(FileName, FileSize, ContentType, Timeout),
   43:     [domain(), FileName, FileSize, ContentType, Timeout]).
   44: 
   45: -define(CTL_ERROR(Messsage), "Error: \"" ++ Messsage ++ "\"\n").
   46: -define(HTTP_UPLOAD_NOT_ENABLED_ERROR, ?CTL_ERROR("mod_http_upload is not loaded for this host")).
   47: -define(HTTP_UPLOAD_FILESIZE_ERROR, ?CTL_ERROR("size must be positive integer")).
   48: -define(HTTP_UPLOAD_TIMEOUT_ERROR, ?CTL_ERROR("timeout must be positive integer")).
   49: 
   50: -define(S3_BUCKET_URL, "http://localhost:9000/mybucket/").
   51: -define(S3_REGION, "eu-east-25").
   52: -define(S3_ACCESS_KEY_ID, "AKIAIAOAONIULXQGMOUA").
   53: 
   54: %%Prefix MUST be a constant string, otherwise it results in compilation error
   55: -define(GET_URL(Prefix, Sting), fun() -> Prefix ++ URL = Sting, URL end()).
   56: 
   57: %% The following is an example presigned URL:
   58: %%
   59: %%     https://s3.amazonaws.com/examplebucket/test.txt
   60: %%     ?X-Amz-Algorithm=AWS4-HMAC-SHA256
   61: %%     &X-Amz-Credential=<your-access-key-id>/20130721/us-east-1/s3/aws4_request
   62: %%     &X-Amz-Date=20130721T201207Z
   63: %%     &X-Amz-Expires=86400
   64: %%     &X-Amz-SignedHeaders=host
   65: %%     &X-Amz-Signature=<signature-value>
   66: %%
   67: %% for more details see
   68: %%     https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
   69: -define(S3_BASE_URL_REGEX, "^"?S3_BUCKET_URL".+/"?HTTP_UPLOAD_FILENAME).
   70: -define(S3_ALGORITHM_REGEX, "[?&]X-Amz-Algorithm=AWS4-HMAC-SHA256(&|$)").
   71: -define(S3_CREDENTIAL_REGEX,
   72:         % X-Amz-Credential=<your-access-key-id>/<date>/<AWS-region>/<AWS-service>/aws4_request
   73:         "[?&]X-Amz-Credential="?S3_ACCESS_KEY_ID"%2F[0-9]{8}%2F"?S3_REGION"%2Fs3%2Faws4_request(&|$)").
   74: -define(S3_DATE_REGEX, "X-Amz-Date=[0-9]{8}T[0-9]{6}Z(&|$)").
   75: -define(S3_EXPIRATION_REGEX, "[?&]X-Amz-Expires="?HTTP_UPLOAD_TIMEOUT"(&|$)").
   76: -define(S3_SIGNED_HEADERS, "[?&]X-Amz-SignedHeaders=content-length%3Bhost(&|$)").
   77: -define(S3_SIGNED_HEADERS_WITH_ACL,
   78:         "[?&]X-Amz-SignedHeaders=content-length%3Bhost%3Bx-amz-acl(&|$)").
   79: -define(S3_SIGNED_HEADERS_WITH_CONTENT_TYPE,
   80:         "[?&]X-Amz-SignedHeaders=content-length%3Bcontent-type%3Bhost(&|$)").
   81: -define(S3_SIGNED_HEADERS_WITH_CONTENT_TYPE_AND_ACL,
   82:         "[?&]X-Amz-SignedHeaders=content-length%3Bcontent-type%3Bhost%3Bx-amz-acl(&|$)").
   83: -define(S3_SIGNATURE_REGEX, "[?&]X-Amz-Signature=[^&]+(&|$)").
   84: 
   85: 
   86: -record(offline_msg, {us, timestamp, expire, from, to, packet, permanent_fields = []}).
   87: 
   88: %%--------------------------------------------------------------------
   89: %% Suite configuration
   90: %%--------------------------------------------------------------------
   91: 
   92: all() ->
   93:     [
   94:      {group, accounts},
   95:      {group, sessions},
   96:      {group, vcard},
   97:      {group, roster},
   98:      {group, roster_advanced},
   99:      {group, last},
  100:      {group, private},
  101:      {group, stanza},
  102:      {group, stats},
  103:      {group, basic},
  104:      {group, upload},
  105:      {group, graphql}
  106:     ].
  107: 
  108: groups() ->
  109:     [{accounts, [sequence], accounts()},
  110:      {sessions, [sequence], sessions()},
  111:      {vcard, [sequence], vcard()},
  112:      {roster, [sequence], roster()},
  113:      {last, [sequence], last()},
  114:      {private, [sequence], private()},
  115:      {stanza, [sequence], stanza()},
  116:      {roster_advanced, [sequence], roster_advanced()},
  117:      {basic, [sequence], basic()},
  118:      {stats, [sequence], stats()},
  119:      {upload, [], upload()},
  120:      {upload_with_acl, [], upload_enabled()},
  121:      {upload_without_acl, [], upload_enabled()},
  122:      {graphql, [], graphql()}].
  123: 
  124: basic() ->
  125:     [simple_register,
  126:      simple_unregister,
  127:      register_twice,
  128:      backup_restore_mnesia,
  129:      restore_mnesia_wrong,
  130:      dump_and_load,
  131:      load_mnesia_wrong,
  132:      dump_table,
  133:      get_loglevel,
  134:      remove_old_messages_test,
  135:      remove_expired_messages_test].
  136: 
  137: accounts() -> [change_password, check_password_hash, check_password,
  138:                check_account, ban_account, num_active_users, delete_old_users,
  139:                delete_old_users_vhost].
  140: 
  141: sessions() -> [num_resources_num, kick_session, status,
  142:                sessions_info, set_presence].
  143: 
  144: vcard() -> [vcard_rw, vcard2_rw, vcard2_multi_rw].
  145: 
  146: roster() -> [rosteritem_rw,
  147:              presence_after_add_rosteritem,
  148:              push_roster,
  149:              push_roster_all,
  150:              push_roster_alltoall].
  151: 
  152: roster_advanced() -> [process_rosteritems_list_simple,
  153:                       process_rosteritems_list_nomatch,
  154:                       process_rosteritems_list_advanced1,
  155:                       process_rosteritems_list_advanced2,
  156:                       process_rosteritems_delete_advanced,
  157:                       process_rosteritems_delete_advanced2].
  158: 
  159: last() -> [set_last].
  160: 
  161: private() -> [private_rw].
  162: 
  163: stanza() -> [send_message, send_message_wrong_jid, send_stanza, send_stanzac2s_wrong].
  164: 
  165: stats() -> [stats_global, stats_host].
  166: 
  167: upload() ->
  168:     [upload_not_enabled, upload_wrong_filesize, upload_wrong_timeout,
  169:      {group, upload_with_acl}, {group, upload_without_acl}].
  170: 
  171: upload_enabled() ->
  172:     [upload_returns_correct_urls_without_content_type,
  173:      upload_returns_correct_urls_with_content_type,
  174:      real_upload_without_content_type,
  175:      real_upload_with_content_type].
  176: 
  177: graphql() ->
  178:     [graphql_wrong_arguments_number,
  179:      can_execute_admin_queries_with_permissions,
  180:      can_handle_execution_error].
  181: 
  182: suite() ->
  183:     require_rpc_nodes([mim]) ++ escalus:suite().
  184: 
  185: init_per_suite(Config) ->
  186:     TemplatePath = filename:join(?config(mim_data_dir, Config), "roster.template"),
  187:     AuthMods = auth_modules(),
  188:     Node = mim(),
  189:     Config1 = ejabberd_node_utils:init(Node, Config),
  190:     Config2 = escalus:init_per_suite([{ctl_auth_mods, AuthMods},
  191:                                       {roster_template, TemplatePath} | Config1]),
  192:     dynamic_modules:ensure_modules(domain_helper:host_type(), [{mod_last,
  193:         default_mod_config(mod_last)}]),
  194:     dynamic_modules:ensure_modules(domain_helper:secondary_host_type(),
  195:         [{mod_last, default_mod_config(mod_last)}]),
  196:     prepare_roster_template(TemplatePath, domain()),
  197:     %% dump_and_load requires at least one mnesia table
  198:     %% ensure, that passwd table is available
  199:     catch rpc_call(ejabberd_auth_internal, start, [host_type()]),
  200:     escalus:create_users(Config2, escalus:get_users([alice, mike, bob, kate])).
  201: 
  202: prepare_roster_template(TemplatePath, Domain) ->
  203:     {ok, [RosterIn]} = file:consult(TemplatePath ++ ".in"),
  204:     DomainStr = binary_to_list(Domain),
  205:     Roster = [{User, DomainStr, Group, Name} || {User, Group, Name} <- RosterIn],
  206:     FormattedRoster = io_lib:format("~tp.~n", [Roster]),
  207:     file:write_file(TemplatePath, FormattedRoster).
  208: 
  209: end_per_suite(Config) ->
  210:     Config1 = lists:keydelete(ctl_auth_mods, 1, Config),
  211:     delete_users(Config1),
  212:     dynamic_modules:stop(domain_helper:host_type(), mod_last),
  213:     dynamic_modules:stop(domain_helper:secondary_host_type(), mod_last),
  214:     escalus:end_per_suite(Config1).
  215: 
  216: init_per_group(basic, Config) ->
  217:     dynamic_modules:ensure_modules(domain_helper:host_type(),
  218:         [{mod_offline, default_mod_config(mod_offline)}]),
  219:     Config;
  220: init_per_group(private, Config) ->
  221:     dynamic_modules:ensure_modules(domain_helper:host_type(),
  222:                                    [{mod_private, #{iqdisc => one_queue}}]
  223:                                   ),
  224:     Config;
  225: init_per_group(vcard, Config) ->
  226:     case rpc(mim(), gen_mod, get_module_opt, [host_type(), mod_vcard, backend, mnesia]) of
  227:         ldap ->
  228:             {skip, vcard_set_not_supported_with_ldap};
  229:         _ ->
  230:             Config
  231:     end;
  232: init_per_group(roster_advanced, Config) ->
  233:     case rpc(mim(), gen_mod, get_module_opt, [host_type(), mod_roster, backend, mnesia]) of
  234:         mnesia ->
  235:             Config;
  236:         _ ->
  237:             {skip, command_process_rosteritems_supports_only_mnesia}
  238:     end;
  239: init_per_group(upload_without_acl, Config) ->
  240:     dynamic_modules:start(host_type(), mod_http_upload, mod_http_upload_config(false)),
  241:     [{with_acl, false} | Config];
  242: init_per_group(upload_with_acl, Config) ->
  243:     dynamic_modules:start(host_type(), mod_http_upload, mod_http_upload_config(true)),
  244:     [{with_acl, true} | Config];
  245: init_per_group(_GroupName, Config) ->
  246:     Config.
  247: 
  248: mod_http_upload_config(AddAcl) ->
  249:     config([modules, mod_http_upload],
  250:         #{max_file_size => 1234,
  251:           s3 => #{bucket_url => <<?S3_BUCKET_URL>>,
  252:                   add_acl => AddAcl,
  253:                   region => <<?S3_REGION>>,
  254:                   access_key_id => <<?S3_ACCESS_KEY_ID>>,
  255:                   secret_access_key => <<"CG5fGqG0/n6NCPJ10FylpdgRnuV52j8IZvU7BSj8">>
  256:                  }
  257:         }).
  258: 
  259: end_per_group(basic, Config) ->
  260:     dynamic_modules:stop(domain_helper:host_type(), mod_offline),
  261:     Config;
  262: end_per_group(private, Config) ->
  263:     dynamic_modules:stop(domain_helper:host_type(), mod_private),
  264:     Config;
  265: end_per_group(Rosters, Config) when (Rosters == roster) or (Rosters == roster_advanced) ->
  266:     TemplatePath = escalus_config:get_config(roster_template, Config),
  267:     RegUsers = [atom_to_list(U) || {U, _} <- escalus_config:get_config(escalus_users, Config)],
  268:     {ok, [Roster]} = file:consult(TemplatePath),
  269:     C = fun({U, S, _, _}) ->
  270:         case lists:member(U, RegUsers) of
  271:             true ->
  272:                 SB = string_to_binary(S),
  273:                 UB = string_to_binary(U),
  274:                 Acc = mongoose_helper:new_mongoose_acc(SB),
  275:                 rpc(mim(), mongoose_hooks, remove_user, [Acc, SB, UB]);
  276:             _ ->
  277:                ok
  278:         end
  279:     end,
  280:     lists:foreach(C, Roster),
  281:     Config;
  282: end_per_group(UploadGroup, Config) when UploadGroup =:= upload_without_acl;
  283:                                         UploadGroup =:= upload_with_acl ->
  284:     dynamic_modules:stop(host_type(), mod_http_upload),
  285:     Config;
  286: end_per_group(_GroupName, Config) ->
  287:     Config.
  288: 
  289: get_registered_users() ->
  290:     rpc(mim(), ejabberd_auth, get_vh_registered_users, [domain()]).
  291: 
  292: init_per_testcase(CaseName, Config)
  293:   when CaseName == delete_old_users_vhost
  294:        orelse CaseName == stats_global
  295:        orelse CaseName == stats_host ->
  296:     {_, AuthMods} = lists:keyfind(ctl_auth_mods, 1, Config),
  297:     case lists:member(ejabberd_auth_ldap, AuthMods) of
  298:         true -> {skip, "not supported for LDAP"};
  299:         false -> escalus:init_per_testcase(CaseName, Config)
  300:     end;
  301: init_per_testcase(check_password_hash, Config) ->
  302:     {_, AuthMods} = lists:keyfind(ctl_auth_mods, 1, Config),
  303:     case lists:member(ejabberd_auth_ldap, AuthMods) of
  304:         true ->
  305:             {skip, not_fully_supported_with_ldap};
  306:         false ->
  307:             AuthOpts = mongoose_helper:auth_opts_with_password_format(plain),
  308:             Config1 = mongoose_helper:backup_and_set_config_option(Config, {auth, host_type()},
  309:                                                                    AuthOpts),
  310:             Config2 = escalus:create_users(Config1, escalus:get_users([carol])),
  311:             escalus:init_per_testcase(check_password_hash, Config2)
  312:     end;
  313: init_per_testcase(delete_old_users, Config) ->
  314:     {_, AuthMods} = lists:keyfind(ctl_auth_mods, 1, Config),
  315:     case lists:member(ejabberd_auth_ldap, AuthMods) of
  316:         true -> {skip, not_fully_supported_with_ldap};
  317:         false -> escalus:init_per_testcase(delete_old_users, Config)
  318:     end;
  319: init_per_testcase(CaseName, Config) when CaseName == real_upload_without_content_type;
  320:                                          CaseName == real_upload_with_content_type ->
  321:     case mongoose_helper:should_minio_be_running(Config) of
  322:         true -> escalus:init_per_testcase(CaseName, Config);
  323:         false -> {skip, "minio is not running"}
  324:     end;
  325: init_per_testcase(CaseName, Config) ->
  326:     escalus:init_per_testcase(CaseName, Config).
  327: 
  328: end_per_testcase(delete_old_users, Config) ->
  329:     Users = escalus_users:get_users([alice, bob, kate, mike]),
  330:     lists:foreach(fun({_User, UserSpec}) ->
  331:                 {Username, Domain, Pass} = get_user_data(UserSpec, Config),
  332:                 JID = mongoose_helper:make_jid(Username, Domain),
  333:                 rpc(mim(), ejabberd_auth, try_register, [JID, Pass])
  334:         end, Users),
  335:     escalus:end_per_testcase(delete_old_users, Config);
  336: end_per_testcase(check_password_hash, Config) ->
  337:     mongoose_helper:restore_config(Config),
  338:     escalus:delete_users(Config, escalus:get_users([carol]));
  339: end_per_testcase(CaseName, Config) ->
  340:     %% Because kick_session fails with unexpected stanza received:
  341:     %% <presence from="alicE@localhost/res3"
  342:     %%     to="alice@localhost/res1" type="unavailable" />
  343:     %% TODO: Remove when escalus learns how to automatically deal
  344:     %% with 'unavailable' stanzas on client stop.
  345:     mongoose_helper:kick_everyone(),
  346:     escalus:end_per_testcase(CaseName, Config).
  347: 
  348: %%--------------------------------------------------------------------
  349: %% http upload tests
  350: %%--------------------------------------------------------------------
  351: upload_not_enabled(Config) ->
  352:     Ret = mongooseimctl("http_upload", ?HTTP_UPLOAD_PARAMS("text/plain"), Config),
  353:     ?assertEqual({?HTTP_UPLOAD_NOT_ENABLED_ERROR, 1}, Ret).
  354: 
  355: upload_wrong_filesize(Config) ->
  356:     Ret = mongooseimctl("http_upload", ?HTTP_UPLOAD_PARAMS_WITH_FILESIZE("0"), Config),
  357:     ?assertEqual({?HTTP_UPLOAD_FILESIZE_ERROR, 1}, Ret),
  358:     Ret = mongooseimctl("http_upload", ?HTTP_UPLOAD_PARAMS_WITH_FILESIZE("-1"), Config),
  359:     ?assertEqual({?HTTP_UPLOAD_FILESIZE_ERROR, 1}, Ret).
  360: 
  361: upload_wrong_timeout(Config) ->
  362:     Ret = mongooseimctl("http_upload", ?HTTP_UPLOAD_PARAMS_WITH_TIMEOUT("0"), Config),
  363:     ?assertEqual({?HTTP_UPLOAD_TIMEOUT_ERROR, 1}, Ret),
  364:     Ret = mongooseimctl("http_upload", ?HTTP_UPLOAD_PARAMS_WITH_TIMEOUT("-1"), Config),
  365:     ?assertEqual({?HTTP_UPLOAD_TIMEOUT_ERROR, 1}, Ret).
  366: 
  367: upload_returns_correct_urls_with_content_type(Config) ->
  368:     upload_returns_correct_urls(Config, "text/plain").
  369: 
  370: upload_returns_correct_urls_without_content_type(Config) ->
  371:     upload_returns_correct_urls(Config, "").
  372: 
  373: real_upload_with_content_type(Config) ->
  374:     real_upload(Config, "text/plain").
  375: 
  376: real_upload_without_content_type(Config) ->
  377:     real_upload(Config, "").
  378: 
  379: upload_returns_correct_urls(Config, ContentType) ->
  380:     HttpUploadParams = ?HTTP_UPLOAD_PARAMS(ContentType),
  381:     {Output, 0} = mongooseimctl("http_upload", HttpUploadParams, Config),
  382:     {PutURL, GetURL} = get_urls(Output),
  383:     WithACL = proplists:get_value(with_acl, Config),
  384:     check_urls(PutURL, GetURL, WithACL, ContentType).
  385: 
  386: get_urls(Output) ->
  387:     [PutStr, GetStr | _] = string:split(Output, "\n", all),
  388:     PutURL = ?GET_URL("PutURL: ", PutStr),
  389:     GetURL = ?GET_URL("GetURL: ", GetStr),
  390:     {PutURL, GetURL}.
  391: 
  392: check_urls(PutURL, GetURL, WithACL, ContentType) ->
  393:     check_bucket_url_and_filename(put, PutURL),
  394:     check_bucket_url_and_filename(get, GetURL),
  395:     check_substring(?S3_ALGORITHM_REGEX, PutURL),
  396:     check_substring(?S3_CREDENTIAL_REGEX, PutURL),
  397:     check_substring(?S3_DATE_REGEX, PutURL),
  398:     check_substring(?S3_EXPIRATION_REGEX, PutURL),
  399:     SignedHeadersRegex = signed_headers_regex(WithACL, ContentType),
  400:     check_substring(SignedHeadersRegex, PutURL),
  401:     check_substring(?S3_SIGNATURE_REGEX, PutURL).
  402: 
  403: check_bucket_url_and_filename(Type, Url) ->
  404:     UrlRegex = case Type of
  405:                    get -> ?S3_BASE_URL_REGEX"$";
  406:                    put -> ?S3_BASE_URL_REGEX"\?.*"
  407:                end,
  408:     ct:log("check_bucket_url_and_filename type=~p url=~p regex=~p",
  409:            [Type, Url, UrlRegex]),
  410:     ?assertMatch({match, [{0, _}]}, re:run(Url, UrlRegex)).
  411: 
  412: check_substring(SubString, String) ->
  413:     ?assertMatch({match, [_]}, re:run(String, SubString, [global])).
  414: 
  415: signed_headers_regex(false, "") -> ?S3_SIGNED_HEADERS;
  416: signed_headers_regex(false, _)  -> ?S3_SIGNED_HEADERS_WITH_CONTENT_TYPE;
  417: signed_headers_regex(true, "")  -> ?S3_SIGNED_HEADERS_WITH_ACL;
  418: signed_headers_regex(true, _)   -> ?S3_SIGNED_HEADERS_WITH_CONTENT_TYPE_AND_ACL.
  419: 
  420: real_upload(Config, ContentType) ->
  421:     #{node := Node} = mim(),
  422:     BinPath = distributed_helper:bin_path(Node, Config),
  423:     UploadScript = filename:join(?config(mim_data_dir, Config), "test_file_upload.sh"),
  424:     Ret = mongooseimctl_helper:run(UploadScript, [ContentType], [{cd, BinPath}]),
  425:     ?assertMatch({_, 0}, Ret),
  426:     ok.
  427: %%--------------------------------------------------------------------
  428: %% service_admin_extra_accounts tests
  429: %%--------------------------------------------------------------------
  430: 
  431: change_password(Config) ->
  432:     {User, Domain, OldPassword} = get_user_data(alice, Config),
  433:     mongooseimctl("change_password", [User, Domain, <<OldPassword/binary, $2>>], Config),
  434:     {error, {connection_step_failed, _, _}} = escalus_client:start_for(Config, alice, <<"newres">>),
  435:     mongooseimctl("change_password", [User, Domain, OldPassword], Config),
  436:     {ok, Alice2} = escalus_client:start_for(Config, alice, <<"newres2">>),
  437:     escalus_client:stop(Config, Alice2).
  438: 
  439: check_password_hash(Config) ->
  440:     {User, Domain, Pass} = get_user_data(carol, Config),
  441:     MD5Hash = get_md5(Pass),
  442:     MD5HashBad = get_md5(<<Pass/binary, "bad">>),
  443:     SHAHash = get_sha(Pass),
  444: 
  445:     {_, 0} = mongooseimctl("check_password_hash", [User, Domain, MD5Hash, "md5"], Config),
  446:     {_, ErrCode} = mongooseimctl("check_password_hash", [User, Domain, MD5HashBad, "md5"], Config),
  447:     true = (ErrCode =/= 0), %% Must return code other than 0
  448:     {_, 0} = mongooseimctl("check_password_hash", [User, Domain, SHAHash, "sha"], Config).
  449: 
  450: check_password(Config) ->
  451:     {User, Domain, Pass} = get_user_data(alice, Config),
  452:     MetricName = [backends, auth, check_password],
  453:     OldValue = get_metric(MetricName),
  454:     {_, 0} = mongooseimctl("check_password", [User, Domain, Pass], Config),
  455:     {_, ErrCode} = mongooseimctl("check_password", [User, Domain, <<Pass/binary, "Bad">>], Config),
  456:     mongoose_helper:wait_until(
  457:       fun() -> get_metric(MetricName) end, true,
  458:       #{validator => fun(NewValue) -> OldValue =/= NewValue end, name => ?FUNCTION_NAME}),
  459:     true = (ErrCode =/= 0). %% Must return code other than 0
  460: 
  461: get_metric(MetricName) ->
  462:     HostType = domain_helper:host_type(mim),
  463:     HostTypePrefix = domain_helper:make_metrics_prefix(HostType),
  464:     {ok, Value} = rpc(mim(), mongoose_metrics, get_metric_value, [[HostTypePrefix | MetricName]]),
  465:     Value.
  466: 
  467: check_account(Config) ->
  468:     {User, Domain, _Pass} = get_user_data(alice, Config),
  469: 
  470:     {_, 0} = mongooseimctl("check_account", [User, Domain], Config),
  471:     {_, ErrCode} = mongooseimctl("check_account", [<<User/binary, "Bad">>, Domain], Config),
  472:     true = (ErrCode =/= 0). %% Must return code other than 0
  473: 
  474: ban_account(Config) ->
  475:     {User, Domain, Pass} = get_user_data(mike, Config),
  476: 
  477:     {ok, Mike} = escalus_client:start_for(Config, mike, <<"newres">>),
  478:     {_, 0} = mongooseimctl("ban_account", [User, Domain, "SomeReason"], Config),
  479:     escalus:assert(is_stream_error, [<<"conflict">>, <<"SomeReason">>],
  480:                    escalus:wait_for_stanza(Mike)),
  481:     {error, {connection_step_failed, _, _}} = escalus_client:start_for(Config, mike, <<"newres2">>),
  482:     mongooseimctl("change_password", [User, Domain, Pass], Config),
  483:     escalus_connection:wait_for_close(Mike, 1000),
  484:     escalus_cleaner:remove_client(Config, Mike).
  485: 
  486: num_active_users(Config) ->
  487:     %% Given some users with recorded last activity timestamps
  488:     {AliceName, Domain, _} = get_user_data(alice, Config),
  489:     {MikeName, Domain, _} = get_user_data(mike, Config),
  490:     {Mega, Secs, _} = os:timestamp(),
  491:     Now = Mega * 1000000 + Secs,
  492:     set_last(AliceName, Domain, Now),
  493:     set_last(MikeName, Domain, Now),
  494:     {SLastActiveBefore, _} = mongooseimctl("num_active_users", [Domain, "5"], Config),
  495:     %% When we artificially remove a user's last activity timestamp in the given period
  496:     TenDaysAgo = Now - 864000,
  497:     set_last(MikeName, Domain, TenDaysAgo),
  498:     %% Then we expect that the number of active users in the last 5 days is one less
  499:     %% than before the change above
  500:     {SLastActiveAfter, _} = mongooseimctl("num_active_users", [Domain, "5"], Config),
  501:     NLastActiveBefore = list_to_integer(string:strip(SLastActiveBefore, both, $\n)),
  502:     NLastActiveAfter = list_to_integer(string:strip(SLastActiveAfter, both, $\n)),
  503:     NLastActiveAfter = NLastActiveBefore - 1.
  504: 
  505: delete_old_users(Config) ->
  506:     {AliceName, Domain, _} = get_user_data(alice, Config),
  507:     {BobName, Domain, _} = get_user_data(bob, Config),
  508:     {KateName, Domain, _} = get_user_data(kate, Config),
  509:     {MikeName, Domain, _} = get_user_data(mike, Config),
  510: 
  511:     {Mega, Secs, _} = os:timestamp(),
  512:     Now = Mega*1000000+Secs,
  513:     set_last(AliceName, Domain, Now),
  514:     set_last(BobName, Domain, Now),
  515:     set_last(MikeName, Domain, Now),
  516:     set_last(KateName, Domain, 0),
  517: 
  518:     {_, 0} = mongooseimctl("delete_old_users", ["10"], Config),
  519:     {_, 0} = mongooseimctl("check_account", [AliceName, Domain], Config),
  520:     {_, ErrCode} = mongooseimctl("check_account", [KateName, Domain], Config),
  521:     true = (ErrCode =/= 0). %% Must return code other than 0
  522: 
  523: delete_old_users_vhost(Config) ->
  524:     {AliceName, Domain, _} = get_user_data(alice, Config),
  525:     {KateName, Domain, KatePass} = get_user_data(kate, Config),
  526:     SecDomain = ct:get_config({hosts, mim, secondary_domain}),
  527: 
  528:     {Mega, Secs, _} = os:timestamp(),
  529:     Now = Mega*1000000+Secs,
  530:     set_last(AliceName, Domain, Now-86400*30),
  531: 
  532:     {_, 0} = mongooseimctl("register_identified", [KateName, SecDomain, KatePass], Config),
  533:     {_, 0} = mongooseimctl("check_account", [KateName, SecDomain], Config),
  534:     {_, 0} = mongooseimctl("delete_old_users_vhost", [SecDomain, "10"], Config),
  535:     {_, 0} = mongooseimctl("check_account", [AliceName, Domain], Config),
  536:     {_, ErrCode} = mongooseimctl("check_account", [KateName, SecDomain], Config),
  537:     true = (ErrCode =/= 0). %% Must return code other than 0
  538: 
  539: %%--------------------------------------------------------------------
  540: %% service_admin_extra_accounts tests
  541: %%--------------------------------------------------------------------
  542: 
  543: %% Checks both num_resources and resource_num
  544: num_resources_num(Config) ->
  545:     escalus:story(Config, [{alice, 3}, {bob, 1}], fun(_, Alice2, _, _) ->
  546:                 {Username, Domain, _} = get_user_data(alice, Config),
  547:                 ResName = binary_to_list(escalus_client:resource(Alice2)) ++ "\n",
  548: 
  549:                 {"3\n", _} = mongooseimctl("num_resources", [Username, Domain], Config),
  550:                 {ResName, _} = mongooseimctl("resource_num", [Username, Domain, "2"], Config)
  551:         end).
  552: 
  553: kick_session(Config) ->
  554:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  555:                 Username = escalus_client:username(Alice),
  556:                 Domain = escalus_client:server(Alice),
  557:                 Resource = escalus_client:resource(Alice),
  558:                 Args = [Username, Domain, Resource, "Because I can!"],
  559: 
  560:                 {_, 0} = mongooseimctl("kick_session", Args, Config),
  561:                 Stanza = escalus:wait_for_stanza(Alice),
  562:                 escalus:assert(is_stream_error, [<<"conflict">>, <<"Because I can!">>], Stanza)
  563:         end).
  564: 
  565: status(Config) ->
  566:     escalus:story(Config, [{alice, 1}, {mike, 1}, {bob, 1}], fun(User1, User2, User3) ->
  567:                 PriDomain = escalus_client:server(User1),
  568:                 SecDomain = ct:get_config({hosts, mim, secondary_domain}),
  569:                 AwayPresence = escalus_stanza:presence_show(<<"away">>),
  570:                 escalus_client:send(User2, AwayPresence),
  571: 
  572:                 {"2\n", _} = mongooseimctl("status_num", ["available"], Config),
  573: 
  574:                 {"2\n", _} = mongooseimctl("status_num_host", [PriDomain, "available"], Config),
  575:                 {"0\n", _} = mongooseimctl("status_num_host", [SecDomain, "available"], Config),
  576: 
  577:                 {StatusList, _} = mongooseimctl("status_list", ["available"], Config),
  578:                 match_user_status([User1, User3], StatusList),
  579: 
  580:                 {StatusList2, _} = mongooseimctl("status_list_host",
  581:                                                [PriDomain, "available"], Config),
  582:                 match_user_status([User1, User3], StatusList2),
  583:                 {[], _} = mongooseimctl("status_list_host", [SecDomain, "available"], Config)
  584:         end).
  585: 
  586: sessions_info(Config) ->
  587:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(User1, User2, User3) ->
  588:                 Username1 = escalus_client:username(User1),
  589:                 PriDomain = escalus_client:server(User1),
  590:                 SecDomain = ct:get_config({hosts, mim, secondary_domain}),
  591:                 AwayPresence = escalus_stanza:presence_show(<<"away">>),
  592:                 escalus_client:send(User2, AwayPresence),
  593: 
  594:                 {UserList, _} = mongooseimctl("connected_users_info", [], Config),
  595:                 match_user_info([User1, User2, User3], UserList),
  596: 
  597:                 {UserList2, _} = mongooseimctl("connected_users_vhost", [PriDomain], Config),
  598:                 match_user_info([User1, User2, User3], UserList2),
  599:                 {[], _} = mongooseimctl("connected_users_vhost", [SecDomain], Config),
  600: 
  601:                 {UserList3, _} = mongooseimctl("user_sessions_info",
  602:                                                [Username1, PriDomain], Config),
  603:                 match_user_info([User1], UserList3)
  604:         end).
  605: 
  606: set_presence(Config) ->
  607:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  608:                 Username = escalus_client:username(Alice),
  609:                 Domain = escalus_client:server(Alice),
  610:                 Resource = escalus_client:resource(Alice),
  611: 
  612:                 {_, 0} = mongooseimctl("set_presence",
  613:                                      [Username, Domain, Resource,
  614:                                       "available", "away", "mystatus", "10"],
  615:                                      Config),
  616:                 Presence = escalus:wait_for_stanza(Alice),
  617:                 escalus:assert(is_presence_with_show, [<<"away">>], Presence),
  618:                 escalus:assert(is_presence_with_status, [<<"mystatus">>], Presence),
  619:                 escalus:assert(is_presence_with_priority, [<<"10">>], Presence)
  620:         end).
  621: 
  622: %%--------------------------------------------------------------------
  623: %% service_admin_extra_vcard tests
  624: %%--------------------------------------------------------------------
  625: 
  626: vcard_rw(Config) ->
  627:     {Username, Domain, _} = get_user_data(alice, Config),
  628: 
  629:     {_, ExitCode} = mongooseimctl("get_vcard", [Username, Domain, "NICKNAME"], Config),
  630:     true = (ExitCode /= 0),
  631: 
  632:     {_, 0} = mongooseimctl("set_vcard", [Username, Domain, "NICKNAME", "SomeNickname"], Config),
  633:     {"SomeNickname\n", 0} = mongooseimctl("get_vcard", [Username, Domain, "NICKNAME"], Config).
  634: 
  635: vcard2_rw(Config) ->
  636:     {Username, Domain, _} = get_user_data(alice, Config),
  637: 
  638:     {_, ExitCode} = mongooseimctl("get_vcard2", [Username, Domain, "ORG", "ORGNAME"], Config),
  639:     true = (ExitCode /= 0),
  640: 
  641:     {_, 0} = mongooseimctl("set_vcard2", [Username, Domain, "ORG", "ORGNAME", "ESL"], Config),
  642:     {"ESL\n", 0} = mongooseimctl("get_vcard2", [Username, Domain, "ORG", "ORGNAME"], Config).
  643: 
  644: vcard2_multi_rw(Config) ->
  645:     {Username, Domain, _} = get_user_data(alice, Config),
  646: 
  647:     {_, ExitCode} = mongooseimctl("get_vcard2_multi", [Username, Domain, "ORG", "ORGUNIT"], Config),
  648:     true = (ExitCode /= 0),
  649: 
  650:     Args = [Username, Domain, "ORG", "ORGUNIT", "sales;marketing"],
  651:     {_, 0} = mongooseimctl("set_vcard2_multi", Args, Config),
  652:     {OrgUnits0, 0} = mongooseimctl("get_vcard2_multi",
  653:                                    [Username, Domain, "ORG", "ORGUNIT"], Config),
  654:     OrgUnits = string:tokens(OrgUnits0, "\n"),
  655:     2 = length(OrgUnits),
  656:     true = (lists:member("sales", OrgUnits) andalso lists:member("marketing", OrgUnits)).
  657: 
  658: %%--------------------------------------------------------------------
  659: %% service_admin_extra_vcard tests
  660: %%--------------------------------------------------------------------
  661: 
  662: rosteritem_rw(Config) ->
  663:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  664:                 BobJid = escalus_users:get_jid(Config, bob),
  665:                 MikeJid = escalus_users:get_jid(Config, mike),
  666: 
  667:                 {AliceName, Domain, _} = get_user_data(alice, Config),
  668:                 {BobName, Domain, _} = get_user_data(bob, Config),
  669:                 {MikeName, Domain, _} = get_user_data(mike, Config),
  670: 
  671:                 {_, 0} = add_rosteritem1(AliceName, Domain, BobName, Config),
  672:                 {_, 0} = mongooseimctl("add_rosteritem",
  673:                                      [AliceName, Domain, MikeName,
  674:                                       Domain, "My Mike",
  675:                                       "My Group", "both"], Config),
  676: 
  677:                 [Push1, Push2] = escalus:wait_for_stanzas(Alice, 2), % Check roster broadcasts
  678:                 escalus:assert(is_roster_set, Push1),
  679:                 escalus:assert(roster_contains, [BobJid], Push1),
  680:                 escalus:assert(is_roster_set, Push2),
  681:                 escalus:assert(roster_contains, [MikeJid], Push2),
  682: 
  683:                 {Items1, 0} = mongooseimctl("get_roster", [AliceName, Domain], Config),
  684:                 match_roster([{BobName, Domain, "MyBob", "MyGroup", "both"},
  685:                               {MikeName, Domain, "MyMike", "MyGroup", "both"}], Items1),
  686: 
  687:                 escalus:send(Alice, escalus_stanza:roster_get()),
  688:                 Roster1 = escalus:wait_for_stanza(Alice),
  689:                 escalus:assert(is_roster_result, Roster1),
  690:                 escalus:assert(roster_contains, [BobJid], Roster1),
  691:                 escalus:assert(roster_contains, [MikeJid], Roster1),
  692: 
  693:                 {_, 0} = mongooseimctl("delete_rosteritem",
  694:                                      [AliceName, Domain, BobName, Domain],
  695:                                      Config),
  696: 
  697:                 Push3 = escalus:wait_for_stanza(Alice),
  698:                 escalus:assert(is_roster_set, Push3),
  699:                 escalus:assert(roster_contains, [BobJid], Push3),
  700: 
  701:                 {Items2, 0} = mongooseimctl("get_roster", [AliceName, Domain], Config),
  702:                 match_roster([{MikeName, Domain, "MyMike", "MyGroup", "both"}], Items2),
  703: 
  704:                 escalus:send(Alice, escalus_stanza:roster_remove_contact(MikeJid)),  % cleanup
  705:                 escalus:wait_for_stanzas(Alice, 2, 5000)
  706:         end).
  707: 
  708: presence_after_add_rosteritem(Config) ->
  709:      escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  710:                  BobJid = escalus_users:get_jid(Config, bob),
  711:                  {AliceName, Domain, _} = get_user_data(alice, Config),
  712:                  {BobName, Domain, _} = get_user_data(bob, Config),
  713: 
  714:                  {_, 0} = add_rosteritem1(AliceName, Domain, BobName, Config),
  715: 
  716:                  escalus:send(Alice, escalus_stanza:presence(<<"available">>)),
  717:                  escalus:assert(is_presence, escalus:wait_for_stanza(Bob)),
  718: 
  719:                  escalus:send(Alice, escalus_stanza:roster_remove_contact(BobJid)),  % cleanup
  720:                  %% Wait for stanzas, so they would not end up in the next story
  721:                  escalus:wait_for_stanzas(Alice, 3, 5000)
  722:          end).
  723: 
  724: push_roster(Config) ->
  725:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  726:                 BobJid = escalus_users:get_jid(Config, bob),
  727:                 {AliceName, Domain, _} = get_user_data(alice, Config),
  728:                 TemplatePath = escalus_config:get_config(roster_template, Config),
  729: 
  730:                 {_, 0} = mongooseimctl("push_roster", [TemplatePath, AliceName, Domain], Config),
  731:                 escalus:send(Alice, escalus_stanza:roster_get()),
  732:                 Roster1 = escalus:wait_for_stanza(Alice),
  733:                 escalus:assert(is_roster_result, Roster1),
  734:                 escalus:assert(roster_contains, [BobJid], Roster1),
  735: 
  736:                 escalus:send(Alice, escalus_stanza:roster_remove_contact(BobJid)), % cleanup
  737:                 escalus:wait_for_stanzas(Alice, 2, 5000)
  738:         end).
  739: 
  740: process_rosteritems_list_simple(Config) ->
  741:     escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  742:         %% given
  743:         Action = "list",
  744:         Subs = "any",
  745:         Asks = "any",
  746:         User = escalus_client:short_jid(Alice),
  747:         Contact = string:to_lower(binary_to_list(escalus_client:short_jid(Bob))),
  748:         {AliceName, Domain, _} = get_user_data(alice, Config),
  749:         {BobName, Domain, _} = get_user_data(bob, Config),
  750:         %% when
  751:         {_, 0} = add_rosteritem1(AliceName, Domain, BobName, Config),
  752:         _S = escalus:wait_for_stanzas(Alice, 2),
  753:         {R, 0} = mongooseimctl("process_rosteritems", [Action, Subs, Asks, User, Contact], Config),
  754:         %% then
  755:         {match, _} = re:run(R, ".*Matches:.*" ++ Contact ++ ".*"),
  756:         {_, 0} = mongooseimctl("delete_rosteritem", [AliceName, Domain, BobName, Domain], Config)
  757:     end).
  758: 
  759: process_rosteritems_list_nomatch(Config) ->
  760:     escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  761:         %% given
  762:         Action = "list",
  763:         Subs = "from:both",
  764:         Asks = "any",
  765:         User = escalus_client:short_jid(Alice),
  766:         Contact =string:to_lower(binary_to_list(escalus_client:short_jid(Bob))),
  767:         {AliceName, Domain, _} = get_user_data(alice, Config),
  768:         {BobName, Domain, _} = get_user_data(bob, Config),
  769:         {_, 0} = mongooseimctl("add_rosteritem", [AliceName, Domain, BobName,
  770:                                                 Domain, "MyBob", "MyGroup", "to"], Config),
  771:         escalus:wait_for_stanzas(Alice, 2),
  772:         %% when
  773:         {R, 0} = mongooseimctl("process_rosteritems", [Action, Subs, Asks, User, Contact], Config),
  774:         %% then
  775:         nomatch = re:run(R, ".*Matches:.*" ++ Contact ++ ".*"),
  776:         {_, 0} = mongooseimctl("delete_rosteritem", [AliceName, Domain, BobName, Domain], Config)
  777:     end).
  778: 
  779: process_rosteritems_list_advanced1(Config) ->
  780:     escalus:story(Config, [{alice, 1}, {mike, 1}, {kate, 1}], fun(Alice, Mike, Kate) ->
  781:         %% given
  782:         Action = "list",
  783:         Subs = "from:both",
  784:         Asks = "any",
  785:         User = escalus_client:short_jid(Alice),
  786:         {AliceName, Domain, _} = get_user_data(alice, Config),
  787:         {MikeName, Domain, _} = get_user_data(mike, Config),
  788:         {KateName, Domain, _} = get_user_data(kate, Config),
  789:         ContactMike = string:to_lower(binary_to_list(escalus_client:short_jid(Mike))),
  790:         ContactKate= string:to_lower(binary_to_list(escalus_client:short_jid(Kate))),
  791:         ContactsRegexp = ContactMike ++ ":" ++
  792:                          string:substr(binary_to_list(KateName), 1, 2) ++
  793:                          ".*@.*",
  794: 
  795:         {_, 0} = add_rosteritem2(AliceName, Domain, MikeName, Domain, Config),
  796:         {_, 0} = mongooseimctl("add_rosteritem", [AliceName, Domain, KateName,
  797:                                                 Domain, "BestFriend", "MyGroup", "both"], Config),
  798:         escalus:wait_for_stanzas(Alice, 4),
  799:         %% when
  800:         {R, 0} = mongooseimctl("process_rosteritems",
  801:                              [Action, Subs, Asks, User, ContactsRegexp],
  802:                              Config),
  803:         %% then
  804:         {match, _} = re:run(R, ".*Matches:.*" ++ ContactMike ++ ".*"),
  805:         {match, _} = re:run(R, ".*Matches:.*" ++ ContactKate ++ ".*"),
  806:         {_, 0} = mongooseimctl("delete_rosteritem", [AliceName, Domain, MikeName, Domain], Config),
  807:         {_, 0} = mongooseimctl("delete_rosteritem", [AliceName, Domain, KateName, Domain], Config)
  808:     end).
  809: 
  810: process_rosteritems_delete_advanced(Config) ->
  811:     escalus:story(Config, [{alice, 1}, {mike, 1}, {kate, 1}], fun(Alice, Mike, Kate) ->
  812:         %% given
  813:         Action = "delete",
  814:         Subs = "from",
  815:         Asks = "any",
  816:         User = escalus_client:short_jid(Alice),
  817:         {AliceName, Domain, _} = get_user_data(alice, Config),
  818:         {MikeName, Domain, _} = get_user_data(mike, Config),
  819:         {KateName, Domain, _} = get_user_data(kate, Config),
  820:         ContactMike = string:to_lower(binary_to_list(escalus_client:short_jid(Mike))),
  821:         ContactKate= string:to_lower(binary_to_list(escalus_client:short_jid(Kate))),
  822:         ContactsRegexp = ".*" ++ string:substr(ContactMike, 3) ++
  823:                          ":" ++ string:substr(ContactKate, 1, 2) ++
  824:                          "@" ++ binary_to_list(Domain),
  825:         {_, 0} = mongooseimctl("add_rosteritem", [AliceName, Domain, MikeName,
  826:                                                 Domain, "DearMike", "MyGroup", "from"], Config),
  827:         {_, 0} = mongooseimctl("add_rosteritem", [AliceName, Domain, KateName,
  828:                                                 Domain, "Friend", "MyGroup", "from"], Config),
  829:         escalus:wait_for_stanzas(Alice, 4),
  830:         %% when
  831:         {R, 0} = mongooseimctl("process_rosteritems",
  832:                              [Action, Subs, Asks, User, ContactsRegexp],
  833:                              Config),
  834:         %% then
  835:         {match, _} = re:run(R, ".*Matches:.*" ++ ContactMike ++ ".*"),
  836:         nomatch = re:run(R, ".*Matches:.*" ++ ContactKate ++ ".*"),
  837:         {_, 0} = mongooseimctl("delete_rosteritem", [AliceName, Domain, MikeName, Domain], Config),
  838:         {_, 0} = mongooseimctl("delete_rosteritem", [AliceName, Domain, KateName, Domain], Config)
  839:     end).
  840: 
  841: process_rosteritems_list_advanced2(Config) ->
  842:     escalus:story(Config, [{alice, 1}, {mike, 1}, {kate, 1}], fun(Alice, Mike, Kate) ->
  843:         %% given
  844:         Action = "list",
  845:         Subs = "any",
  846:         Asks = "any",
  847:         User = escalus_client:short_jid(Alice),
  848:         {AliceName, Domain, _} = get_user_data(alice, Config),
  849:         {MikeName, Domain, _} = get_user_data(mike, Config),
  850:         {KateName, Domain, _} = get_user_data(kate, Config),
  851:         ContactMike = string:to_lower(binary_to_list(escalus_client:short_jid(Mike))),
  852:         ContactKate= string:to_lower(binary_to_list(escalus_client:short_jid(Kate))),
  853:         ContactsRegexp = ".*e@lo.*",
  854:         {_, 0} = add_rosteritem2(AliceName, Domain, MikeName, Domain, Config),
  855:         {_, 0} = mongooseimctl("add_rosteritem", [AliceName, Domain, KateName,
  856:                                                 Domain, "KateFromSchool",
  857:                                                 "MyGroup", "from"], Config),
  858:         escalus:wait_for_stanzas(Alice, 4),
  859:         %% when
  860:         {R, 0} = mongooseimctl("process_rosteritems",
  861:                              [Action, Subs, Asks, User, ContactsRegexp],
  862:                              Config),
  863:         %% then
  864:         {match, _} = re:run(R, ".*Matches:.*" ++ ContactMike ++ ".*"),
  865:         {match, _} = re:run(R, ".*Matches:.*" ++ ContactKate ++ ".*"),
  866:         {_, 0} = mongooseimctl("delete_rosteritem", [AliceName, Domain, MikeName, Domain], Config),
  867:         {_, 0} = mongooseimctl("delete_rosteritem", [AliceName, Domain, KateName, Domain], Config)
  868:     end).
  869: 
  870: process_rosteritems_delete_advanced2(Config) ->
  871:     escalus:story(Config, [{alice, 1}, {bob, 1}, {mike, 1}, {kate, 1}],
  872:       fun(Alice, Bob, Mike, Kate) ->
  873:         %% given
  874:         Action = "delete",
  875:         Subs = "to:from",
  876:         Asks = "any",
  877:         User = "al.c[e]@.*host:((b[o]b)|(mike))@loc.*t2",
  878:         {AliceName, Domain, _} = get_user_data(alice, Config),
  879:         {BobName, Domain, _} = get_user_data(bob, Config),
  880:         {MikeName, Domain, _} = get_user_data(mike, Config),
  881:         {KateName, Domain, _} = get_user_data(kate, Config),
  882:         ContactMike = string:to_lower(binary_to_list(escalus_client:short_jid(Mike))),
  883:         ContactKate= string:to_lower(binary_to_list(escalus_client:short_jid(Kate))),
  884:         ContactBob= string:to_lower(binary_to_list(escalus_client:short_jid(Bob))),
  885:         ContactsReg = ".ik[ea]@localho+.*:k@loc.*st:(alice)+@.*:no",
  886:         {_, 0} = mongooseimctl("add_rosteritem",
  887:                              [AliceName, Domain, MikeName,
  888:                               Domain, "DearMike", "MyGroup", "to"],
  889:                              Config),
  890:         {_, 0} = mongooseimctl("add_rosteritem",
  891:                              [AliceName, Domain, KateName,
  892:                               Domain, "HateHerSheHasSoNiceLegs",
  893:                               "MyGroup", "to"], Config),
  894:         {_, 0} = mongooseimctl("add_rosteritem", [BobName, Domain, AliceName,
  895:                                                 Domain, "Girlfriend", "MyGroup", "from"], Config),
  896:         escalus:wait_for_stanzas(Alice, 4),
  897:         escalus:wait_for_stanzas(Bob, 2),
  898:         %% when
  899:         {R, 0} = mongooseimctl("process_rosteritems",
  900:                              [Action, Subs, Asks, User, ContactsReg],
  901:                              Config),
  902:         %% then
  903:         {match, _} = re:run(R, ".*Matches:.*" ++ ContactMike ++ ".*"),
  904:         nomatch = re:run(R, ".*Matches:.*" ++ ContactKate ++ ".*"),
  905:         nomatch = re:run(R, ".*Matches:.*" ++ ContactBob ++ ".*"),
  906:         {_, 0} = mongooseimctl("delete_rosteritem", [AliceName, Domain, MikeName, Domain], Config),
  907:         {_, 0} = mongooseimctl("delete_rosteritem", [AliceName, Domain, KateName, Domain], Config),
  908:         {_, 0} = mongooseimctl("delete_rosteritem", [BobName, Domain, AliceName, Domain], Config)
  909:     end).
  910: 
  911: push_roster_all(Config) ->
  912:     escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  913:                 TemplatePath = escalus_config:get_config(roster_template, Config),
  914: 
  915:                 {_, 0} = mongooseimctl("push_roster_all", [TemplatePath], Config),
  916: 
  917:                 escalus:send(Alice, escalus_stanza:roster_get()),
  918:                 Roster1 = escalus:wait_for_stanza(Alice),
  919:                 escalus:assert(is_roster_result, Roster1),
  920:                 BobJid = escalus_client:short_jid(Bob),
  921:                 escalus:assert(roster_contains, [BobJid], Roster1),
  922: 
  923:                 escalus:send(Bob, escalus_stanza:roster_get()),
  924:                 Roster2 = escalus:wait_for_stanza(Bob),
  925:                 escalus:assert(is_roster_result, Roster2),
  926:                 AliceJid = escalus_client:short_jid(Alice),
  927:                 escalus:assert(roster_contains, [AliceJid], Roster2),
  928: 
  929:                 escalus:send_and_wait(Alice, escalus_stanza:roster_remove_contact(bob)), % cleanup
  930:                 escalus:send_and_wait(Bob, escalus_stanza:roster_remove_contact(alice)) % cleanup
  931:         end).
  932: 
  933: push_roster_alltoall(Config) ->
  934:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  935:                 BobJid = escalus_users:get_jid(Config, bob),
  936:                 MikeJid = escalus_users:get_jid(Config, mike),
  937:                 KateJid = escalus_users:get_jid(Config, kate),
  938:                 {_, Domain, _} = get_user_data(alice, Config),
  939: 
  940:                 {_, 0} = mongooseimctl("push_roster_alltoall", [Domain, "MyGroup"], Config),
  941: 
  942:                 escalus:send(Alice, escalus_stanza:roster_get()),
  943:                 Roster = escalus:wait_for_stanza(Alice),
  944: 
  945:                 escalus:assert(is_roster_result, Roster),
  946:                 escalus:assert(roster_contains, [BobJid], Roster),
  947:                 escalus:assert(roster_contains, [MikeJid], Roster),
  948:                 escalus:assert(roster_contains, [KateJid], Roster)
  949:         end).
  950: 
  951: %%--------------------------------------------------------------------
  952: %% service_admin_extra_last tests
  953: %%--------------------------------------------------------------------
  954: 
  955: set_last(Config) ->
  956:     escalus:story(Config, [{alice, 1}], fun(Alice) ->
  957:                 BobJid = escalus_users:get_jid(Config, bob),
  958:                 {AliceName, Domain, _} = get_user_data(alice, Config),
  959:                 {BobName, Domain, _} = get_user_data(bob, Config),
  960: 
  961:                 {_, 0} = add_rosteritem1(AliceName, Domain, BobName, Config),
  962:                 {_, 0} = mongooseimctl("add_rosteritem",
  963:                                      [BobName, Domain, AliceName,
  964:                                       Domain, "MyAlice", "MyGroup", "both"],
  965:                                      Config),
  966: 
  967:                 escalus:wait_for_stanza(Alice), % ignore push
  968: 
  969:                 Now = os:system_time(second),
  970:                 TS = integer_to_list(Now - 7200),
  971:                 {_, 0} = mongooseimctl("set_last", [BobName, Domain, TS, "Status"], Config),
  972:                 escalus:send(Alice, escalus_stanza:last_activity(BobJid)),
  973:                 LastAct = escalus:wait_for_stanza(Alice),
  974:                 escalus:assert(is_last_result, LastAct),
  975:                 Seconds = list_to_integer(binary_to_list(
  976:                             exml_query:path(LastAct, [{element, <<"query">>},
  977:                             {attr, <<"seconds">>}]))),
  978:                 true = (( (Seconds > 7100) andalso (Seconds < 7300) ) orelse Seconds),
  979: 
  980:                 {_, 0} = mongooseimctl("delete_rosteritem",
  981:                                      [AliceName, Domain, BobName, Domain],
  982:                                      Config), % cleanup
  983:                 {_, 0} = mongooseimctl("delete_rosteritem",
  984:                                      [BobName, Domain, AliceName, Domain],
  985:                                      Config)
  986:         end).
  987: 
  988: %%--------------------------------------------------------------------
  989: %% service_admin_extra_private tests
  990: %%--------------------------------------------------------------------
  991: 
  992: private_rw(Config) ->
  993:     {AliceName, Domain, _} = get_user_data(alice, Config),
  994:     XmlEl1 = "<secretinfo xmlns=\"nejmspejs\">1</secretinfo>",
  995:     XmlEl2 = "<secretinfo xmlns=\"inny\">2</secretinfo>",
  996: 
  997:     {_, 0} = mongooseimctl("private_set", [AliceName, Domain, XmlEl1], Config),
  998:     {_, 0} = mongooseimctl("private_set", [AliceName, Domain, XmlEl2], Config),
  999: 
 1000:     {Result, 0} = mongooseimctl("private_get",
 1001:                               [AliceName, Domain, "secretinfo", "nejmspejs"],
 1002:                               Config),
 1003:     {ok, #xmlel{ name = <<"secretinfo">>, attrs = [{<<"xmlns">>, <<"nejmspejs">>}],
 1004:                 children = [#xmlcdata{ content = <<"1">> }]}} = exml:parse(list_to_binary(Result)).
 1005: 
 1006: %%--------------------------------------------------------------------
 1007: %% service_admin_extra_stanza tests
 1008: %%--------------------------------------------------------------------
 1009: 
 1010: send_message(Config) ->
 1011:     escalus:story(Config, [{alice, 1}, {bob, 2}], fun(Alice, Bob1, Bob2) ->
 1012:                 {_, 0} = mongooseimctl("send_message_chat", [escalus_client:full_jid(Alice),
 1013:                                                            escalus_client:full_jid(Bob1),
 1014:                                                            "Hi Bob!"], Config),
 1015:                 Stanza1 = escalus:wait_for_stanza(Bob1),
 1016:                 escalus:assert(is_chat_message, [<<"Hi Bob!">>], Stanza1),
 1017: 
 1018:                 {_, 0} = mongooseimctl("send_message_headline",
 1019:                                      [escalus_client:full_jid(Alice),
 1020:                                       escalus_client:short_jid(Bob1),
 1021:                                       "Subj", "Hi Bob!!"], Config),
 1022:                 Stanza2 = escalus:wait_for_stanza(Bob1),
 1023:                 Stanza3 = escalus:wait_for_stanza(Bob2),
 1024:                 escalus:assert(is_headline_message, [<<"Subj">>, <<"Hi Bob!!">>], Stanza2),
 1025:                 escalus:assert(is_headline_message, [<<"Subj">>, <<"Hi Bob!!">>], Stanza3)
 1026:         end).
 1027: 
 1028: send_message_wrong_jid(Config) ->
 1029:     escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
 1030:         {_, Err1} = mongooseimctl("send_message_chat", ["@@#$%!!.§§£",
 1031:                                                    escalus_client:full_jid(Bob),
 1032:                                                    "Hello bobby!"], Config),
 1033:         {_, Err2} = mongooseimctl("send_message_headline", ["%%@&@&@==//\///",
 1034:                                                        escalus_client:short_jid(Bob),
 1035:                                                        "Subj", "Are
 1036:                                                        you there?"],
 1037:                              Config),
 1038:         true = Err1 =/= 0,
 1039:         true = Err2 =/= 0,
 1040:         escalus_assert:has_no_stanzas(Alice),
 1041:         escalus_assert:has_no_stanzas(Bob)
 1042:     end).
 1043: 
 1044: send_stanza(Config) ->
 1045:     escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
 1046:                 Domain = escalus_client:server(Alice),
 1047:                 Resource = escalus_client:resource(Alice),
 1048:                 {BobName, _, _} = get_user_data(bob, Config),
 1049:                 BobJID = <<BobName/binary, $@, Domain/binary, $/,
 1050:                            (escalus_client:resource(Bob))/binary>>,
 1051: 
 1052:                 Stanza = Stanza = create_stanza(Alice, BobJID),
 1053:                 {_, 0} = mongooseimctl("send_stanza_c2s",
 1054:                        [BobName, Domain, Resource, Stanza],
 1055:                        Config),
 1056: 
 1057:                 Message = escalus:wait_for_stanza(Alice),
 1058:                 escalus:assert(is_chat_message, [<<"Hi">>], Message),
 1059:                 escalus:assert(is_stanza_from, [Bob], Message)
 1060:         end).
 1061: 
 1062: send_stanzac2s_wrong(Config) ->
 1063:     escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
 1064:         Domain = escalus_client:server(Alice),
 1065:         Resource = escalus_client:resource(Alice),
 1066:         WrongBobName = "bobby_the_great",
 1067:         {BobName, _, _} = get_user_data(bob, Config),
 1068:         BobJID = <<BobName/binary, $@, Domain/binary, $/, (escalus_client:resource(Bob))/binary>>,
 1069:         Stanza = create_stanza(Alice, BobJID),
 1070:         StanzaWrong = <<"<iq type='get' id='234234'><xmlns='wrongwrong'>">>,
 1071:         {_, Err} = mongooseimctl("send_stanza_c2s",
 1072:                   [WrongBobName, Domain, Resource, Stanza],
 1073:                   Config),
 1074:         {_, Err2} = mongooseimctl("send_stanza_c2s",
 1075:                   [BobName, Domain, Resource,  StanzaWrong],
 1076:                   Config),
 1077: 
 1078:         true = Err =/= 0,
 1079:         true = Err2 =/= 0,
 1080:         escalus_assert:has_no_stanzas(Alice)
 1081:     end).
 1082: 
 1083: create_stanza(Name1, JID2) ->
 1084:     exml:to_binary(escalus_stanza:from(escalus_stanza:chat_to(Name1, "Hi"), JID2)).
 1085: 
 1086: %%--------------------------------------------------------------------
 1087: %% service_admin_extra_stats tests
 1088: %%--------------------------------------------------------------------
 1089: 
 1090: stats_global(Config) ->
 1091:     escalus:story(Config, [{alice, 1}, {bob, 1}], fun(_Alice, _Bob) ->
 1092:                 RegisteredCount = length(escalus_config:get_config(escalus_users, Config, [])),
 1093:                 Registered = integer_to_list(RegisteredCount) ++ "\n",
 1094: 
 1095:                 {UpTime, 0} = mongooseimctl("stats", ["uptimeseconds"], Config),
 1096:                 _ = list_to_integer(string:strip(UpTime, both, $\n)),
 1097:                 {Registered, 0} = mongooseimctl("stats", ["registeredusers"], Config),
 1098: 
 1099:                 {"2\n", 0} = mongooseimctl("stats", ["onlineusersnode"], Config),
 1100: 
 1101:                 {"2\n", 0} = mongooseimctl("stats", ["onlineusers"], Config)
 1102:         end).
 1103: 
 1104: stats_host(Config) ->
 1105:     escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, _Bob) ->
 1106:                 RegisteredCount = length(escalus_config:get_config(escalus_users, Config, [])),
 1107:                 Registered = integer_to_list(RegisteredCount) ++ "\n",
 1108: 
 1109:                 PriDomain = escalus_client:server(Alice),
 1110:                 SecDomain = ct:get_config({hosts, mim, secondary_domain}),
 1111: 
 1112:                 {Registered, 0} = mongooseimctl("stats_host",
 1113:                                                 ["registeredusers", PriDomain], Config),
 1114:                 {"0\n", 0} = mongooseimctl("stats_host", ["registeredusers", SecDomain], Config),
 1115: 
 1116:                 {"2\n", 0} = mongooseimctl("stats_host", ["onlineusers", PriDomain], Config),
 1117:                 {"0\n", 0} = mongooseimctl("stats_host", ["onlineusers", SecDomain], Config)
 1118:         end).
 1119: 
 1120: %%--------------------------------------------------------------------
 1121: %% mongoose_graphql tests
 1122: %%--------------------------------------------------------------------
 1123: 
 1124: can_execute_admin_queries_with_permissions(Config) ->
 1125:     Query = "query { checkAuth { authStatus } }",
 1126:     Res = mongooseimctl("graphql", [Query], Config),
 1127:     ?assertMatch({_, 0}, Res),
 1128:     Data = element(1, Res),
 1129:     ?assertNotEqual(nomatch, string:find(Data, "AUTHORIZED")).
 1130: 
 1131: can_handle_execution_error(Config) ->
 1132:     Query = "{}",
 1133:     Res = mongooseimctl("graphql", [Query], Config),
 1134:     ?assertMatch({_, 0}, Res),
 1135:     Data = element(1, Res),
 1136:     ?assertNotEqual(nomatch, string:find(Data, "parser_error")).
 1137: 
 1138: graphql_wrong_arguments_number(Config) ->
 1139:     ExpectedFragment = "This command requires",
 1140:     ResNoArgs = mongooseimctl("graphql", [], Config),
 1141:     ?assertMatch({_, 1}, ResNoArgs),
 1142:     Data1 = element(1, ResNoArgs),
 1143:     ?assertNotEqual(nomatch, string:find(Data1, ExpectedFragment)),
 1144: 
 1145:     ResTooManyArgs = mongooseimctl("graphql", ["{}", "{}"], Config),
 1146:     ?assertMatch({_, 1}, ResTooManyArgs),
 1147:     Data2 = element(1, ResTooManyArgs),
 1148:     ?assertNotEqual(nomatch, string:find(Data2, ExpectedFragment)).
 1149: 
 1150: %%-----------------------------------------------------------------
 1151: %% Improve coverage
 1152: %%-----------------------------------------------------------------
 1153: 
 1154: simple_register(Config) ->
 1155:     %% given
 1156:     Domain = domain(),
 1157:     {Name, Password} = {<<"tyler">>, <<"durden">>},
 1158:     %% when
 1159:     {R1, 0} = mongooseimctl("registered_users", [Domain], Config),
 1160:     Before = length(string:tokens(R1, "\n")),
 1161:     {_, 0} = mongooseimctl("register", [Domain, Password], Config),
 1162:     {_, 0} = mongooseimctl("register_identified", [Name, Domain, Password], Config),
 1163: 
 1164:     {R2, 0} = mongooseimctl("registered_users", [Domain], Config),
 1165:     After = length(string:tokens(R2, "\n")),
 1166:     %% then
 1167:     2 = After - Before.
 1168: 
 1169: simple_unregister(Config) ->
 1170:     %% given
 1171:     Domain = domain(),
 1172:     {Name, _} = {<<"tyler">>, <<"durden">>},
 1173:     %% when
 1174:     {_, 0} = mongooseimctl("unregister", [Name, Domain], Config),
 1175:     {R2, 0} = mongooseimctl("registered_users", [Domain], Config),
 1176:     %% then
 1177:     nomatch = re:run(R2, ".*(" ++ binary_to_list(Name) ++ ").*").
 1178: 
 1179: register_twice(Config) ->
 1180:     %% given
 1181:     Domain = domain(),
 1182:     {Name,  Password} = {<<"tyler">>, <<"durden">>},
 1183:     %% when
 1184:     {_, 0} = mongooseimctl("register_identified", [Name, Domain, Password], Config),
 1185:     {R, Code} = mongooseimctl("register_identified", [Name, Domain, Password], Config),
 1186:     %% then
 1187:     {match, _} = re:run(R, ".*(already registered).*"),
 1188:     true = (Code =/= 0),
 1189:     {_, 0} = mongooseimctl("unregister", [Name, Domain], Config).
 1190: 
 1191: backup_restore_mnesia(Config) ->
 1192:     %% given
 1193:     TableName = passwd,
 1194:     TableSize = rpc_call(mnesia, table_info, [TableName, size]),
 1195:     %% Table passwd should not be empty
 1196:     FileName = "backup_mnesia.bup",
 1197:     %% when
 1198:     {R, 0} = mongooseimctl("backup", [FileName], Config),
 1199:     nomatch = re:run(R, ".+"),
 1200:     rpc_call(mnesia, clear_table, [TableName]),
 1201:     0 = rpc_call(mnesia, table_info, [TableName, size]),
 1202:     {R2, 0} = mongooseimctl("restore", [FileName], Config),
 1203:     %% then
 1204:     nomatch = re:run(R2, ".+"),
 1205:     TableSize = rpc_call(mnesia, table_info, [TableName, size]).
 1206: 
 1207: restore_mnesia_wrong(Config) ->
 1208:     FileName = "file that doesnt exist13123.bup",
 1209:     {R2, _} = mongooseimctl("restore", [FileName], Config),
 1210:     {match, Code} = re:run(R2, ".+"),
 1211:     true = (Code =/= 0).
 1212: 
 1213: dump_and_load(Config) ->
 1214:     FileName = "dump.bup",
 1215:     TableName = passwd,
 1216:     %% Table passwd should not be empty
 1217:     TableSize = rpc_call(mnesia, table_info, [TableName, size]),
 1218:     {DumpReturns, 0} = mongooseimctl("dump", [FileName], Config),
 1219:     ct:log("DumpReturns ~p", [DumpReturns]),
 1220:     {ok, DumpData} = rpc_call(file, consult, [FileName]),
 1221:     ct:log("DumpData ~p", [DumpData]),
 1222:     rpc_call(mnesia, clear_table, [TableName]),
 1223:     0 = rpc_call(mnesia, table_info, [TableName, size]),
 1224:     {R, 0} = mongooseimctl("load", [FileName], Config),
 1225:     ct:log("LoadReturns ~p", [R]),
 1226:     {match, _} = re:run(R, ".+"),
 1227:     TableSize = rpc_call(mnesia, table_info, [TableName, size]).
 1228: 
 1229: load_mnesia_wrong(Config) ->
 1230:     FileName = "file that doesnt existRHCP.bup",
 1231:     {R2, Code} = mongooseimctl("restore", [FileName], Config),
 1232:     {match, _} = re:run(R2, ".+"),
 1233:     true = (Code =/= 0).
 1234: 
 1235: dump_table(Config) ->
 1236:     FileName = "dump.mn",
 1237:     TableName = passwd,
 1238:     %% Table passwd should not be empty
 1239:     TableSize = rpc_call(mnesia, table_info, [TableName, size]),
 1240:     {_, 0} = mongooseimctl("dump_table", [FileName, atom_to_list(TableName)], Config),
 1241:     rpc_call(mnesia, clear_table, [TableName]),
 1242:     0 = rpc_call(mnesia, table_info, [TableName, size]),
 1243:     {R, 0} = mongooseimctl("load", [FileName], Config),
 1244:     {match, _} = re:run(R, ".+"),
 1245:     TableSize = rpc_call(mnesia, table_info, [TableName, size]).
 1246: 
 1247: get_loglevel(Config) ->
 1248:     {R, 0} = mongooseimctl("get_loglevel", [], Config),
 1249:     LogLevel = rpc_call(mongoose_logs, get_global_loglevel, []),
 1250:     Regexp = io_lib:format("global loglevel is \(.\)\{1,2\}, which means '~p'", [LogLevel]),
 1251:     {match, _} = re:run(R, Regexp, [{capture, first}]).
 1252: 
 1253: remove_old_messages_test(Config) ->
 1254:     escalus:story(Config, [{alice, 1}], fun(_) ->
 1255:         %% given
 1256:         JidA = nick_to_jid(alice, Config),
 1257:         JidB = nick_to_jid(bob, Config),
 1258:         JidRecordAlice = jid:from_binary(JidA),
 1259:         JidRecordBob = jid:from_binary(JidB),
 1260:         Domain = domain(),
 1261:         Msg1 = escalus_stanza:chat_to(<<"bob@", Domain/binary>>,
 1262:                                       "Hi, how are you? Its old message!"),
 1263:         Msg2 = escalus_stanza:chat_to(<<"bob@", Domain/binary>>,
 1264:                                       "Hello its new message!"),
 1265:         OldTimestamp = fallback_timestamp(10, os:system_time(microsecond)),
 1266:         OfflineOld = generate_offline_message(JidRecordAlice, JidRecordBob, Msg1, OldTimestamp),
 1267:         OfflineNew = generate_offline_message(JidRecordAlice, JidRecordBob, Msg2, os:system_time(microsecond)),
 1268:         {LUser, LServer} = jid:to_lus(JidRecordBob),
 1269:         HostType = host_type(),
 1270:         rpc_call(mod_offline_backend, write_messages, [host_type(), LUser, LServer, [OfflineOld, OfflineNew]]),
 1271:         %% when
 1272:         {_, 0} = mongooseimctl("delete_old_messages", [LServer, "1"], Config),
 1273:         {ok, SecondList} = rpc_call(mod_offline_backend, pop_messages, [HostType, JidRecordBob]),
 1274:         %% then
 1275:         1 = length(SecondList)
 1276:     end).
 1277: 
 1278: remove_expired_messages_test(Config) ->
 1279:     escalus:story(Config, [{mike, 1}], fun(_) ->
 1280:         %% given
 1281:         JidA = nick_to_jid(mike, Config),
 1282:         JidB = nick_to_jid(kate, Config),
 1283:         JidRecordMike = jid:from_binary(JidA),
 1284:         JidRecordKate = jid:from_binary(JidB),
 1285:         Domain = domain(),
 1286:         Msg1 = escalus_stanza:chat_to(<<"kate@", Domain/binary>>, "Rolling stones"),
 1287:         Msg2 = escalus_stanza:chat_to(<<"kate@", Domain/binary>>, "Arctic monkeys!"),
 1288:         Msg3 = escalus_stanza:chat_to(<<"kate@", Domain/binary>>, "More wine..."),
 1289:         Msg4 = escalus_stanza:chat_to(<<"kate@", Domain/binary>>, "kings of leon"),
 1290:         OldTimestamp = fallback_timestamp(10, os:system_time(microsecond)),
 1291:         ExpirationTime = fallback_timestamp(2, os:system_time(microsecond)),
 1292:         ExpirationTimeFuture= fallback_timestamp(-5, os:system_time(microsecond)),
 1293:         OfflineOld = generate_offline_expired_message(JidRecordMike,
 1294:                                                       JidRecordKate, Msg1,
 1295:                                                       OldTimestamp,
 1296:                                                       ExpirationTime),
 1297:         OfflineNow = generate_offline_expired_message(JidRecordMike,
 1298:                              JidRecordKate, Msg2, os:system_time(microsecond), ExpirationTime),
 1299:         OfflineFuture = generate_offline_expired_message(JidRecordMike,
 1300:                              JidRecordKate, Msg3, os:system_time(microsecond), ExpirationTimeFuture),
 1301:         OfflineFuture2 = generate_offline_expired_message(JidRecordMike,
 1302:                                                           JidRecordKate, Msg4,
 1303:                                                           OldTimestamp,
 1304:                                                           ExpirationTimeFuture),
 1305:         {LUser, LServer} = jid:to_lus(JidRecordKate),
 1306:         Args = [OfflineOld, OfflineNow, OfflineFuture, OfflineFuture2],
 1307:         HostType = host_type(),
 1308:         rpc_call(mod_offline_backend, write_messages, [HostType, LUser, LServer, Args]),
 1309:         %% when
 1310:         {_, 0} = mongooseimctl("delete_expired_messages", [LServer], Config),
 1311:         {ok, SecondList} = rpc_call(mod_offline_backend, pop_messages, [HostType, JidRecordKate]),
 1312:         %% then
 1313:         2 = length(SecondList)
 1314:     end).
 1315: 
 1316: %%-----------------------------------------------------------------
 1317: %% Helpers
 1318: %%-----------------------------------------------------------------
 1319: 
 1320: 
 1321: nick_to_jid(UserName, Config) when is_atom(UserName) ->
 1322:     UserSpec = escalus_users:get_userspec(Config, UserName),
 1323:     escalus_utils:jid_to_lower(escalus_users:get_jid(Config, UserSpec)).
 1324: 
 1325: generate_offline_message(From, To, Msg, TimeStamp) ->
 1326:     {LUser, LServer} = jid:to_lus(To),
 1327:     #offline_msg{us = {LUser, LServer}, timestamp = TimeStamp, expire = never,
 1328:                  from = From, to = To, packet = Msg}.
 1329: 
 1330: generate_offline_expired_message(From, To, Msg, TimeStamp, ExpirationTime) ->
 1331:     {LUser, LServer} = jid:to_lus(To),
 1332:     #offline_msg{us = {LUser, LServer}, timestamp = TimeStamp,
 1333:                  expire = ExpirationTime, from = From, to = To, packet = Msg}.
 1334: 
 1335: 
 1336: fallback_timestamp(HowManyDays, TS_MicroSeconds) ->
 1337:     HowManySeconds = HowManyDays * 86400,
 1338:     HowManyMicroSeconds = erlang:convert_time_unit(HowManySeconds, second, microsecond),
 1339:     TS_MicroSeconds - HowManyMicroSeconds.
 1340: 
 1341: get_user_data(User, Config) when is_atom(User) ->
 1342:     get_user_data(escalus_users:get_options(Config, User, <<"newres">>), Config);
 1343: get_user_data(User, _Config) ->
 1344:     {_, Password} = lists:keyfind(password, 1, User),
 1345:     {_, Username} = lists:keyfind(username, 1, User),
 1346:     {_, Domain} = lists:keyfind(server, 1, User),
 1347:     {Username, Domain, Password}.
 1348: 
 1349: get_md5(AccountPass) ->
 1350:     lists:flatten([io_lib:format("~.16B", [X])
 1351:                    || X <- binary_to_list(crypto:hash(md5, AccountPass))]).
 1352: get_sha(AccountPass) ->
 1353:     lists:flatten([io_lib:format("~.16B", [X])
 1354:                    || X <- binary_to_list(crypto:hash(sha, AccountPass))]).
 1355: 
 1356: set_last(User, Domain, TStamp) ->
 1357:     rpc(mim(), mod_last, store_last_info,
 1358:         [host_type(), escalus_utils:jid_to_lower(User), Domain, TStamp, <<>>]).
 1359: 
 1360: delete_users(_Config) ->
 1361:     lists:foreach(fun({User, Domain}) ->
 1362:                 JID = mongoose_helper:make_jid(User, Domain),
 1363:                 rpc(mim(), ejabberd_auth, remove_user, [JID])
 1364:         end, get_registered_users()).
 1365: 
 1366: %%-----------------------------------------------------------------
 1367: %% Predicates
 1368: %%-----------------------------------------------------------------
 1369: 
 1370: match_user_status(Users, StatusTxt) ->
 1371:     Statuses = string:tokens(StatusTxt, "\n"),
 1372:     true = (length(Users) == length(Statuses)),
 1373:     match_user_status2(Users, Statuses).
 1374: 
 1375: match_user_status2([], _) ->
 1376:     true;
 1377: match_user_status2([User | UserR], Statuses) ->
 1378:     Username = binary_to_list(escalus_client:username(User)),
 1379:     Domain = binary_to_list(escalus_client:server(User)),
 1380:     Resource = binary_to_list(escalus_client:resource(User)),
 1381: 
 1382:     true = lists:any(fun(Status) ->
 1383:                 [Username, Domain, Resource]
 1384:                 =:=
 1385:                 lists:sublist(string:tokens(Status, "\t"), 1, 3)
 1386:         end, Statuses),
 1387:     match_user_status2(UserR, Statuses).
 1388: 
 1389: match_user_info(Users, UsersTxt) ->
 1390:     UsersInfo = string:tokens(UsersTxt, "\n"),
 1391:     case length(Users) == length(UsersInfo) of
 1392:         true ->
 1393:             ok;
 1394:         false ->
 1395:             ct:fail(#{what => match_user_info_failed,
 1396:                       users => Users, user_info => UsersInfo})
 1397:     end,
 1398:     match_user_info2(Users, UsersInfo).
 1399: 
 1400: match_user_info2([], _) ->
 1401:     true;
 1402: match_user_info2([User | UserR], UsersInfo) ->
 1403:     Username = binary_to_list(escalus_client:username(User)),
 1404:     Domain = binary_to_list(escalus_client:server(User)),
 1405:     Resource = binary_to_list(escalus_client:resource(User)),
 1406:     FullJID = Username ++ "@" ++ Domain ++ "/" ++ Resource,
 1407: 
 1408:     true = lists:any(fun(UserInfo) ->
 1409:                 string:str(UserInfo, string:to_lower(FullJID)) =:= 1
 1410:         end, UsersInfo),
 1411:     match_user_info2(UserR, UsersInfo).
 1412: 
 1413: match_roster(ItemsValid, Items) ->
 1414:     ItemsTokens = [ string:tokens(ItemToken, "\t") || ItemToken <- string:tokens(Items, "\n") ],
 1415: 
 1416:     true = (length(ItemsValid) == length(ItemsTokens)),
 1417:     true = lists:all(fun({Username, Domain, _Nick, _Group, _Sub}) ->
 1418:                     JID = escalus_utils:jid_to_lower(<<Username/binary, "@", Domain/binary >>),
 1419:                     lists:any(fun
 1420:                                 ([RosterJID, _Nick, _Sub, "none", _Group]) ->
 1421:                                     JID =:= escalus_utils:jid_to_lower(list_to_binary(RosterJID));
 1422:                                 (_) ->
 1423:                                     false
 1424:                               end, ItemsTokens)
 1425:             end, ItemsValid).
 1426: 
 1427: string_to_binary(List) ->
 1428:     case erlang:system_info(otp_release) of
 1429:         [$R|_] ->
 1430:             list_to_binary(List);
 1431:         _ ->
 1432:             unicode:characters_to_binary(List)
 1433:     end.
 1434: 
 1435: add_rosteritem1(UserName1, Domain, UserName2, Config) ->
 1436:     mongooseimctl("add_rosteritem",
 1437:                 [UserName1, Domain, UserName2,
 1438:                  Domain, "MyBob", "MyGroup", "both"], Config).
 1439: 
 1440: add_rosteritem2(Name1, Domain1, Name2, Domain2, Config) ->
 1441:     mongooseimctl("add_rosteritem",
 1442:                 [Name1, Domain1, Name2,
 1443:                  Domain2, "DearMike", "MyGroup", "both"], Config).