1: %%============================================================================== 2: %% Copyright 2016 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: %% Author: Joseph Yiasemides <joseph.yiasemides@erlang-solutions.com> 17: %% Description: Test HTTP Administration API for Mult-user Chat (MUC) 18: %%============================================================================== 19: 20: -module(muc_http_api_SUITE). 21: -compile([export_all, nowarn_export_all]). 22: 23: -include_lib("escalus/include/escalus.hrl"). 24: -include_lib("escalus/include/escalus_xmlns.hrl"). 25: -include_lib("common_test/include/ct.hrl"). 26: -include_lib("eunit/include/eunit.hrl"). 27: -include_lib("exml/include/exml.hrl"). 28: 29: -import(domain_helper, [domain/0]). 30: 31: %%-------------------------------------------------------------------- 32: %% Suite configuration 33: %%-------------------------------------------------------------------- 34: 35: all() -> 36: [{group, positive}, 37: {group, negative}]. 38: 39: groups() -> 40: G = [{positive, [parallel], success_response() ++ complex()}, 41: {negative, [parallel], failure_response()}], 42: ct_helper:repeat_all_until_all_ok(G). 43: 44: success_response() -> 45: [ 46: create_room, 47: invite_online_user_to_room, 48: kick_user_from_room, 49: %% invite_offline_user_to_room, %% TO DO. 50: send_message_to_room 51: ]. 52: 53: complex() -> 54: [ 55: multiparty_multiprotocol 56: ]. 57: 58: failure_response() -> 59: [failed_invites, 60: failed_messages]. 61: 62: %%-------------------------------------------------------------------- 63: %% Init & teardown 64: %%-------------------------------------------------------------------- 65: 66: init_per_suite(Config) -> 67: muc_helper:load_muc(), 68: escalus:init_per_suite(Config). 69: 70: end_per_suite(Config) -> 71: muc_helper:unload_muc(), 72: escalus_fresh:clean(), 73: escalus:end_per_suite(Config). 74: 75: init_per_group(_GroupName, Config) -> 76: escalus:create_users(Config, escalus:get_users([alice, bob, kate])). 77: 78: end_per_group(_GroupName, Config) -> 79: escalus:delete_users(Config, escalus:get_users([alice, bob, kate])). 80: 81: init_per_testcase(CaseName, Config0) -> 82: Config1 = [{room_name, make_distinct_name(<<"wonderland">>)}|Config0], 83: escalus:init_per_testcase(CaseName, Config1). 84: 85: end_per_testcase(CaseName, Config) -> 86: muc_helper:destroy_room(muc_helper:muc_host(), ?config(room_name, Config)), 87: escalus:end_per_testcase(CaseName, Config). 88: 89: 90: %%-------------------------------------------------------------------- 91: %% Tests 92: %%-------------------------------------------------------------------- 93: 94: create_room(Config) -> 95: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 96: Path = path([]), 97: Name = ?config(room_name, Config), 98: Body = #{name => Name, 99: owner => escalus_client:short_jid(Alice), 100: nick => <<"ali">>}, 101: Res = rest_helper:make_request(#{role => admin, 102: method => <<"POST">>, 103: path => Path, 104: body => Body, 105: return_headers => true}), 106: {{<<"201">>, _}, Headers, Name} = Res, 107: Exp = <<"/api", (path([Name]))/binary>>, 108: Uri = uri_string:parse(proplists:get_value(<<"location">>, Headers)), 109: ?assertEqual(Exp, maps:get(path, Uri)), 110: %% Service acknowledges room creation (10.1.1 Ex. 154), then 111: %% (presumably 7.2.16) sends room subject, finally the IQ 112: %% result of the IQ request (10.1.2) for an instant room. The 113: %% stanza for 7.2.16 has a BODY element which it shouldn't. 114: escalus:wait_for_stanzas(Alice, 3), 115: escalus:send(Alice, stanza_get_rooms()), 116: Stanza = escalus:wait_for_stanza(Alice), 117: true = has_room(muc_helper:room_address(Name), Stanza), 118: escalus:assert(is_stanza_from, [muc_helper:muc_host()], Stanza) 119: end). 120: 121: invite_online_user_to_room(Config) -> 122: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 123: Name = ?config(room_name, Config), 124: Path = path([Name, "participants"]), 125: Reason = <<"I think you'll like this room!">>, 126: Body = #{sender => escalus_client:short_jid(Alice), 127: recipient => escalus_client:short_jid(Bob), 128: reason => Reason}, 129: {{<<"404">>, _}, <<"room does not exist">>} = rest_helper:post(admin, Path, Body), 130: set_up_room(Config, Alice), 131: {{<<"204">>, _}, <<"">>} = rest_helper:post(admin, Path, Body), 132: Stanza = escalus:wait_for_stanza(Bob), 133: is_direct_invitation(Stanza), 134: direct_invite_has_reason(Stanza, Reason) 135: end). 136: 137: send_message_to_room(Config) -> 138: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(_Alice, Bob) -> 139: Name = ?config(room_name, Config), 140: %% Alice creates a MUC room. 141: muc_helper:start_room([], escalus_users:get_user_by_name(alice), 142: Name, <<"ali">>, []), 143: %% Bob enters the room. 144: escalus:send(Bob, 145: muc_helper:stanza_muc_enter_room(Name, 146: <<"bobcat">>)), 147: escalus:wait_for_stanzas(Bob, 2), 148: %% Parameters for this test. 149: Path = path([Name, "messages"]), 150: Message = <<"Greetings!">>, 151: Body = #{from => escalus_client:short_jid(Bob), 152: body => Message}, 153: {{<<"204">>, _}, <<"">>} = rest_helper:post(admin, Path, Body), 154: Got = escalus:wait_for_stanza(Bob), 155: escalus:assert(is_message, Got), 156: Message = exml_query:path(Got, [{element, <<"body">>}, cdata]) 157: end). 158: 159: kick_user_from_room(Config) -> 160: escalus:fresh_story(Config, 161: [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) -> 162: %% Parameters for this test. 163: Name = ?config(room_name, Config), 164: Path = path([Name, "bobcat"]), 165: %% Alice creates and enters the room. 166: escalus:send(Alice, 167: muc_helper:stanza_muc_enter_room(Name, 168: <<"alibaba">>)), 169: escalus:send(Alice, 170: muc_helper:stanza_default_muc_room(Name, 171: <<"alibaba">>)), 172: %% Alice gets an IQ result, her affiliation information, and 173: %% the room's subject line. 174: escalus:wait_for_stanzas(Alice, 3), 175: %% Bob enters the room. 176: escalus:send(Bob, 177: muc_helper:stanza_muc_enter_room(Name, 178: <<"bobcat">>)), 179: escalus:wait_for_stanzas(Bob, 3), 180: %% Alice sees Bob's presence. 181: escalus:wait_for_stanza(Alice), 182: %% Kate enters the room. 183: escalus:send(Kate, 184: muc_helper:stanza_muc_enter_room(Name, 185: <<"kitkat">>)), 186: escalus:wait_for_stanzas(Kate, 4), 187: %% Alice and Bob see Kate's presence. 188: escalus:wait_for_stanza(Alice), 189: escalus:wait_for_stanza(Bob), 190: %% The HTTP call in question. 191: {{<<"204">>, _}, <<"">>} = rest_helper:delete(admin, Path), 192: BobRoomAddress = muc_helper:room_address(Name, <<"bobcat">>), 193: %% Bob finds out he's been kicked. 194: KickedStanza = escalus:wait_for_stanza(Bob), 195: is_unavailable_presence_from(KickedStanza, BobRoomAddress), 196: %% Kate finds out Bob is kicked. 197: is_unavailable_presence_from(escalus:wait_for_stanza(Kate), 198: BobRoomAddress), 199: %% Alice finds out Bob is kicked. 200: is_unavailable_presence_from(escalus:wait_for_stanza(Alice), 201: BobRoomAddress), 202: %% **NOTE**: Alice is a moderator so Bob is kicked through 203: %% her. She recieves and IQ result. 204: escalus:wait_for_stanza(Alice) 205: end). 206: 207: multiparty_multiprotocol(Config) -> 208: MUCPath = path([]), 209: Room = ?config(room_name, Config), 210: RoomInvitePath = path([Room, "participants"]), 211: Reason = <<"I think you'll like this room!">>, 212: MessagePath = path([Room, "messages"]), 213: Message = <<"Greetings!">>, 214: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], 215: fun(Alice, Bob, Kate) -> 216: %% XMPP: Bob does not see a MUC room called 'wonderland'. 217: false = user_sees_room(Bob, Room), 218: %% HTTP: create a room on Alice's behalf. 219: {{<<"201">>, _}, Room} = 220: rest_helper:post(admin, MUCPath, 221: #{name => Room, 222: owner => escalus_client:short_jid(Alice), 223: nick => <<"ali">>}), 224: %% See comments under the create room test case. 225: escalus:wait_for_stanzas(Alice, 3), 226: %% XMPP: Kate sees the MUC room. 227: true = user_sees_room(Kate, Room), 228: %% HTTP: Alice invites Bob to the MUC room. 229: {{<<"204">>, _}, <<"">>} = 230: rest_helper:post(admin, RoomInvitePath, 231: invite_body(Alice, Bob, Reason)), 232: %% XMPP: Bob recieves the invite to the MUC room. 233: Room = wait_for_invite(Bob, Reason), 234: %% HTTP: Alice invites Kate to the MUC room. 235: {{<<"204">>, _}, <<"">>} = 236: rest_helper:post(admin, RoomInvitePath, 237: invite_body(Alice, Kate, Reason)), 238: %% XMPP: kate recieves the invite to the MUC room. 239: Room = wait_for_invite(Kate, Reason), 240: %% XMPP: Bob joins the MUC room with the JID he recieved. 241: escalus:send(Bob, 242: muc_helper:stanza_muc_enter_room(Room, 243: <<"bobcat">>)), 244: %% Bob gets precense informing him of his room occupancy, 245: %% he recieves a presence informing him about Alice's 246: %% affiliation and occupancy, and (presumably what is 247: %% intended to be) the room subject. See 7.1 in the XEP. 248: escalus:wait_for_stanzas(Bob, 3), 249: %% Alice sees Bob's presence. 250: escalus:wait_for_stanza(Alice), 251: %% XMPP: kate joins the MUC room with the JID she recieved. 252: escalus:send(Kate, 253: muc_helper:stanza_muc_enter_room(Room, 254: <<"kitkat">>)), 255: %% Kate gets analogous stanza's to Bob + Bob's presence. 256: escalus:wait_for_stanzas(Kate, 4), 257: %% Alice & Bob get's Kate's presence. 258: [ escalus:wait_for_stanza(User) || User <- [Alice, Bob] ], 259: %% HTTP: Alice sends a message to the room. 260: {{<<"204">>, _}, <<"">>} = 261: rest_helper:post(admin, MessagePath, 262: #{from => escalus_client:short_jid(Alice), 263: body => Message}), 264: %% XMPP: All three recieve the message sent to the MUC room. 265: [ Message = wait_for_group_message(User) || User <- [Alice, Bob, Kate] ], 266: %% XMPP: Bob and Kate send a message to the MUC room. 267: [ user_sends_message_to_room(U, M, Room) 268: || {U, M} <- [{Bob, <<"I'm Bob.">>}, {Kate, <<"I'm Kate.">>}] ], 269: %% XMPP: Alice recieves the messages from Bob and Kate. 270: BobRoomJID = muc_helper:room_address(Room, <<"bobcat">>), 271: KateRoomJID = muc_helper:room_address(Room, <<"kitkat">>), 272: 273: ?assertEqual([{BobRoomJID, <<"I'm Bob.">>}, {KateRoomJID, <<"I'm Kate.">>}], 274: user_sees_message_from(Alice, Room, 2)) 275: end). 276: 277: failed_invites(Config) -> 278: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 279: Name = set_up_room(Config, Alice), 280: BAlice = escalus_client:short_jid(Alice), 281: BBob = escalus_client:short_jid(Bob), 282: % non-existing room 283: {{<<"404">>, _}, <<"room does not exist">>} = send_invite(<<"thisroomdoesnotexist">>, BAlice, BBob), 284: % invite with bad jid 285: {{<<"400">>, _}, <<"Invalid jid:", _/binary>>} = send_invite(Name, BAlice, <<"@badjid">>), 286: {{<<"400">>, _}, <<"Invalid jid:", _/binary>>} = send_invite(Name, <<"@badjid">>, BBob), 287: ok 288: end). 289: 290: failed_messages(Config) -> 291: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 292: Name = set_up_room(Config, Alice), 293: % non-existing room 294: BAlice = escalus_client:short_jid(Alice), 295: BBob = escalus_client:short_jid(Bob), 296: {{<<"404">>, _}, <<"room does not exist">>} = send_invite(<<"thisroomdoesnotexist">>, BAlice, BBob), 297: % invite with bad jid 298: {{<<"400">>, _}, <<"Invalid jid:", _/binary>>} = send_invite(Name, BAlice, <<"@badjid">>), 299: {{<<"400">>, _}, <<"Invalid jid:", _/binary>>} = send_invite(Name, <<"@badjid">>, BBob), 300: ok 301: end). 302: 303: 304: %%-------------------------------------------------------------------- 305: %% Ancillary (adapted from the MUC suite) 306: %%-------------------------------------------------------------------- 307: 308: set_up_room(Config, Alice) -> 309: % create a room first 310: Name = ?config(room_name, Config), 311: Path = path([]), 312: Body = #{name => Name, 313: owner => escalus_client:short_jid(Alice), 314: nick => <<"ali">>}, 315: Res = rest_helper:post(admin, Path, Body), 316: {{<<"201">>, _}, Name} = Res, 317: Name. 318: 319: send_invite(RoomName, BinFrom, BinTo) -> 320: Path = path([RoomName, "participants"]), 321: Reason = <<"I think you'll like this room!">>, 322: Body = #{sender => BinFrom, 323: recipient => BinTo, 324: reason => Reason}, 325: rest_helper:post(admin, Path, Body). 326: 327: make_distinct_name(Prefix) -> 328: {_, S, US} = os:timestamp(), 329: L = lists:flatten([integer_to_list(S rem 100), ".", integer_to_list(US)]), 330: Suffix = list_to_binary(L), 331: %% The bove is adapted from `escalus_fresh'. 332: <<Prefix/binary, $-, Suffix/binary>>. 333: 334: stanza_get_rooms() -> 335: escalus_stanza:setattr(escalus_stanza:iq_get(?NS_DISCO_ITEMS, []), <<"to">>, 336: muc_helper:muc_host()). 337: 338: has_room(JID, #xmlel{children = [ #xmlel{children = Rooms} ]}) -> 339: RoomPred = fun(Item) -> 340: exml_query:attr(Item, <<"jid">>) == JID 341: end, 342: lists:any(RoomPred, Rooms). 343: 344: is_direct_invitation(Stanza) -> 345: escalus:assert(is_message, Stanza), 346: ?NS_JABBER_X_CONF = exml_query:path(Stanza, [{element, <<"x">>}, {attr, <<"xmlns">>}]). 347: 348: direct_invite_has_reason(Stanza, Reason) -> 349: Reason = exml_query:path(Stanza, [{element, <<"x">>}, {attr, <<"reason">>}]). 350: 351: invite_body(Sender, Recipient, Reason) -> 352: #{sender => escalus_client:short_jid(Sender), 353: recipient => escalus_client:short_jid(Recipient), 354: reason => Reason}. 355: 356: wait_for_invite(Recipient, Reason) -> 357: Stanza = escalus:wait_for_stanza(Recipient), 358: is_direct_invitation(Stanza), 359: direct_invite_has_reason(Stanza, Reason), 360: get_room_name(get_room_jid(Stanza)). 361: 362: get_room_jid(#xmlel{children = [ Invite ]}) -> 363: exml_query:attr(Invite, <<"jid">>). 364: 365: wait_for_group_message(Recipient) -> 366: Got = escalus:wait_for_stanza(Recipient), 367: escalus:assert(is_message, Got), 368: exml_query:path(Got, [{element, <<"body">>}, cdata]). 369: 370: user_sees_room(User, Room) -> 371: escalus:send(User, stanza_get_rooms()), 372: Stanza = escalus:wait_for_stanza(User), 373: escalus:assert(is_stanza_from, [muc_helper:muc_host()], Stanza), 374: has_room(muc_helper:room_address(Room), Stanza). 375: 376: get_room_name(JID) -> 377: escalus_utils:get_username(JID). 378: 379: user_sends_message_to_room(User, Message, Room) -> 380: Chat = escalus_stanza:chat_to(muc_helper:room_address(Room), Message), 381: Stanza = escalus_stanza:setattr(Chat, <<"type">>, <<"groupchat">>), 382: escalus:send(User, muc_helper:stanza_to_room(Stanza, Room)). 383: 384: user_sees_message_from(User, Room, Times) -> 385: user_sees_message_from(User, Room, Times, []). 386: 387: user_sees_message_from(_, _, 0, Messages) -> 388: lists:sort(Messages); 389: user_sees_message_from(User, Room, Times, Messages) -> 390: Stanza = escalus:wait_for_stanza(User), 391: UserRoomJID = exml_query:path(Stanza, [{attr, <<"from">>}]), 392: Body = exml_query:path(Stanza, [{element, <<"body">>}, cdata]), 393: user_sees_message_from(User, Room, Times - 1, [{UserRoomJID, Body} | Messages]). 394: 395: is_unavailable_presence_from(Stanza, RoomJID) -> 396: escalus:assert(is_presence_with_type, [<<"unavailable">>], Stanza), 397: escalus_assert:is_stanza_from(RoomJID, Stanza). 398: 399: path(Items) -> 400: AllItems = ["mucs", domain() | Items], 401: iolist_to_binary([[$/, Item] || Item <- AllItems]).