1: %%============================================================================== 2: %% Copyright 2012-2020 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: 17: -module(rest_SUITE). 18: -compile([export_all, nowarn_export_all]). 19: 20: -include_lib("escalus/include/escalus.hrl"). 21: -include_lib("eunit/include/eunit.hrl"). 22: -include_lib("exml/include/exml.hrl"). 23: 24: -import(rest_helper, 25: [assert_inlist/2, 26: assert_notinlist/2, 27: decode_maplist/1, 28: gett/2, 29: gett/3, 30: post/3, 31: putt/3, 32: delete/2] 33: ). 34: -import(domain_helper, [host_type/0, domain/0]). 35: 36: -define(PRT(X, Y), ct:log("~p: ~p", [X, Y])). 37: -define(OK, {<<"200">>, <<"OK">>}). 38: -define(CREATED, {<<"201">>, <<"Created">>}). 39: -define(NOCONTENT, {<<"204">>, <<"No Content">>}). 40: -define(NOT_FOUND, {<<"404">>, _}). 41: -define(NOT_AUTHORIZED, {<<"401">>, _}). 42: -define(FORBIDDEN, {<<"403">>, _}). 43: -define(BAD_REQUEST, {<<"400">>, _}). 44: 45: %%-------------------------------------------------------------------- 46: %% Suite configuration 47: %%-------------------------------------------------------------------- 48: 49: all() -> 50: [ 51: {group, admin}, 52: {group, auth}, 53: {group, blank_auth}, 54: {group, roster} 55: ]. 56: 57: groups() -> 58: [{admin, [parallel], test_cases()}, 59: {auth, [parallel], auth_test_cases()}, 60: {blank_auth, [parallel], blank_auth_testcases()}, 61: {roster, [parallel], roster_test_cases()} 62: ]. 63: 64: auth_test_cases() -> 65: [auth_passes_correct_creds, 66: auth_fails_incorrect_creds]. 67: 68: blank_auth_testcases() -> 69: [auth_passes_without_creds, 70: auth_fails_with_creds]. 71: 72: test_cases() -> 73: [non_existent_command_returns404, 74: existent_command_with_missing_arguments_returns404, 75: invalid_query_string, 76: invalid_request_body, 77: user_can_be_registered_and_removed, 78: user_registration_errors, 79: sessions_are_listed, 80: session_can_be_kicked, 81: session_kick_errors, 82: messages_are_sent_and_received, 83: message_errors, 84: stanzas_are_sent_and_received, 85: stanza_errors, 86: messages_are_archived, 87: message_archive_errors, 88: messages_can_be_paginated, 89: password_can_be_changed, 90: password_change_errors 91: ]. 92: 93: roster_test_cases() -> 94: [list_contacts, 95: befriend_and_alienate, 96: befriend_and_alienate_auto, 97: list_contacts_errors, 98: add_contact_errors, 99: subscription_errors, 100: delete_contact_errors]. 101: 102: suite() -> 103: escalus:suite(). 104: 105: %%-------------------------------------------------------------------- 106: %% Init & teardown 107: %%-------------------------------------------------------------------- 108: 109: init_per_suite(Config) -> 110: Config1 = dynamic_modules:save_modules(host_type(), Config), 111: Config2 = rest_helper:maybe_enable_mam(mam_helper:backend(), host_type(), Config1), 112: Config3 = ejabberd_node_utils:init(Config2), 113: escalus:init_per_suite(Config3). 114: 115: end_per_suite(Config) -> 116: escalus_fresh:clean(), 117: dynamic_modules:restore_modules(Config), 118: escalus:end_per_suite(Config). 119: 120: init_per_group(auth, Config) -> 121: rest_helper:change_admin_creds({<<"ala">>, <<"makota">>}), 122: Config; 123: init_per_group(blank_auth, Config) -> 124: rest_helper:change_admin_creds(any), 125: Config; 126: init_per_group(_GroupName, Config) -> 127: escalus:create_users(Config, escalus:get_users([alice, bob])). 128: 129: end_per_group(auth, _Config) -> 130: rest_helper:change_admin_creds(any); 131: end_per_group(_GroupName, Config) -> 132: escalus:delete_users(Config, escalus:get_users([alice, bob, mike])). 133: 134: init_per_testcase(CaseName, Config) -> 135: MAMTestCases = [messages_are_archived, message_archive_errors, messages_can_be_paginated], 136: rest_helper:maybe_skip_mam_test_cases(CaseName, MAMTestCases, Config). 137: 138: end_per_testcase(CaseName, Config) -> 139: escalus:end_per_testcase(CaseName, Config). 140: 141: rpc(M, F, A) -> 142: distributed_helper:rpc(distributed_helper:mim(), M, F, A). 143: 144: %%-------------------------------------------------------------------- 145: %% Tests 146: %%-------------------------------------------------------------------- 147: 148: % Authorization 149: auth_passes_correct_creds(_Config) -> 150: % try to login with the same creds 151: {?OK, _Users} = gett(admin, path("users", [domain()]), {<<"ala">>, <<"makota">>}). 152: 153: auth_fails_incorrect_creds(_Config) -> 154: % try to login with different creds 155: {?NOT_AUTHORIZED, _} = gett(admin, path("users", [domain()]), {<<"ola">>, <<"mapsa">>}). 156: 157: auth_passes_without_creds(_Config) -> 158: % try with no auth 159: {?OK, _Users} = gett(admin, path("users", [domain()])). 160: 161: auth_fails_with_creds(_Config) -> 162: % try with any auth 163: {?NOT_AUTHORIZED, _} = gett(admin, path("users", [domain()]), {<<"aaaa">>, <<"bbbb">>}). 164: 165: non_existent_command_returns404(_C) -> 166: {?NOT_FOUND, _} = gett(admin, <<"/isitthereornot">>). 167: 168: existent_command_with_missing_arguments_returns404(_C) -> 169: {?NOT_FOUND, _} = gett(admin, <<"/contacts/">>). 170: 171: invalid_query_string(Config) -> 172: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 173: AliceJid = escalus_users:get_jid(Config1, alice), 174: BobJid = escalus_users:get_jid(Config1, bob), 175: {?BAD_REQUEST, <<"Invalid query string">>} = 176: gett(admin, <<"/messages/", AliceJid/binary, "/", BobJid/binary, "?kukurydza">>). 177: 178: invalid_request_body(_Config) -> 179: {?BAD_REQUEST, <<"Invalid request body">>} = post(admin, path("users"), <<"kukurydza">>). 180: 181: user_can_be_registered_and_removed(_Config) -> 182: % list users 183: {?OK, Lusers} = gett(admin, path("users")), 184: Domain = domain(), 185: assert_inlist(<<"alice@", Domain/binary>>, Lusers), 186: % create user 187: CrUser = #{username => <<"mike">>, password => <<"nicniema">>}, 188: {?CREATED, _} = post(admin, path("users"), CrUser), 189: {?OK, Lusers1} = gett(admin, path("users")), 190: assert_inlist(<<"mike@", Domain/binary>>, Lusers1), 191: % try to create the same user 192: {?FORBIDDEN, _} = post(admin, path("users"), CrUser), 193: % delete user 194: {?NOCONTENT, _} = delete(admin, path("users", ["mike"])), 195: {?OK, Lusers2} = gett(admin, path("users")), 196: assert_notinlist(<<"mike@", Domain/binary>>, Lusers2). 197: 198: user_registration_errors(_Config) -> 199: {AnonUser, AnonDomain} = anon_us(), 200: {?BAD_REQUEST, <<"Invalid JID", _/binary>>} = 201: post(admin, path("users"), #{username => <<"m@ke">>, password => <<"nicniema">>}), 202: {?BAD_REQUEST, <<"Missing password", _/binary>>} = 203: post(admin, path("users"), #{username => <<"mike">>}), 204: {?BAD_REQUEST, <<"Missing user name", _/binary>>} = 205: post(admin, path("users"), #{password => <<"nicniema">>}), 206: {?FORBIDDEN, <<"Can't register user", _/binary>>} = 207: post(admin, path("users"), #{username => <<"mike">>, password => <<>>}), 208: {?FORBIDDEN, <<"Can't register user", _/binary>>} = 209: post(admin, <<"/users/", AnonDomain/binary>>, #{username => AnonUser, 210: password => <<"secret">>}), 211: {?FORBIDDEN, <<"User does not exist or you are not authorized properly">>} = 212: delete(admin, <<"/users/", AnonDomain/binary, "/", AnonUser/binary>>), 213: {?BAD_REQUEST, <<"Invalid JID", _/binary>>} = 214: delete(admin, path("users", ["@mike"])). 215: 216: sessions_are_listed(_) -> 217: % no session 218: {?OK, Sessions} = gett(admin, path("sessions")), 219: true = is_list(Sessions). 220: 221: session_can_be_kicked(Config) -> 222: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 223: % Alice is connected 224: AliceJid = jid:nameprep(escalus_client:full_jid(Alice)), 225: AliceSessionPath = <<"/sessions/", (escalus_client:server(Alice))/binary, 226: "/", (escalus_client:username(Alice))/binary, 227: "/", (escalus_client:resource(Alice))/binary>>, 228: {?OK, Sessions1} = gett(admin, path("sessions")), 229: assert_inlist(AliceJid, Sessions1), 230: % kick alice 231: % mongoose_c2s:exit is an async operation 232: {?NOCONTENT, _} = delete(admin, AliceSessionPath), 233: escalus:wait_for_stanza(Alice), 234: true = escalus_connection:wait_for_close(Alice, timer:seconds(1)), 235: mongoose_helper:wait_until( 236: fun() -> 237: {?OK, Sessions2} = gett(admin, path("sessions")), 238: lists:member(AliceJid, Sessions2) 239: end, false), 240: {?NOT_FOUND, <<"No active session">>} = delete(admin, AliceSessionPath), 241: ok 242: end). 243: 244: session_kick_errors(_Config) -> 245: {?BAD_REQUEST, <<"Missing user name">>} = 246: delete(admin, <<"/sessions/", (domain())/binary>>), 247: %% Resource is matched first, because Cowboy matches path elements from the right 248: {?BAD_REQUEST, <<"Missing user name">>} = 249: delete(admin, <<"/sessions/", (domain())/binary, "/resource">>). 250: 251: messages_are_sent_and_received(Config) -> 252: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 253: {M1, M2} = send_messages(Alice, Bob), 254: Res = escalus:wait_for_stanza(Alice), 255: escalus:assert(is_chat_message, [maps:get(body, M1)], Res), 256: Res1 = escalus:wait_for_stanza(Bob), 257: escalus:assert(is_chat_message, [maps:get(body, M2)], Res1) 258: end). 259: 260: message_errors(Config) -> 261: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 262: AliceJID = escalus_users:get_jid(Config1, alice), 263: BobJID = escalus_users:get_jid(Config1, bob), 264: {?BAD_REQUEST, <<"Missing sender JID">>} = 265: post(admin, "/messages", #{to => BobJID, body => <<"whatever">>}), 266: {?BAD_REQUEST, <<"Missing recipient JID">>} = 267: post(admin, "/messages", #{caller => AliceJID, body => <<"whatever">>}), 268: {?BAD_REQUEST, <<"Missing message body">>} = 269: post(admin, "/messages", #{caller => AliceJID, to => BobJID}), 270: {?BAD_REQUEST, <<"Invalid recipient JID">>} = 271: send_message_bin(AliceJID, <<"@noway">>), 272: {?BAD_REQUEST, <<"Invalid sender JID">>} = 273: send_message_bin(<<"@noway">>, BobJID), 274: {?BAD_REQUEST, <<"User does not exist">>} = 275: send_message_bin(<<"baduser@", (domain())/binary>>, BobJID), 276: {?BAD_REQUEST, <<"User's domain does not exist">>} = 277: send_message_bin(<<"baduser@baddomain">>, BobJID). 278: 279: stanzas_are_sent_and_received(Config) -> 280: %% this is to test the API for sending arbitrary stanzas, e.g. message with extra elements 281: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 282: AliceJid = escalus_client:full_jid(Alice), 283: BobJid = escalus_client:full_jid(Bob), 284: Stanza = extended_message([{<<"from">>, AliceJid}, {<<"to">>, BobJid}]), 285: {?NOCONTENT, _} = send_stanza(Stanza), 286: Res = escalus:wait_for_stanza(Bob), 287: ?assertEqual(<<"attribute">>, exml_query:attr(Res, <<"extra">>)), 288: ?assertEqual(<<"inside the sibling">>, exml_query:path(Res, [{element, <<"sibling">>}, cdata])) 289: end). 290: 291: stanza_errors(Config) -> 292: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 293: AliceJid = escalus_users:get_jid(Config1, alice), 294: BobJid = escalus_users:get_jid(Config1, bob), 295: UnknownJid = <<"baduser@", (domain())/binary>>, 296: {?BAD_REQUEST, <<"Missing recipient JID">>} = 297: send_stanza(extended_message([{<<"from">>, AliceJid}])), 298: {?BAD_REQUEST, <<"Missing sender JID">>} = 299: send_stanza(extended_message([{<<"to">>, BobJid}])), 300: {?BAD_REQUEST, <<"Invalid recipient JID">>} = 301: send_stanza(extended_message([{<<"from">>, AliceJid}, {<<"to">>, <<"@invalid">>}])), 302: {?BAD_REQUEST, <<"Invalid sender JID">>} = 303: send_stanza(extended_message([{<<"from">>, <<"@invalid">>}, {<<"to">>, BobJid}])), 304: {?BAD_REQUEST, <<"User's domain does not exist">>} = 305: send_stanza(extended_message([{<<"from">>, <<"baduser@baddomain">>}, {<<"to">>, BobJid}])), 306: {?BAD_REQUEST, <<"User does not exist">>} = 307: send_stanza(extended_message([{<<"from">>, UnknownJid}, {<<"to">>, BobJid}])), 308: {?BAD_REQUEST, <<"Malformed stanza">>} = 309: send_stanza(broken_message([{<<"from">>, AliceJid}, {<<"to">>, BobJid}])), 310: {?BAD_REQUEST, <<"Missing stanza">>} = 311: post(admin, <<"/stanzas">>, #{}). 312: 313: messages_are_archived(Config) -> 314: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 315: {M1, _M2} = send_messages(Alice, Bob), 316: AliceJID = maps:get(to, M1), 317: BobJID = maps:get(caller, M1), 318: GetPath = lists:flatten(["/messages", 319: "/", binary_to_list(AliceJID), 320: "/", binary_to_list(BobJID), 321: "?limit=10"]), 322: mam_helper:maybe_wait_for_archive(Config), 323: {?OK, Msgs} = gett(admin, GetPath), 324: [Last, Previous|_] = lists:reverse(decode_maplist(Msgs)), 325: <<"hello from Alice">> = maps:get(body, Last), 326: AliceJID = maps:get(sender, Last), 327: <<"hello from Bob">> = maps:get(body, Previous), 328: BobJID = maps:get(sender, Previous), 329: % now if we leave limit out we should get the same result 330: GetPath1 = lists:flatten(["/messages", 331: "/", binary_to_list(AliceJID), 332: "/", binary_to_list(BobJID)]), 333: mam_helper:maybe_wait_for_archive(Config), 334: {?OK, Msgs1} = gett(admin, GetPath1), 335: [Last1, Previous1|_] = lists:reverse(decode_maplist(Msgs1)), 336: <<"hello from Alice">> = maps:get(body, Last1), 337: AliceJID = maps:get(sender, Last1), 338: <<"hello from Bob">> = maps:get(body, Previous1), 339: BobJID = maps:get(sender, Previous1), 340: % and we can do the same without specifying contact 341: GetPath2 = lists:flatten(["/messages/", binary_to_list(AliceJID)]), 342: mam_helper:maybe_wait_for_archive(Config), 343: {?OK, Msgs2} = gett(admin, GetPath2), 344: [Last2, Previous2|_] = lists:reverse(decode_maplist(Msgs2)), 345: <<"hello from Alice">> = maps:get(body, Last2), 346: AliceJID = maps:get(sender, Last2), 347: <<"hello from Bob">> = maps:get(body, Previous2), 348: BobJID = maps:get(sender, Previous2) 349: end). 350: 351: message_archive_errors(Config) -> 352: Config1 = escalus_fresh:create_users(Config, [{alice, 1}]), 353: User = binary_to_list(escalus_users:get_username(Config1, alice)), 354: Domain = binary_to_list(domain_helper:domain()), 355: {?NOT_FOUND, <<"Missing owner JID">>} = 356: gett(admin, "/messages"), 357: {?BAD_REQUEST, <<"Invalid owner JID">>} = 358: gett(admin, "/messages/@invalid"), 359: {?BAD_REQUEST, <<"User does not exist">>} = 360: gett(admin, "/messages/baduser@" ++ Domain), 361: {?BAD_REQUEST, <<"Invalid interlocutor JID">>} = 362: gett(admin, "/messages/" ++ User ++ "/@invalid"), 363: {?BAD_REQUEST, <<"Invalid limit">>} = 364: gett(admin, "/messages/" ++ User ++ "?limit=x"), 365: {?BAD_REQUEST, <<"Invalid value of 'before'">>} = 366: gett(admin, "/messages/" ++ User ++ "?before=x"). 367: 368: messages_can_be_paginated(Config) -> 369: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 370: AliceJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Alice)), 371: BobJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)), 372: rest_helper:fill_archive(Alice, Bob), 373: mam_helper:maybe_wait_for_archive(Config), 374: % recent msgs with a limit 375: M1 = get_messages(AliceJID, BobJID, 10), 376: ?assertEqual(6, length(M1)), 377: M2 = get_messages(AliceJID, BobJID, 3), 378: ?assertEqual(3, length(M2)), 379: % older messages - earlier then the previous midnight 380: PriorTo = rest_helper:make_timestamp(-1, {0, 0, 1}) div 1000, 381: M3 = get_messages(AliceJID, BobJID, PriorTo, 10), 382: ?assertEqual(4, length(M3)), 383: [Oldest|_] = decode_maplist(M3), 384: ?assertEqual(maps:get(body, Oldest), <<"A">>), 385: % same with limit 386: M4 = get_messages(AliceJID, BobJID, PriorTo, 2), 387: ?assertEqual(2, length(M4)), 388: [Oldest2|_] = decode_maplist(M4), 389: ?assertEqual(maps:get(body, Oldest2), <<"B">>), 390: ok 391: end). 392: 393: password_can_be_changed(Config) -> 394: % bob logs in with his regular password 395: escalus:story(Config, [{bob, 1}], fun(#client{} = _Bob) -> 396: skip 397: end), 398: % we change password 399: NewPass = <<"niemakrolika">>, 400: {?NOCONTENT, _} = putt(admin, path("users", ["bob"]), 401: #{newpass => NewPass}), 402: % he logs with his alternative password 403: ConfigWithBobsAltPass = escalus_users:update_userspec(Config, bob, password, NewPass), 404: escalus:story(ConfigWithBobsAltPass, [{bob, 1}], fun(#client{} = _Bob) -> 405: ignore 406: end), 407: % we can't log with regular passwd anymore 408: try escalus:story(Config, [{bob, 1}], fun(Bob) -> ?PRT("Bob", Bob) end) of 409: _ -> ct:fail("bob connected with old password") 410: catch error:{badmatch, _} -> 411: ok 412: end, 413: % we change it back 414: {?NOCONTENT, _} = putt(admin, path("users", ["bob"]), 415: #{newpass => <<"makrolika">>}), 416: % now he logs again with the regular one 417: escalus:story(Config, [{bob, 1}], fun(#client{} = _Bob) -> 418: just_dont_do_anything 419: end). 420: 421: password_change_errors(Config) -> 422: Alice = binary_to_list(escalus_users:get_username(Config, alice)), 423: {AnonUser, AnonDomain} = anon_us(), 424: Args = #{newpass => <<"secret">>}, 425: {?FORBIDDEN, <<"User does not exist or you are not authorized properly">>} = 426: putt(admin, <<"/users/", AnonDomain/binary, "/", AnonUser/binary>>, Args), 427: {?BAD_REQUEST, <<"Missing user name">>} = 428: putt(admin, path("users", []), Args), 429: {?BAD_REQUEST, <<"Missing new password">>} = 430: putt(admin, path("users", [Alice]), #{}), 431: {?BAD_REQUEST, <<"Empty password">>} = 432: putt(admin, path("users", [Alice]), #{newpass => <<>>}), 433: {?BAD_REQUEST, <<"Invalid JID">>} = 434: putt(admin, path("users", ["@invalid"]), Args). 435: 436: anon_us() -> 437: AnonConfig = [{escalus_users, escalus_ct:get_config(escalus_anon_users)}], 438: AnonDomain = escalus_users:get_server(AnonConfig, jon), 439: AnonUser = escalus_users:get_username(AnonConfig, jon), 440: {AnonUser, AnonDomain}. 441: 442: list_contacts(Config) -> 443: escalus:fresh_story( 444: Config, [{alice, 1}, {bob, 1}], 445: fun(Alice, Bob) -> 446: AliceJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Alice)), 447: BobJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)), 448: add_sample_contact(Bob, Alice), 449: % list bob's contacts 450: {?OK, R} = gett(admin, lists:flatten(["/contacts/", binary_to_list(BobJID)])), 451: [R1] = decode_maplist(R), 452: #{jid := AliceJID, subscription := <<"none">>, ask := <<"none">>} = R1, 453: ok 454: end 455: ), 456: ok. 457: 458: befriend_and_alienate(Config) -> 459: escalus:fresh_story( 460: Config, [{alice, 1}, {bob, 1}], 461: fun(Alice, Bob) -> 462: AliceJID = escalus_utils:jid_to_lower( 463: escalus_client:short_jid(Alice)), 464: BobJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)), 465: AliceS = binary_to_list(AliceJID), 466: BobS = binary_to_list(BobJID), 467: AlicePath = lists:flatten(["/contacts/", AliceS]), 468: BobPath = lists:flatten(["/contacts/", BobS]), 469: % rosters are empty 470: check_roster_empty(AlicePath), 471: check_roster_empty(BobPath), 472: % adds them to rosters 473: {?NOCONTENT, _} = post(admin, AlicePath, #{jid => BobJID}), 474: {?NOCONTENT, _} = post(admin, AlicePath, #{jid => BobJID}), % it is idempotent 475: {?NOCONTENT, _} = post(admin, BobPath, #{jid => AliceJID}), 476: check_roster(BobPath, AliceJID, none, none), 477: check_roster(AlicePath, BobJID, none, none), 478: % now do the subscription sequence 479: PutPathA = lists:flatten([AlicePath, "/", BobS]), 480: {?NOCONTENT, _} = putt(admin, PutPathA, #{action => <<"subscribe">>}), 481: check_roster(AlicePath, BobJID, none, out), 482: PutPathB = lists:flatten([BobPath, "/", AliceS]), 483: {?NOCONTENT, _} = putt(admin, PutPathB, #{action => <<"subscribed">>}), 484: check_roster(AlicePath, BobJID, to, none), 485: check_roster(BobPath, AliceJID, from, none), 486: {?NOCONTENT, _} = putt(admin, PutPathB, #{action => <<"subscribe">>}), 487: check_roster(BobPath, AliceJID, from, out), 488: {?NOCONTENT, _} = putt(admin, PutPathA, #{action => <<"subscribed">>}), 489: check_roster(AlicePath, BobJID, both, none), 490: check_roster(BobPath, AliceJID, both, none), 491: % now remove 492: {?NOCONTENT, _} = delete(admin, PutPathA), 493: check_roster_empty(AlicePath), 494: check_roster(BobPath, AliceJID, none, none), 495: {?NOCONTENT, _} = delete(admin, PutPathB), 496: check_roster_empty(BobPath), 497: APushes = lists:filter(fun escalus_pred:is_roster_set/1, 498: escalus:wait_for_stanzas(Alice, 20)), 499: AExp = [{none, none}, 500: {none, subscribe}, 501: {to, none}, 502: {both, none}, 503: {remove, none}], 504: check_pushlist(AExp, APushes), 505: BPushes = lists:filter(fun escalus_pred:is_roster_set/1, 506: escalus:wait_for_stanzas(Bob, 20)), 507: BExp = [{none, none}, 508: {from, none}, 509: {from, subscribe}, 510: {both, none}, 511: {to, none}, 512: {none, none}, 513: {remove, none}], 514: check_pushlist(BExp, BPushes), 515: ok 516: end 517: ), 518: ok. 519: 520: 521: befriend_and_alienate_auto(Config) -> 522: escalus:fresh_story( 523: Config, [{alice, 1}, {bob, 1}], 524: fun(Alice, Bob) -> 525: AliceJID = escalus_utils:jid_to_lower( 526: escalus_client:short_jid(Alice)), 527: BobJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)), 528: AliceS = binary_to_list(AliceJID), 529: BobS = binary_to_list(BobJID), 530: AlicePath = lists:flatten(["/contacts/", AliceS]), 531: BobPath = lists:flatten(["/contacts/", BobS]), 532: check_roster_empty(AlicePath), 533: check_roster_empty(BobPath), 534: ManagePath = lists:flatten(["/contacts/", 535: AliceS, 536: "/", 537: BobS, 538: "/manage" 539: ]), 540: {?NOCONTENT, _} = putt(admin, ManagePath, #{action => <<"connect">>}), 541: check_roster(AlicePath, BobJID, both, none), 542: check_roster(BobPath, AliceJID, both, none), 543: {?NOCONTENT, _} = putt(admin, ManagePath, #{action => <<"disconnect">>}), 544: check_roster_empty(AlicePath), 545: check_roster_empty(BobPath), 546: APushes = lists:filter(fun escalus_pred:is_roster_set/1, 547: escalus:wait_for_stanzas(Alice, 20)), 548: ct:log("APushes: ~p", [APushes]), 549: AExp = [{none, none}, 550: {both, none}, 551: {remove, none}], 552: check_pushlist(AExp, APushes), 553: BPushes = lists:filter(fun escalus_pred:is_roster_set/1, 554: escalus:wait_for_stanzas(Bob, 20)), 555: ct:log("BPushes: ~p", [BPushes]), 556: BExp = [{none, none}, 557: {both, none}, 558: {remove, none}], 559: check_pushlist(BExp, BPushes), 560: ok 561: end 562: ), 563: ok. 564: 565: list_contacts_errors(_Config) -> 566: {?NOT_FOUND, <<"Domain not found">>} = gett(admin, contacts_path("baduser@baddomain")). 567: 568: add_contact_errors(Config) -> 569: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 570: BobJID = escalus_users:get_jid(Config, bob), 571: AliceS = binary_to_list(escalus_users:get_jid(Config1, alice)), 572: DomainS = binary_to_list(domain()), 573: {?BAD_REQUEST, <<"Missing JID">>} = 574: post(admin, contacts_path(AliceS), #{}), 575: {?BAD_REQUEST, <<"Invalid JID">>} = 576: post(admin, contacts_path(AliceS), #{jid => <<"@invalidjid">>}), 577: {?BAD_REQUEST, <<"Invalid user JID">>} = 578: post(admin, contacts_path("@invalid_jid"), #{jid => BobJID}), 579: {?NOT_FOUND, <<"The user baduser@", _/binary>>} = 580: post(admin, contacts_path("baduser@" ++ DomainS), #{jid => BobJID}), 581: {?NOT_FOUND, <<"Domain not found">>} = 582: post(admin, contacts_path("baduser@baddomain"), #{jid => BobJID}). 583: 584: subscription_errors(Config) -> 585: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 586: AliceS = binary_to_list(escalus_users:get_jid(Config1, alice)), 587: BobS = binary_to_list(escalus_users:get_jid(Config1, bob)), 588: DomainS = binary_to_list(domain()), 589: {?BAD_REQUEST, <<"Invalid contact JID">>} = 590: putt(admin, contacts_path(AliceS, "@invalid_jid"), #{action => <<"subscribe">>}), 591: {?BAD_REQUEST, <<"Invalid user JID">>} = 592: putt(admin, contacts_path("@invalid_jid", BobS), #{action => <<"subscribe">>}), 593: {?BAD_REQUEST, <<"Missing action">>} = 594: putt(admin, contacts_path(AliceS, BobS), #{}), 595: {?BAD_REQUEST, <<"Missing action">>} = 596: putt(admin, contacts_manage_path(AliceS, BobS), #{}), 597: {?BAD_REQUEST, <<"Invalid action">>} = 598: putt(admin, contacts_path(AliceS, BobS), #{action => <<"something stupid">>}), 599: {?BAD_REQUEST, <<"Invalid action">>} = 600: putt(admin, contacts_manage_path(AliceS, BobS), #{action => <<"off with his head">>}), 601: {?BAD_REQUEST, <<"Invalid user JID">>} = 602: putt(admin, contacts_manage_path("@invalid", BobS), #{action => <<"connect">>}), 603: {?BAD_REQUEST, <<"Invalid contact JID">>} = 604: putt(admin, contacts_manage_path(AliceS, "@bzzz"), #{action => <<"connect">>}), 605: {?NOT_FOUND, <<"The user baduser@baddomain does not exist">>} = 606: putt(admin, contacts_manage_path(AliceS, "baduser@baddomain"), #{action => <<"connect">>}), 607: {?NOT_FOUND, <<"Domain not found">>} = 608: putt(admin, contacts_manage_path("baduser@baddomain", AliceS), #{action => <<"connect">>}), 609: {?NOT_FOUND, <<"Cannot remove", _/binary>>} = 610: putt(admin, contacts_manage_path(AliceS, "baduser@" ++ DomainS), #{action => <<"disconnect">>}). 611: 612: delete_contact_errors(Config) -> 613: Config1 = escalus_fresh:create_users(Config, [{alice, 1}]), 614: AliceS = binary_to_list(escalus_users:get_jid(Config1, alice)), 615: DomainS = binary_to_list(domain()), 616: {?NOT_FOUND, <<"Cannot remove", _/binary>>} = 617: delete(admin, contacts_path(AliceS, "baduser@" ++ DomainS)), 618: {?BAD_REQUEST, <<"Missing contact JID">>} = 619: delete(admin, contacts_path(AliceS)). 620: 621: %%-------------------------------------------------------------------- 622: %% Helpers 623: %%-------------------------------------------------------------------- 624: 625: contacts_path(UserJID) -> 626: "/contacts/" ++ UserJID. 627: 628: contacts_path(UserJID, ContactJID) -> 629: contacts_path(UserJID) ++ "/" ++ ContactJID. 630: 631: contacts_manage_path(UserJID, ContactJID) -> 632: contacts_path(UserJID, ContactJID) ++ "/manage". 633: 634: send_messages(Alice, Bob) -> 635: AliceJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Alice)), 636: BobJID = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)), 637: M = #{caller => BobJID, to => AliceJID, body => <<"hello from Bob">>}, 638: {?NOCONTENT, _} = post(admin, <<"/messages">>, M), 639: M1 = #{caller => AliceJID, to => BobJID, body => <<"hello from Alice">>}, 640: {?NOCONTENT, _} = post(admin, <<"/messages">>, M1), 641: {M, M1}. 642: 643: send_message_bin(BFrom, BTo) -> 644: % this is to trigger invalid jid errors 645: M = #{caller => BFrom, to => BTo, body => <<"whatever">>}, 646: post(admin, <<"/messages">>, M). 647: 648: send_stanza(StanzaBin) -> 649: post(admin, <<"/stanzas">>, #{stanza => StanzaBin}). 650: 651: broken_message(Attrs) -> 652: remove_last_character(extended_message(Attrs)). 653: 654: remove_last_character(Bin) -> 655: binary:part(Bin, 0, byte_size(Bin) - 1). 656: 657: extended_message(Attrs) -> 658: M = #xmlel{name = <<"message">>, 659: attrs = [{<<"extra">>, <<"attribute">>} | Attrs], 660: children = [#xmlel{name = <<"body">>, 661: children = [#xmlcdata{content = <<"the body">>}]}, 662: #xmlel{name = <<"sibling">>, 663: children = [#xmlcdata{content = <<"inside the sibling">>}]} 664: ] 665: }, 666: exml:to_binary(M). 667: 668: check_roster(Path, Jid, Subs, Ask) -> 669: {?OK, R} = gett(admin, Path), 670: S = atom_to_binary(Subs, latin1), 671: A = atom_to_binary(Ask, latin1), 672: Res = decode_maplist(R), 673: [#{jid := Jid, subscription := S, ask := A}] = Res. 674: 675: check_roster_empty(Path) -> 676: {?OK, R} = gett(admin, Path), 677: [] = decode_maplist(R). 678: 679: get_messages(Me, Other, Count) -> 680: GetPath = lists:flatten(["/messages/", 681: binary_to_list(Me), 682: "/", binary_to_list(Other), 683: "?limit=", integer_to_list(Count)]), 684: {?OK, Msgs} = gett(admin, GetPath), 685: Msgs. 686: 687: get_messages(Me, Other, Before, Count) -> 688: GetPath = lists:flatten(["/messages/", 689: binary_to_list(Me), 690: "/", binary_to_list(Other), 691: "?before=", integer_to_list(Before), 692: "&limit=", integer_to_list(Count)]), 693: {?OK, Msgs} = gett(admin, GetPath), 694: Msgs. 695: 696: to_list(V) when is_binary(V) -> 697: binary_to_list(V); 698: to_list(V) when is_list(V) -> 699: V. 700: 701: add_sample_contact(Bob, Alice) -> 702: escalus:send(Bob, escalus_stanza:roster_add_contact(Alice, 703: [<<"friends">>], 704: <<"Alicja">>)), 705: Received = escalus:wait_for_stanzas(Bob, 2), 706: escalus:assert_many([is_roster_set, is_iq_result], Received), 707: Result = hd([R || R <- Received, escalus_pred:is_roster_set(R)]), 708: escalus:assert(count_roster_items, [1], Result), 709: escalus:send(Bob, escalus_stanza:iq_result(Result)). 710: 711: check_pushlist([], _Stanzas) -> 712: ok; 713: check_pushlist(Expected, []) -> 714: ?assertEqual(Expected, []); 715: check_pushlist(Expected, [Iq|StanzaTail]) -> 716: [{ExpectedSub, ExpectedAsk}| TailExp] = Expected, 717: case does_push_match(Iq, ExpectedSub, ExpectedAsk) of 718: true -> 719: check_pushlist(TailExp, StanzaTail); 720: false -> 721: check_pushlist(Expected, StanzaTail) 722: end. 723: 724: does_push_match(Iq, ExpectedSub, ExpectedAsk) -> 725: [Subs] = exml_query:paths(Iq, [{element, <<"query">>}, 726: {element, <<"item">>}, 727: {attr, <<"subscription">>}]), 728: AskList = exml_query:paths(Iq, [{element, <<"query">>}, 729: {element, <<"item">>}, 730: {attr, <<"ask">>}]), 731: Ask = case AskList of 732: [] -> <<"none">>; 733: [A] -> A 734: end, 735: ESub = atom_to_binary(ExpectedSub, latin1), 736: EAsk = atom_to_binary(ExpectedAsk, latin1), 737: {Subs, Ask} == {ESub, EAsk}. 738: 739: path(Category) -> 740: path(Category, []). 741: 742: path(Category, Items) -> 743: DomainStr = binary_to_list(domain()), 744: string:join(["", Category, DomainStr | Items], "/").