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