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