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 MUC Light 18: %%============================================================================== 19: 20: -module(muc_light_http_api_SUITE). 21: -compile([export_all, nowarn_export_all]). 22: 23: -include_lib("common_test/include/ct.hrl"). 24: -include_lib("eunit/include/eunit.hrl"). 25: -include_lib("escalus/include/escalus.hrl"). 26: -include_lib("escalus/include/escalus_xmlns.hrl"). 27: -include_lib("exml/include/exml.hrl"). 28: 29: -import(muc_light_helper, [stanza_create_room/3]). 30: -import(distributed_helper, [subhost_pattern/1]). 31: -import(domain_helper, [host_type/0, domain/0]). 32: -import(config_parser_helper, [mod_config/2]). 33: -import(rest_helper, [putt/3, post/3, delete/2]). 34: 35: %%-------------------------------------------------------------------- 36: %% Suite configuration 37: %%-------------------------------------------------------------------- 38: 39: all() -> 40: [{group, positive}, 41: {group, negative}]. 42: 43: groups() -> 44: [{positive, [parallel], success_response()}, 45: {negative, [parallel], negative_response()}]. 46: 47: success_response() -> 48: [create_unique_room, 49: create_identifiable_room, 50: invite_to_room, 51: send_message_to_room, 52: delete_room 53: ]. 54: 55: negative_response() -> 56: [create_room_errors, 57: create_identifiable_room_errors, 58: invite_to_room_errors, 59: send_message_errors, 60: delete_room_errors]. 61: 62: %%-------------------------------------------------------------------- 63: %% Init & teardown 64: %%-------------------------------------------------------------------- 65: 66: init_per_suite(Config) -> 67: Config1 = dynamic_modules:save_modules(host_type(), Config), 68: dynamic_modules:ensure_modules(host_type(), required_modules()), 69: escalus:init_per_suite(Config1). 70: 71: end_per_suite(Config) -> 72: escalus_fresh:clean(), 73: dynamic_modules:restore_modules(Config), 74: escalus:end_per_suite(Config). 75: 76: init_per_group(_GroupName, Config) -> 77: escalus:create_users(Config, escalus:get_users([alice, bob, kate])). 78: 79: end_per_group(_GroupName, Config) -> 80: escalus:delete_users(Config, escalus:get_users([alice, bob, kate])). 81: 82: init_per_testcase(CaseName, Config) -> 83: escalus:init_per_testcase(CaseName, Config). 84: 85: end_per_testcase(CaseName, Config) -> 86: escalus:end_per_testcase(CaseName, Config). 87: 88: required_modules() -> 89: [{mod_muc_light, 90: mod_config(mod_muc_light, #{rooms_in_rosters => true, 91: backend => mongoose_helper:mnesia_or_rdbms_backend()}) 92: }]. 93: 94: %%-------------------------------------------------------------------- 95: %% Tests 96: %%-------------------------------------------------------------------- 97: 98: create_unique_room(Config) -> 99: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 100: MUCLightDomain = muc_light_domain(), 101: Path = path([MUCLightDomain]), 102: Name = <<"wonderland">>, 103: Body = #{ name => Name, 104: owner => escalus_client:short_jid(Alice), 105: subject => <<"Lewis Carol">> 106: }, 107: {{<<"201">>, _}, _} = rest_helper:post(admin, Path, Body), 108: [Item] = get_disco_rooms(Alice), 109: true = is_room_name(Name, Item), 110: true = is_room_domain(MUCLightDomain, Item) 111: end). 112: 113: create_identifiable_room(Config) -> 114: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 115: MUCLightDomain = muc_light_domain(), 116: Path = path([MUCLightDomain]), 117: RandBits = base16:encode(crypto:strong_rand_bytes(5)), 118: Name = <<"wonderland">>, 119: RoomID = <<"just_some_id_", RandBits/binary>>, 120: RoomIDescaped = escalus_utils:jid_to_lower(RoomID), 121: Body = #{ id => RoomID, 122: name => Name, 123: owner => escalus_client:short_jid(Alice), 124: subject => <<"Lewis Carol">> 125: }, 126: {{<<"201">>, _}, RoomJID} = rest_helper:putt(admin, Path, Body), 127: [Item] = get_disco_rooms(Alice), 128: [RoomIDescaped, MUCLightDomain] = binary:split(RoomJID, <<"@">>), 129: true = is_room_name(Name, Item), 130: true = is_room_domain(MUCLightDomain, Item), 131: true = is_room_id(RoomIDescaped, Item) 132: end). 133: 134: invite_to_room(Config) -> 135: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], 136: fun(Alice, Bob, Kate) -> 137: RoomID = atom_to_binary(?FUNCTION_NAME), 138: Path = path([muc_light_domain(), RoomID, "participants"]), 139: %% XMPP: Alice creates a room. 140: Stt = stanza_create_room(RoomID, 141: [{<<"roomname">>, <<"wonderland">>}], [{Kate, member}]), 142: escalus:send(Alice, Stt), 143: %% XMPP: Alice recieves a affiliation message to herself and 144: %% an IQ result when creating the MUC Light room. 145: escalus:wait_for_stanza(Alice), 146: escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)), 147: %% (*) HTTP: Invite Bob (change room affiliation) on Alice's behalf. 148: Body = #{ sender => escalus_client:short_jid(Alice), 149: recipient => escalus_client:short_jid(Bob) 150: }, 151: {{<<"204">>, _}, <<"">>} = rest_helper:post(admin, Path, Body), 152: %% XMPP: Bob recieves his affiliation information. 153: member_is_affiliated(escalus:wait_for_stanza(Bob), Bob), 154: %% XMPP: Alice recieves Bob's affiliation infromation. 155: member_is_affiliated(escalus:wait_for_stanza(Alice), Bob), 156: %% XMPP: Alice does NOT recieve an IQ result stanza following 157: %% her HTTP request to invite Bob in story point (*). 158: escalus_assert:has_no_stanzas(Alice) 159: end). 160: 161: send_message_to_room(Config) -> 162: RoomID = atom_to_binary(?FUNCTION_NAME), 163: Path = path([muc_light_domain(), RoomID, "messages"]), 164: Text = <<"Hello everyone!">>, 165: escalus:fresh_story(Config, 166: [{alice, 1}, {bob, 1}, {kate, 1}], 167: fun(Alice, Bob, Kate) -> 168: %% XMPP: Alice creates a room. 169: escalus:send(Alice, stanza_create_room(RoomID, 170: [{<<"roomname">>, <<"wonderland">>}], [{Bob, member}, {Kate, member}])), 171: %% XMPP: Alice gets her own affiliation info 172: escalus:wait_for_stanza(Alice), 173: %% XMPP: And Alice gets IQ result 174: CreationResult = escalus:wait_for_stanza(Alice), 175: escalus:assert(is_iq_result, CreationResult), 176: %% XMPP: Get Bob and Kate recieve their affiliation information. 177: [ escalus:wait_for_stanza(U) || U <- [Bob, Kate] ], 178: %% HTTP: Alice sends a message to the MUC room. 179: Body = #{ from => escalus_client:short_jid(Alice), 180: body => Text 181: }, 182: {{<<"204">>, _}, <<"">>} = rest_helper:post(admin, Path, Body), 183: %% XMPP: Both Bob and Kate see the message. 184: [ see_message_from_user(U, Alice, Text) || U <- [Bob, Kate] ] 185: end). 186: 187: delete_room(Config) -> 188: RoomID = atom_to_binary(?FUNCTION_NAME), 189: RoomName = <<"wonderland">>, 190: escalus:fresh_story(Config, 191: [{alice, 1}, {bob, 1}, {kate, 1}], 192: fun(Alice, Bob, Kate)-> 193: {{<<"204">>, <<"No Content">>}, <<"">>} = 194: check_delete_room(Config, RoomName, RoomID, RoomID, 195: Alice, [Bob, Kate]) 196: end). 197: 198: create_room_errors(Config) -> 199: Config1 = escalus_fresh:create_users(Config, [{alice, 1}]), 200: AliceJid = escalus_users:get_jid(Config1, alice), 201: Path = path([muc_light_domain()]), 202: Body = #{name => <<"Name">>, owner => AliceJid, subject => <<"Lewis Carol">>}, 203: {{<<"400">>, _}, <<"Missing room name">>} = 204: post(admin, Path, maps:remove(name, Body)), 205: {{<<"400">>, _}, <<"Missing owner JID">>} = 206: post(admin, Path, maps:remove(owner, Body)), 207: {{<<"400">>, _}, <<"Missing room subject">>} = 208: post(admin, Path, maps:remove(subject, Body)), 209: {{<<"400">>, _}, <<"Invalid owner JID">>} = 210: post(admin, Path, Body#{owner := <<"@invalid">>}), 211: {{<<"400">>, _}, <<"Given user does not exist">>} = 212: post(admin, Path, Body#{owner := <<"baduser@", (domain())/binary>>}), 213: {{<<"404">>, _}, <<"MUC Light server not found">>} = 214: post(admin, path([domain_helper:domain()]), Body). 215: 216: create_identifiable_room_errors(Config) -> 217: Config1 = escalus_fresh:create_users(Config, [{alice, 1}]), 218: AliceJid = escalus_users:get_jid(Config1, alice), 219: Path = path([muc_light_domain()]), 220: Body = #{id => <<"ID">>, name => <<"NameA">>, owner => AliceJid, subject => <<"Lewis Carol">>}, 221: {{<<"201">>, _}, _RoomJID} = putt(admin, Path, Body#{id => <<"ID1">>}), 222: % Fails to create a room with the same ID 223: {{<<"400">>, _}, <<"Missing room ID">>} = 224: putt(admin, Path, maps:remove(id, Body)), 225: {{<<"400">>, _}, <<"Missing room name">>} = 226: putt(admin, Path, maps:remove(name, Body)), 227: {{<<"400">>, _}, <<"Missing owner JID">>} = 228: putt(admin, Path, maps:remove(owner, Body)), 229: {{<<"400">>, _}, <<"Missing room subject">>} = 230: putt(admin, Path, maps:remove(subject, Body)), 231: {{<<"400">>, _}, <<"Invalid owner JID">>} = 232: putt(admin, Path, Body#{owner := <<"@invalid">>}), 233: {{<<"400">>, _}, <<"Given user does not exist">>} = 234: post(admin, Path, Body#{owner := <<"baduser@", (domain())/binary>>}), 235: {{<<"403">>, _}, <<"Room already exists">>} = 236: putt(admin, Path, Body#{id := <<"ID1">>, name := <<"NameB">>}), 237: {{<<"404">>, _}, <<"MUC Light server not found">>} = 238: putt(admin, path([domain_helper:domain()]), Body). 239: 240: invite_to_room_errors(Config) -> 241: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 242: AliceJid = escalus_users:get_jid(Config1, alice), 243: BobJid = escalus_users:get_jid(Config1, bob), 244: Name = jid:nodeprep(<<(escalus_users:get_username(Config1, alice))/binary, "-room">>), 245: muc_light_helper:create_room(Name, muc_light_domain(), alice, [], Config1, <<"v1">>), 246: Path = path([muc_light_domain(), Name, "participants"]), 247: Body = #{sender => AliceJid, recipient => BobJid}, 248: {{<<"400">>, _}, <<"Missing recipient JID">>} = 249: rest_helper:post(admin, Path, maps:remove(recipient, Body)), 250: {{<<"400">>, _}, <<"Missing sender JID">>} = 251: rest_helper:post(admin, Path, maps:remove(sender, Body)), 252: {{<<"400">>, _}, <<"Invalid recipient JID">>} = 253: rest_helper:post(admin, Path, Body#{recipient := <<"@invalid">>}), 254: {{<<"400">>, _}, <<"Invalid sender JID">>} = 255: rest_helper:post(admin, Path, Body#{sender := <<"@invalid">>}), 256: {{<<"400">>, _}, <<"Given user does not exist">>} = 257: rest_helper:post(admin, Path, Body#{sender := <<"baduser@", (domain())/binary>>}), 258: {{<<"403">>, _}, <<"Given user does not occupy this room">>} = 259: rest_helper:post(admin, Path, Body#{sender := BobJid, recipient := AliceJid}), 260: {{<<"404">>, _}, <<"Room not found">>} = 261: rest_helper:post(admin, path([muc_light_domain(), "badroom", "participants"]), Body), 262: {{<<"404">>, _}, <<"MUC Light server not found">>} = 263: rest_helper:post(admin, path([domain(), Name, "participants"]), Body). 264: 265: send_message_errors(Config) -> 266: Config1 = escalus_fresh:create_users(Config, [{alice, 1}, {bob, 1}]), 267: AliceJid = escalus_users:get_jid(Config1, alice), 268: BobJid = escalus_users:get_jid(Config1, bob), 269: Name = jid:nodeprep(<<(escalus_users:get_username(Config1, alice))/binary, "-room">>), 270: muc_light_helper:create_room(Name, muc_light_domain(), alice, [], Config1, <<"v1">>), 271: Path = path([muc_light_domain(), Name, "messages"]), 272: Body = #{from => AliceJid, body => <<"hello">>}, 273: {{<<"204">>, _}, <<>>} = 274: rest_helper:post(admin, Path, Body), 275: {{<<"400">>, _}, <<"Missing message body">>} = 276: rest_helper:post(admin, Path, maps:remove(body, Body)), 277: {{<<"400">>, _}, <<"Missing sender JID">>} = 278: rest_helper:post(admin, Path, maps:remove(from, Body)), 279: {{<<"400">>, _}, <<"Invalid sender JID">>} = 280: rest_helper:post(admin, Path, Body#{from := <<"@invalid">>}), 281: {{<<"400">>, _}, <<"Given user does not exist">>} = 282: rest_helper:post(admin, Path, Body#{from := <<"baduser@", (domain())/binary>>}), 283: {{<<"403">>, _}, <<"Given user does not occupy this room">>} = 284: rest_helper:post(admin, Path, Body#{from := BobJid}), 285: {{<<"404">>, _}, <<"Room not found">>} = 286: rest_helper:post(admin, path([muc_light_domain(), "badroom", "messages"]), Body), 287: {{<<"404">>, _}, <<"MUC Light server not found">>} = 288: rest_helper:post(admin, path([domain(), Name, "messages"]), Body). 289: 290: delete_room_errors(_Config) -> 291: {{<<"400">>, _}, <<"Invalid room ID or domain name">>} = 292: delete(admin, path([muc_light_domain(), "@badroom", "management"])), 293: {{<<"404">>, _}, _} = 294: delete(admin, path([muc_light_domain()])), 295: {{<<"404">>, _}, _} = 296: delete(admin, path([muc_light_domain(), "badroom"])), 297: {{<<"404">>, _}, <<"Room not found">>} = 298: delete(admin, path([muc_light_domain(), "badroom", "management"])), 299: {{<<"404">>, _}, <<"MUC Light server not found">>} = 300: delete(admin, path([domain(), "badroom", "management"])), 301: {{<<"404">>, _}, <<"MUC Light server not found">>} = 302: delete(admin, path(["baddomain", "badroom", "management"])). 303: 304: %%-------------------------------------------------------------------- 305: %% Ancillary (borrowed and adapted from the MUC and MUC Light suites) 306: %%-------------------------------------------------------------------- 307: 308: get_disco_rooms(User) -> 309: DiscoStanza = escalus_stanza:to(escalus_stanza:iq_get(?NS_DISCO_ITEMS, []), muc_light_domain()), 310: escalus:send(User, DiscoStanza), 311: Stanza = escalus:wait_for_stanza(User), 312: XNamespaces = exml_query:paths(Stanza, [{element, <<"query">>}, {attr, <<"xmlns">>}]), 313: true = lists:member(?NS_DISCO_ITEMS, XNamespaces), 314: escalus:assert(is_stanza_from, [muc_light_domain()], Stanza), 315: exml_query:paths(Stanza, [{element, <<"query">>}, {element, <<"item">>}]). 316: 317: is_room_name(Name, Item) -> 318: Name == exml_query:attr(Item, <<"name">>). 319: 320: is_room_domain(Domain, Item) -> 321: JID = exml_query:attr(Item, <<"jid">>), 322: [_, Got] = binary:split(JID, <<$@>>, [global]), 323: Domain == Got. 324: 325: is_room_id(Id, Item) -> 326: JID = exml_query:attr(Item, <<"jid">>), 327: [Got, _] = binary:split(JID, <<$@>>, [global]), 328: Id == Got. 329: 330: see_message_from_user(User, Sender, Contents) -> 331: Stanza = escalus:wait_for_stanza(User), 332: #xmlel{ name = <<"message">> } = Stanza, 333: SenderJID = escalus_utils:jid_to_lower(escalus_utils:get_short_jid(Sender)), 334: From = exml_query:path(Stanza, [{attr, <<"from">>}]), 335: {_, _} = binary:match(From, SenderJID), 336: Contents = exml_query:path(Stanza, [{element, <<"body">>}, cdata]). 337: 338: member_is_affiliated(Stanza, User) -> 339: MemberJID = escalus_utils:jid_to_lower(escalus_utils:get_short_jid(User)), 340: Data = exml_query:path(Stanza, [{element, <<"x">>}, {element, <<"user">>}, cdata]), 341: MemberJID == Data. 342: 343: check_delete_room(_Config, RoomName, RoomIDToCreate, RoomIDToDelete, RoomOwner, RoomMembers) -> 344: Members = [{Member, member} || Member <- RoomMembers], 345: escalus:send(RoomOwner, stanza_create_room(RoomIDToCreate, 346: [{<<"roomname">>, RoomName}], 347: Members)), 348: %% XMPP RoomOwner gets affiliation and IQ result 349: Affiliations = [{RoomOwner, owner} | Members], 350: muc_light_helper:verify_aff_bcast([{RoomOwner, owner}], Affiliations), 351: %% and now RoomOwner gets IQ result 352: CreationResult = escalus:wait_for_stanza(RoomOwner), 353: escalus:assert(is_iq_result, CreationResult), 354: muc_light_helper:verify_aff_bcast(Members, Affiliations), 355: Path = path([muc_light_domain(), RoomIDToDelete, "management"]), 356: rest_helper:delete(admin, Path). 357: 358: %%-------------------------------------------------------------------- 359: %% Helpers 360: %%-------------------------------------------------------------------- 361: 362: path(Items) -> 363: AllItems = ["muc-lights" | Items], 364: iolist_to_binary([[$/, Item] || Item <- AllItems]). 365: 366: muc_light_domain() -> 367: muc_light_helper:muc_host().