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