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