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