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